default-avatar-generator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 74971c4d340f209e3c83dc104158b023f74ce58c47bbf01c99b4f716bb2b75d0
4
+ data.tar.gz: 34d505f98d67c5d944001bd5a0d7de6b78bf9dd75226088cc9e53416c0678890
5
+ SHA512:
6
+ metadata.gz: '03832b376aad000e209eafdd17c114aa94672e8916013375ac4eff993eb0652a8b1d4646e3dcea81deece7b802ac3f233dad14c010fe939164d109fdfe69b1d0'
7
+ data.tar.gz: f3203f2a6adc655f5a8ec8e736aafa586e10550e0082fbee9ce1c269fc4a8bce666d61d94003be78c9f41f80883429c04cdf491d4e4dd00ca22d83d2d96bb637
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2024-04-08
9
+
10
+ ### Added
11
+
12
+ - Initial project setup as a ruby gem
13
+ - Generates SVG and JPEG images based on the included SVG files
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Default Avatar Generator
2
+
3
+ [![CI](https://github.com/pas256/default-avatar-generator/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/pas256/default-avatar-generator/actions/workflows/ci.yml)
4
+
5
+ A Ruby gem that generates beautiful default avatars for user accounts when no custom avatar is provided.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'default-avatar-generator'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install default-avatar-generator
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require 'default_avatar_generator'
27
+
28
+ # Generate an avatar for a user
29
+ generator = DefaultAvatarGenerator::Generator.new(text: { character: "A" })
30
+ svg_avatar = generator.generate
31
+ jpeg_avatar = DefaultAvatarGenerator::ImageConverter.svg_to_jpeg(svg_avatar)
32
+ # Save the avatar to a file (if you need to)
33
+ ```
34
+
35
+ ## Development
36
+
37
+ After checking out the repo:
38
+
39
+ bundle install
40
+ ./viewer
41
+
42
+ This will bring up a development server on http://localhost:9292 where you can reload the page and keep generating new avatars.
43
+
44
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
45
+
46
+ ## Contributing
47
+
48
+ Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).
49
+
50
+ ## License
51
+
52
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <!-- Definitions for the gradients -->
4
+ <defs>
5
+ <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
6
+ <stop offset="0%" stop-color="{{color1}}" />
7
+ <stop offset="100%" stop-color="{{color2}}" />
8
+ </linearGradient>
9
+ <linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
10
+ <stop offset="0%" stop-color="{{color3}}" />
11
+ <stop offset="100%" stop-color="{{color4}}" />
12
+ </linearGradient>
13
+ </defs>
14
+
15
+ <!-- Three horizontal bands with gradients -->
16
+ <rect x="0" y="0" width="512" height="256" fill="url(#gradient1)" />
17
+ <rect x="0" y="256" width="512" height="256" fill="url(#gradient2)" />
18
+ </g>
19
+ </svg>
@@ -0,0 +1,24 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <!-- Definitions for the gradients -->
4
+ <defs>
5
+ <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
6
+ <stop offset="0%" stop-color="{{color1}}" />
7
+ <stop offset="100%" stop-color="{{color2}}" />
8
+ </linearGradient>
9
+ <linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
10
+ <stop offset="0%" stop-color="{{color3}}" />
11
+ <stop offset="100%" stop-color="{{color4}}" />
12
+ </linearGradient>
13
+ <linearGradient id="gradient3" x1="0%" y1="0%" x2="100%" y2="0%">
14
+ <stop offset="0%" stop-color="{{color5}}" />
15
+ <stop offset="100%" stop-color="{{color6}}" />
16
+ </linearGradient>
17
+ </defs>
18
+
19
+ <!-- Three horizontal bands with gradients -->
20
+ <rect x="0" y="0" width="512" height="170" fill="url(#gradient1)" />
21
+ <rect x="0" y="170" width="512" height="172" fill="url(#gradient2)" />
22
+ <rect x="0" y="342" width="512" height="170" fill="url(#gradient3)" />
23
+ </g>
24
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <defs>
4
+ <linearGradient id="gradient1" x1="0%" y1="100%" x2="100%" y2="0%">
5
+ <stop offset="0%" stop-color="{{color1}}" />
6
+ <stop offset="100%" stop-color="{{color2}}" />
7
+ </linearGradient>
8
+ </defs>
9
+
10
+ <rect x="0" y="0" width="512" height="512" fill="url(#gradient1)" />
11
+ </g>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <defs>
4
+ <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
5
+ <stop offset="0%" stop-color="{{color1}}" />
6
+ <stop offset="100%" stop-color="{{color2}}" />
7
+ </linearGradient>
8
+ </defs>
9
+
10
+ <rect x="0" y="0" width="512" height="512" fill="url(#gradient1)" />
11
+ </g>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <defs>
4
+ <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
5
+ <stop offset="0%" stop-color="{{color1}}" />
6
+ <stop offset="100%" stop-color="{{color2}}" />
7
+ </linearGradient>
8
+ </defs>
9
+
10
+ <rect x="0" y="0" width="512" height="512" fill="url(#gradient1)" />
11
+ </g>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <defs>
4
+ <linearGradient id="gradient1" x1="0%" y1="0%" x2="0%" y2="100%">
5
+ <stop offset="0%" stop-color="{{color1}}" />
6
+ <stop offset="100%" stop-color="{{color2}}" />
7
+ </linearGradient>
8
+ </defs>
9
+
10
+ <rect x="0" y="0" width="512" height="512" fill="url(#gradient1)" />
11
+ </g>
12
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <rect x="0" y="0" width="512" height="512" fill="{{color1}}" />
3
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g transform="{{transformation}}">
3
+ <path
4
+ d="M 276.506 38.653 C 251.084 38.653 226.657 44.164 203.86 54.93 C 174.909 68.413 150.391 89.677 132.93 116.455 C 114.927 144.047 105.427 175.983 105.427 209.005 L 105.427 232.257 L 74.668 291.062 C 68.787 302.371 68.515 313.951 73.944 322.998 C 79.372 332.044 89.776 337.201 102.532 337.201 L 105.427 337.201 L 105.427 383.432 C 105.427 408.493 125.784 428.849 150.843 428.849 C 151.477 428.849 152.02 428.849 152.651 428.757 L 184.226 424.052 L 184.226 455.807 C 184.226 456.261 184.226 456.805 184.317 457.257 C 185.222 468.475 194.27 476.798 205.759 476.798 C 207.117 476.798 208.473 476.708 209.92 476.435 L 374.126 447.304 C 386.066 445.223 395.476 434.006 395.476 421.882 L 395.476 330.506 C 428.137 298.573 446.682 254.512 446.682 208.823 C 446.682 115.01 370.324 38.653 276.506 38.653 Z M 374.94 316.213 C 372.405 318.566 370.959 321.822 370.959 325.168 L 370.959 421.702 C 370.867 422.154 370.235 422.968 369.783 423.149 L 208.563 451.646 L 208.473 409.578 C 208.473 406.049 206.935 402.613 204.22 400.35 C 201.96 398.45 199.154 397.364 196.26 397.364 C 195.626 397.364 195.084 397.364 194.449 397.456 L 149.849 404.059 C 138.631 403.607 129.673 394.379 129.673 383.07 L 129.673 324.626 C 129.673 317.841 124.245 312.412 117.46 312.412 L 102.532 312.412 C 97.918 312.412 95.476 311.147 94.843 310.06 C 94.208 308.974 94.12 306.171 96.289 302.099 L 128.496 240.67 C 129.402 238.95 129.855 236.96 129.855 234.97 L 129.855 208.733 C 129.855 152.275 162.966 100.529 214.174 76.737 L 214.264 76.737 C 233.715 67.691 254.704 63.073 276.506 63.073 C 356.845 63.073 422.256 128.484 422.256 208.823 C 422.256 249.537 404.976 288.71 374.94 316.213 Z"
5
+ fill="{{color}}" transform="matrix(1, 0, 0, 1, -1.4210854715202004e-14, 0)" />
6
+ </g>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g transform="{{transformation}}">
3
+ <path
4
+ d="M 276.506 38.653 C 251.084 38.653 226.657 44.164 203.86 54.93 C 174.909 68.413 150.391 89.677 132.93 116.455 C 114.927 144.047 105.427 175.983 105.427 209.005 L 105.427 232.257 L 74.668 291.062 C 68.787 302.371 68.515 313.951 73.944 322.998 C 79.372 332.044 89.776 337.201 102.532 337.201 L 105.427 337.201 L 105.427 383.432 C 105.427 408.493 125.784 428.849 150.843 428.849 C 151.477 428.849 152.02 428.849 152.651 428.757 L 184.226 424.052 L 184.226 455.807 C 184.226 456.261 184.226 456.805 184.317 457.257 C 185.222 468.475 194.27 476.798 205.759 476.798 C 207.117 476.798 208.473 476.708 209.92 476.435 L 374.126 447.304 C 386.066 445.223 395.476 434.006 395.476 421.882 L 395.476 330.506 C 428.137 298.573 446.682 254.512 446.682 208.823 C 446.682 115.01 370.324 38.653 276.506 38.653 Z"
5
+ fill="{{color}}" transform="matrix(1, 0, 0, 1, -1.4210854715202004e-14, 0)" />
6
+ </g>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g transform="{{transformation}}">
3
+ <path
4
+ d="M204.6,20.6c-46,3.2-86.2,19.6-113,46c-10.8,10.8-17.6,19.6-24.8,32.6C53.6,122.8,46,155.2,46,186.6c0,24.8,5.8,41.6,28.2,80.4c18.4,31.8,24.2,44,31,63.4c7.4,22,10.6,41.4,10.6,67c0,25.2-3.6,48.4-11,76c-1.8,6.4-3.2,12.2-3.2,12.8c0,0.6,1,2.2,2.4,3.4l2,1.8h108.4h108.4l2.2-2.4c2.4-2.2,2.4-2.4,2.4-19.2c0-18.4,1.6-33.8,4.4-41c1.6-4.2,2-4.6,5.8-5.8c2.2-0.6,11.8-1.6,21.2-2.2c28.2-1.6,39.8-3.8,51-9.4c6.4-3.4,13.6-10,16.4-15.6c2.8-5.4,5-16.2,4.2-21.2c-0.2-2.4-1.4-7-2.4-10.6l-2-6.2l4.8-4.6c5.8-5.8,6.6-8.8,3.8-15.4l-2-4.8l2.4-2.6c5.2-5.4,5.4-9.4,1.4-21.2c-1.6-4.8-2.6-8.8-2.4-9c0.2-0.2,2.8-0.8,5.6-1.2c14.2-2.4,26.4-12.8,26.4-22.6c0-4-3.6-11.2-11.2-22.6c-3.2-4.8-11.2-16.6-17.6-26.2c-6.4-9.4-12-18.2-12.2-19.6c-0.4-1.6,0.2-3.6,2.2-6.8c3.8-6,4.8-9.6,4.8-19.6c0-18-4.8-42.2-11.6-59c-19-47.2-61.4-80.2-122-95C271.2,21.6,233.8,18.6,204.6,20.6z"
5
+ fill="{{color}}" transform="matrix(1, 0, 0, 1.053, 0, -0.529683)" />
6
+ </g>
7
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g transform="{{transformation}}">
3
+ <path
4
+ d="M258.374,20c-27.9,0-54.2,6.2-77.8,17.4c-62.2,28.9-105.4,91.9-105.4,165l0,0v24.3v5.9l-37.2,70.9
5
+ c-9.4,17.9-0.5,32.5,19.7,32.5h17.5v0.4v30.8v36.1c0,21.1,17.2,38.3,38.3,38.3l52.6-7.8l0.1,52.6v0.4l0,0c0.3,6.7,6,11.3,12.9,10
6
+ l189.4-33.6c7.2-1.3,12.9-8.2,12.9-15.5v-8.3v-6.6v-96.4c36.2-33.4,59.1-81.2,59.1-134.4C440.574,99.7,359.174,20,258.374,20z"
7
+ fill="{{color}}" />
8
+ </g>
9
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <text x="256" y="256" font-family="Arial" font-size="220" font-weight="bold" text-anchor="middle"
3
+ dominant-baseline="middle" fill="{{color}}">{{character}}</text>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <text x="256" y="310" font-family="Arial" font-size="220" font-weight="bold" text-anchor="middle"
3
+ fill="{{color}}">{{character}}</text>
4
+ </svg>
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # Background SVG layer
5
+ class BackgroundLayer < Layer
6
+ private
7
+
8
+ def template_path
9
+ File.join(__dir__, '..', 'assets', 'backgrounds', "#{template_name}.svg")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,346 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # List of colors
5
+ class Colors
6
+ class << self
7
+ def all
8
+ COLORS
9
+ end
10
+
11
+ def get(color_name, shade = 500)
12
+ COLORS.dig(color_name.to_sym, shade)
13
+ end
14
+
15
+ def available_colors
16
+ COLORS.keys
17
+ end
18
+
19
+ def select_solid_color
20
+ get(available_colors.sample, solid_shades.sample)
21
+ end
22
+
23
+ def select_contrast_color
24
+ get(available_colors.sample, contrast_shades.sample)
25
+ end
26
+
27
+ def available_shades
28
+ [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
29
+ end
30
+
31
+ def solid_shades
32
+ [300, 400, 500, 600, 700, 800, 900]
33
+ end
34
+
35
+ def contrast_shades
36
+ [50, 100, 950]
37
+ end
38
+
39
+ def opposite_shade(shade)
40
+ shade_map = {
41
+ 50 => 950,
42
+ 100 => 900,
43
+ 200 => 800,
44
+ 300 => 700,
45
+ 400 => 600,
46
+ 500 => 500,
47
+ 600 => 400,
48
+ 700 => 300,
49
+ 800 => 200,
50
+ 900 => 100,
51
+ 950 => 50
52
+ }
53
+ shade_map[shade]
54
+ end
55
+ end
56
+
57
+ COLORS = {
58
+ slate: {
59
+ 50 => '#f8fafc',
60
+ 100 => '#f1f5f9',
61
+ 200 => '#e2e8f0',
62
+ 300 => '#cbd5e1',
63
+ 400 => '#94a3b8',
64
+ 500 => '#64748b',
65
+ 600 => '#475569',
66
+ 700 => '#334155',
67
+ 800 => '#1e293b',
68
+ 900 => '#0f172a',
69
+ 950 => '#020617'
70
+ },
71
+ gray: {
72
+ 50 => '#f9fafb',
73
+ 100 => '#f3f4f6',
74
+ 200 => '#e5e7eb',
75
+ 300 => '#d1d5db',
76
+ 400 => '#9ca3af',
77
+ 500 => '#6b7280',
78
+ 600 => '#4b5563',
79
+ 700 => '#374151',
80
+ 800 => '#1f2937',
81
+ 900 => '#111827',
82
+ 950 => '#030712'
83
+ },
84
+ zinc: {
85
+ 50 => '#fafafa',
86
+ 100 => '#f4f4f5',
87
+ 200 => '#e4e4e7',
88
+ 300 => '#d4d4d8',
89
+ 400 => '#a1a1aa',
90
+ 500 => '#71717a',
91
+ 600 => '#52525b',
92
+ 700 => '#3f3f46',
93
+ 800 => '#27272a',
94
+ 900 => '#18181b',
95
+ 950 => '#09090b'
96
+ },
97
+ neutral: {
98
+ 50 => '#fafafa',
99
+ 100 => '#f5f5f5',
100
+ 200 => '#e5e5e5',
101
+ 300 => '#d4d4d4',
102
+ 400 => '#a3a3a3',
103
+ 500 => '#737373',
104
+ 600 => '#525252',
105
+ 700 => '#404040',
106
+ 800 => '#262626',
107
+ 900 => '#171717',
108
+ 950 => '#0a0a0a'
109
+ },
110
+ stone: {
111
+ 50 => '#fafaf9',
112
+ 100 => '#f5f5f4',
113
+ 200 => '#e7e5e4',
114
+ 300 => '#d6d3d1',
115
+ 400 => '#a8a29e',
116
+ 500 => '#78716c',
117
+ 600 => '#57534e',
118
+ 700 => '#44403c',
119
+ 800 => '#292524',
120
+ 900 => '#1c1917',
121
+ 950 => '#0c0a09'
122
+ },
123
+ red: {
124
+ 50 => '#fef2f2',
125
+ 100 => '#fee2e2',
126
+ 200 => '#fecaca',
127
+ 300 => '#fca5a5',
128
+ 400 => '#f87171',
129
+ 500 => '#ef4444',
130
+ 600 => '#dc2626',
131
+ 700 => '#b91c1c',
132
+ 800 => '#991b1b',
133
+ 900 => '#7f1d1d',
134
+ 950 => '#450a0a'
135
+ },
136
+ orange: {
137
+ 50 => '#fff7ed',
138
+ 100 => '#ffedd5',
139
+ 200 => '#fed7aa',
140
+ 300 => '#fdba74',
141
+ 400 => '#fb923c',
142
+ 500 => '#f97316',
143
+ 600 => '#ea580c',
144
+ 700 => '#c2410c',
145
+ 800 => '#9a3412',
146
+ 900 => '#7c2d12',
147
+ 950 => '#431407'
148
+ },
149
+ amber: {
150
+ 50 => '#fffbeb',
151
+ 100 => '#fef3c7',
152
+ 200 => '#fde68a',
153
+ 300 => '#fcd34d',
154
+ 400 => '#fbbf24',
155
+ 500 => '#f59e0b',
156
+ 600 => '#d97706',
157
+ 700 => '#b45309',
158
+ 800 => '#92400e',
159
+ 900 => '#78350f',
160
+ 950 => '#451a03'
161
+ },
162
+ yellow: {
163
+ 50 => '#fefce8',
164
+ 100 => '#fef9c3',
165
+ 200 => '#fef08a',
166
+ 300 => '#fde047',
167
+ 400 => '#facc15',
168
+ 500 => '#eab308',
169
+ 600 => '#ca8a04',
170
+ 700 => '#a16207',
171
+ 800 => '#854d0e',
172
+ 900 => '#713f12',
173
+ 950 => '#422006'
174
+ },
175
+ lime: {
176
+ 50 => '#f7fee7',
177
+ 100 => '#ecfccb',
178
+ 200 => '#d9f99d',
179
+ 300 => '#bef264',
180
+ 400 => '#a3e635',
181
+ 500 => '#84cc16',
182
+ 600 => '#65a30d',
183
+ 700 => '#4d7c0f',
184
+ 800 => '#3f6212',
185
+ 900 => '#365314',
186
+ 950 => '#1a2e05'
187
+ },
188
+ green: {
189
+ 50 => '#f0fdf4',
190
+ 100 => '#dcfce7',
191
+ 200 => '#bbf7d0',
192
+ 300 => '#86efac',
193
+ 400 => '#4ade80',
194
+ 500 => '#22c55e',
195
+ 600 => '#16a34a',
196
+ 700 => '#15803d',
197
+ 800 => '#166534',
198
+ 900 => '#14532d',
199
+ 950 => '#052e16'
200
+ },
201
+ emerald: {
202
+ 50 => '#ecfdf5',
203
+ 100 => '#d1fae5',
204
+ 200 => '#a7f3d0',
205
+ 300 => '#6ee7b7',
206
+ 400 => '#34d399',
207
+ 500 => '#10b981',
208
+ 600 => '#059669',
209
+ 700 => '#047857',
210
+ 800 => '#065f46',
211
+ 900 => '#064e3b',
212
+ 950 => '#022c22'
213
+ },
214
+ teal: {
215
+ 50 => '#f0fdfa',
216
+ 100 => '#ccfbf1',
217
+ 200 => '#99f6e4',
218
+ 300 => '#5eead4',
219
+ 400 => '#2dd4bf',
220
+ 500 => '#14b8a6',
221
+ 600 => '#0d9488',
222
+ 700 => '#0f766e',
223
+ 800 => '#115e59',
224
+ 900 => '#134e4a',
225
+ 950 => '#042f2e'
226
+ },
227
+ cyan: {
228
+ 50 => '#ecfeff',
229
+ 100 => '#cffafe',
230
+ 200 => '#a5f3fc',
231
+ 300 => '#67e8f9',
232
+ 400 => '#22d3ee',
233
+ 500 => '#06b6d4',
234
+ 600 => '#0891b2',
235
+ 700 => '#0e7490',
236
+ 800 => '#155e75',
237
+ 900 => '#164e63',
238
+ 950 => '#083344'
239
+ },
240
+ sky: {
241
+ 50 => '#f0f9ff',
242
+ 100 => '#e0f2fe',
243
+ 200 => '#bae6fd',
244
+ 300 => '#7dd3fc',
245
+ 400 => '#38bdf8',
246
+ 500 => '#0ea5e9',
247
+ 600 => '#0284c7',
248
+ 700 => '#0369a1',
249
+ 800 => '#075985',
250
+ 900 => '#0c4a6e',
251
+ 950 => '#082f49'
252
+ },
253
+ blue: {
254
+ 50 => '#eff6ff',
255
+ 100 => '#dbeafe',
256
+ 200 => '#bfdbfe',
257
+ 300 => '#93c5fd',
258
+ 400 => '#60a5fa',
259
+ 500 => '#3b82f6',
260
+ 600 => '#2563eb',
261
+ 700 => '#1d4ed8',
262
+ 800 => '#1e40af',
263
+ 900 => '#1e3a8a',
264
+ 950 => '#172554'
265
+ },
266
+ indigo: {
267
+ 50 => '#eef2ff',
268
+ 100 => '#e0e7ff',
269
+ 200 => '#c7d2fe',
270
+ 300 => '#a5b4fc',
271
+ 400 => '#818cf8',
272
+ 500 => '#6366f1',
273
+ 600 => '#4f46e5',
274
+ 700 => '#4338ca',
275
+ 800 => '#3730a3',
276
+ 900 => '#312e81',
277
+ 950 => '#1e1b4b'
278
+ },
279
+ violet: {
280
+ 50 => '#f5f3ff',
281
+ 100 => '#ede9fe',
282
+ 200 => '#ddd6fe',
283
+ 300 => '#c4b5fd',
284
+ 400 => '#a78bfa',
285
+ 500 => '#8b5cf6',
286
+ 600 => '#7c3aed',
287
+ 700 => '#6d28d9',
288
+ 800 => '#5b21b6',
289
+ 900 => '#4c1d95',
290
+ 950 => '#2e1065'
291
+ },
292
+ purple: {
293
+ 50 => '#faf5ff',
294
+ 100 => '#f3e8ff',
295
+ 200 => '#e9d5ff',
296
+ 300 => '#d8b4fe',
297
+ 400 => '#c084fc',
298
+ 500 => '#a855f7',
299
+ 600 => '#9333ea',
300
+ 700 => '#7e22ce',
301
+ 800 => '#6b21a8',
302
+ 900 => '#581c87',
303
+ 950 => '#3b0764'
304
+ },
305
+ fuchsia: {
306
+ 50 => '#fdf4ff',
307
+ 100 => '#fae8ff',
308
+ 200 => '#f5d0fe',
309
+ 300 => '#f0abfc',
310
+ 400 => '#e879f9',
311
+ 500 => '#d946ef',
312
+ 600 => '#c026d3',
313
+ 700 => '#a21caf',
314
+ 800 => '#86198f',
315
+ 900 => '#701a75',
316
+ 950 => '#4a044e'
317
+ },
318
+ pink: {
319
+ 50 => '#fdf2f8',
320
+ 100 => '#fce7f3',
321
+ 200 => '#fbcfe8',
322
+ 300 => '#f9a8d4',
323
+ 400 => '#f472b6',
324
+ 500 => '#ec4899',
325
+ 600 => '#db2777',
326
+ 700 => '#be185d',
327
+ 800 => '#9d174d',
328
+ 900 => '#831843',
329
+ 950 => '#500724'
330
+ },
331
+ rose: {
332
+ 50 => '#fff1f2',
333
+ 100 => '#ffe4e6',
334
+ 200 => '#fecdd3',
335
+ 300 => '#fda4af',
336
+ 400 => '#fb7185',
337
+ 500 => '#f43f5e',
338
+ 600 => '#e11d48',
339
+ 700 => '#be123c',
340
+ 800 => '#9f1239',
341
+ 900 => '#881337',
342
+ 950 => '#4c0519'
343
+ }
344
+ }.freeze
345
+ end
346
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # Composes all of the layers together into a single SVG
5
+ class Composer
6
+ def initialize(layers)
7
+ @layers = layers
8
+ end
9
+
10
+ def compose
11
+ SvgUtils.minify(<<~SVG)
12
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
13
+ #{@layers.map(&:render).join("\n ")}
14
+ </svg>
15
+ SVG
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # Foreground SVG layer
5
+ class ForegroundLayer < Layer
6
+ def self.select_transform
7
+ ['', 'scale(-1, 1) translate(-512, 0)'].sample
8
+ end
9
+
10
+ private
11
+
12
+ def template_path
13
+ File.join(__dir__, '..', 'assets', 'foregrounds', "#{template_name}.svg")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # The Generator class is responsible for creating avatar images with customizable
5
+ # backgrounds, foregrounds, and text layers.
6
+ class Generator
7
+ AVAILABLE_BACKGROUNDS = %w[
8
+ gradient-2h
9
+ gradient-3h
10
+ gradient-backslash
11
+ gradient-slash
12
+ gradient-lr
13
+ gradient-tb
14
+ solid
15
+ ].freeze
16
+
17
+ AVAILABLE_FOREGROUNDS = %w[
18
+ head1-solid
19
+ head2
20
+ head3
21
+ ].freeze
22
+
23
+ # NOTE: 'base' doesn't work because libvips doesn't support `dominant-baseline` in SVG.
24
+ # Can move back once https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/1072
25
+ # makes it way into the latest version.
26
+ AVAILABLE_TEXT = %w[
27
+ hack
28
+ ].freeze
29
+
30
+ def initialize(options = {})
31
+ @options = options
32
+ end
33
+
34
+ def generate # rubocop:disable Metrics/AbcSize
35
+ # Create layers with dynamic parameters
36
+ layers = [
37
+ BackgroundLayer.new(
38
+ select_background,
39
+ background_params.merge(@options[:background] || {})
40
+ ),
41
+ ForegroundLayer.new(
42
+ select_foreground,
43
+ foreground_params.merge(@options[:foreground] || {})
44
+ ),
45
+ TextLayer.new(
46
+ select_text,
47
+ text_params.merge(@options[:text] || {})
48
+ )
49
+ ]
50
+
51
+ Composer.new(layers).compose
52
+ end
53
+
54
+ def select_background
55
+ AVAILABLE_BACKGROUNDS.sample
56
+ end
57
+
58
+ def background_params
59
+ {
60
+ color1: Colors.select_solid_color,
61
+ color2: Colors.select_solid_color,
62
+ color3: Colors.select_solid_color,
63
+ color4: Colors.select_solid_color,
64
+ color5: Colors.select_solid_color,
65
+ color6: Colors.select_solid_color
66
+ }
67
+ end
68
+
69
+ def select_foreground
70
+ AVAILABLE_FOREGROUNDS.sample
71
+ end
72
+
73
+ def foreground_params
74
+ @foreground_color_name = Colors.available_colors.sample
75
+ @foreground_shade = Colors.contrast_shades.sample
76
+ {
77
+ color: Colors.get(@foreground_color_name, @foreground_shade),
78
+ transformation: ForegroundLayer.select_transform
79
+ }
80
+ end
81
+
82
+ def select_text
83
+ AVAILABLE_TEXT.sample
84
+ end
85
+
86
+ def text_params
87
+ {
88
+ color: Colors.get(@foreground_color_name, Colors.opposite_shade(@foreground_shade))
89
+ # character: ('A'..'Z').to_a.sample
90
+ }
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+
5
+ module DefaultAvatarGenerator
6
+ # Converts SVG to other image formats
7
+ class ImageConverter
8
+ def self.svg_to_jpeg(svg_content)
9
+ # Create a temporary file to store the SVG
10
+ temp_svg = Tempfile.new(['avatar', '.svg'])
11
+ begin
12
+ temp_svg.write(svg_content)
13
+ temp_svg.close
14
+
15
+ # Load the SVG using vips and convert to JPEG
16
+ image = Vips::Image.new_from_file(temp_svg.path)
17
+
18
+ # Convert to sRGB color space and save as JPEG to memory
19
+ image = image.colourspace('srgb')
20
+ image.jpegsave_buffer(Q: 90)
21
+ ensure
22
+ # Clean up temporary file
23
+ temp_svg.unlink
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module DefaultAvatarGenerator
6
+ # The Layer class is responsible for rendering SVG templates with given parameters.
7
+ class Layer
8
+ attr_reader :template_name, :params
9
+
10
+ def initialize(template_name, params = {})
11
+ @template_name = template_name
12
+ @params = params
13
+ end
14
+
15
+ def render
16
+ template = load_template
17
+ process_template(template)
18
+ end
19
+
20
+ private
21
+
22
+ def load_template
23
+ # Load the SVG template file
24
+ File.read(template_path)
25
+ end
26
+
27
+ def template_path
28
+ # Implement in subclasses to define where templates are stored
29
+ raise NotImplementedError, "#{self.class} must implement #template_path"
30
+ end
31
+
32
+ def process_template(template)
33
+ # Replace only the parameters that exist in params
34
+ parsed = template.gsub(/\{\{(\w+)\}\}/) do |match|
35
+ param_name = ::Regexp.last_match(1)
36
+ params.key?(param_name.to_sym) ? params[param_name.to_sym].to_s : match
37
+ end
38
+
39
+ # Parse the XML and extract just the first child element inside the svg tag
40
+ doc = Nokogiri::XML(parsed)
41
+ doc.at('svg > *').to_s
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # Utility methods for SVG manipulation
5
+ module SvgUtils
6
+ module_function
7
+
8
+ def minify(svg)
9
+ svg.gsub(/>\s+</, '><').gsub(/\s+/, ' ').strip
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ # Text SVG layer
5
+ class TextLayer < Layer
6
+ private
7
+
8
+ def template_path
9
+ File.join(__dir__, '..', 'assets', 'text', "#{template_name}.svg")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefaultAvatarGenerator
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vips'
4
+ require_relative 'default_avatar_generator/version'
5
+ require_relative 'default_avatar_generator/colors'
6
+ require_relative 'default_avatar_generator/layer'
7
+ require_relative 'default_avatar_generator/background_layer'
8
+ require_relative 'default_avatar_generator/foreground_layer'
9
+ require_relative 'default_avatar_generator/text_layer'
10
+ require_relative 'default_avatar_generator/svg_utils'
11
+ require_relative 'default_avatar_generator/composer'
12
+ require_relative 'default_avatar_generator/generator'
13
+ require_relative 'default_avatar_generator/image_converter'
14
+
15
+ module DefaultAvatarGenerator
16
+ class Error < StandardError; end
17
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: default-avatar-generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Sankauskas
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-09 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: nokogiri
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.18'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.18'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ruby-vips
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.2'
40
+ description: Generates beautiful and unique default avatars for user accounts
41
+ email:
42
+ - peter+avatar@pas.ventures
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - CHANGELOG.md
48
+ - README.md
49
+ - lib/assets/backgrounds/gradient-2h.svg
50
+ - lib/assets/backgrounds/gradient-3h.svg
51
+ - lib/assets/backgrounds/gradient-backslash.svg
52
+ - lib/assets/backgrounds/gradient-lr.svg
53
+ - lib/assets/backgrounds/gradient-slash.svg
54
+ - lib/assets/backgrounds/gradient-tb.svg
55
+ - lib/assets/backgrounds/solid.svg
56
+ - lib/assets/foregrounds/head1-hollow.svg
57
+ - lib/assets/foregrounds/head1-solid.svg
58
+ - lib/assets/foregrounds/head2.svg
59
+ - lib/assets/foregrounds/head3.svg
60
+ - lib/assets/text/base.svg
61
+ - lib/assets/text/hack.svg
62
+ - lib/default_avatar_generator.rb
63
+ - lib/default_avatar_generator/background_layer.rb
64
+ - lib/default_avatar_generator/colors.rb
65
+ - lib/default_avatar_generator/composer.rb
66
+ - lib/default_avatar_generator/foreground_layer.rb
67
+ - lib/default_avatar_generator/generator.rb
68
+ - lib/default_avatar_generator/image_converter.rb
69
+ - lib/default_avatar_generator/layer.rb
70
+ - lib/default_avatar_generator/svg_utils.rb
71
+ - lib/default_avatar_generator/text_layer.rb
72
+ - lib/default_avatar_generator/version.rb
73
+ homepage: https://github.com/pas256/default-avatar-generator
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ homepage_uri: https://github.com/pas256/default-avatar-generator
78
+ source_code_uri: https://github.com/pas256/default-avatar-generator
79
+ changelog_uri: https://github.com/pas256/default-avatar-generator/blob/main/CHANGELOG.md
80
+ rubygems_mfa_required: 'true'
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 3.0.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.6.2
96
+ specification_version: 4
97
+ summary: Generate unique, default avatars for user accounts
98
+ test_files: []