boring_avatars 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 +7 -0
- data/LICENSE +22 -0
- data/README.md +62 -0
- data/THIRD_PARTY_NOTICES.md +30 -0
- data/docs/architecture.md +161 -0
- data/docs/core-library.md +257 -0
- data/docs/rails-binding.md +152 -0
- data/docs/testing-and-compatibility.md +201 -0
- data/lib/boring_avatars/bindings/rails/view_helper.rb +105 -0
- data/lib/boring_avatars/bindings/rails.rb +11 -0
- data/lib/boring_avatars/identifier.rb +25 -0
- data/lib/boring_avatars/input.rb +155 -0
- data/lib/boring_avatars/name_hash.rb +19 -0
- data/lib/boring_avatars/renderer.rb +91 -0
- data/lib/boring_avatars/svg/element.rb +16 -0
- data/lib/boring_avatars/svg/serializer.rb +67 -0
- data/lib/boring_avatars/svg/value.rb +17 -0
- data/lib/boring_avatars/utilities.rb +34 -0
- data/lib/boring_avatars/variants/bauhaus.rb +76 -0
- data/lib/boring_avatars/variants/beam.rb +130 -0
- data/lib/boring_avatars/variants/marble.rb +85 -0
- data/lib/boring_avatars/variants/pixel.rb +49 -0
- data/lib/boring_avatars/variants/ring.rb +42 -0
- data/lib/boring_avatars/variants/sunset.rb +57 -0
- data/lib/boring_avatars/variants.rb +32 -0
- data/lib/boring_avatars/version.rb +5 -0
- data/lib/boring_avatars.rb +34 -0
- data/rbi/boring_avatars.rbi +70 -0
- data/sig/boring_avatars.rbs +44 -0
- metadata +155 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "identifier"
|
|
4
|
+
require_relative "name_hash"
|
|
5
|
+
require_relative "svg/serializer"
|
|
6
|
+
require_relative "variants/bauhaus"
|
|
7
|
+
require_relative "variants/beam"
|
|
8
|
+
require_relative "variants/marble"
|
|
9
|
+
require_relative "variants/pixel"
|
|
10
|
+
require_relative "variants/ring"
|
|
11
|
+
require_relative "variants/sunset"
|
|
12
|
+
|
|
13
|
+
module BoringAvatars
|
|
14
|
+
module Renderer
|
|
15
|
+
VARIANTS = {
|
|
16
|
+
bauhaus: Variants::Bauhaus,
|
|
17
|
+
beam: Variants::Beam,
|
|
18
|
+
marble: Variants::Marble,
|
|
19
|
+
pixel: Variants::Pixel,
|
|
20
|
+
ring: Variants::Ring,
|
|
21
|
+
sunset: Variants::Sunset
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def call(input)
|
|
27
|
+
prefix = Identifier.call(input)
|
|
28
|
+
ids = {
|
|
29
|
+
mask: "#{prefix}-mask",
|
|
30
|
+
filter: "#{prefix}-filter",
|
|
31
|
+
gradient_0: "#{prefix}-gradient-0",
|
|
32
|
+
gradient_1: "#{prefix}-gradient-1"
|
|
33
|
+
}.freeze
|
|
34
|
+
hash = NameHash.call(input.name)
|
|
35
|
+
result = VARIANTS.fetch(input.variant).render(input: input, hash: hash, ids: ids)
|
|
36
|
+
|
|
37
|
+
Svg::Serializer.call(root_element(input, result, ids))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def root_element(input, result, ids)
|
|
41
|
+
root_attributes = {
|
|
42
|
+
"viewBox" => "0 0 #{result.size} #{result.size}",
|
|
43
|
+
"fill" => "none",
|
|
44
|
+
"role" => "img",
|
|
45
|
+
"xmlns" => "http://www.w3.org/2000/svg",
|
|
46
|
+
"width" => input.size,
|
|
47
|
+
"height" => input.size
|
|
48
|
+
}.merge(input.attributes)
|
|
49
|
+
|
|
50
|
+
children = []
|
|
51
|
+
children << Svg::Element.new(name: "title", children: [input.name]) if input.title
|
|
52
|
+
children << mask_element(input, result, ids[:mask])
|
|
53
|
+
children << Svg::Element.new(
|
|
54
|
+
name: "g",
|
|
55
|
+
attributes: { "mask" => "url(##{ids[:mask]})" },
|
|
56
|
+
children: result.content
|
|
57
|
+
)
|
|
58
|
+
unless result.defs.empty?
|
|
59
|
+
children << Svg::Element.new(name: "defs", children: result.defs)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Svg::Element.new(name: "svg", attributes: root_attributes, children: children)
|
|
63
|
+
end
|
|
64
|
+
private_class_method :root_element
|
|
65
|
+
|
|
66
|
+
def mask_element(input, result, mask_id)
|
|
67
|
+
attributes = {
|
|
68
|
+
"id" => mask_id,
|
|
69
|
+
"maskUnits" => "userSpaceOnUse",
|
|
70
|
+
"x" => 0,
|
|
71
|
+
"y" => 0,
|
|
72
|
+
"width" => result.size,
|
|
73
|
+
"height" => result.size
|
|
74
|
+
}.merge(result.mask_attributes)
|
|
75
|
+
rectangle = Svg::Element.new(
|
|
76
|
+
name: "rect",
|
|
77
|
+
attributes: {
|
|
78
|
+
"width" => result.size,
|
|
79
|
+
"height" => result.size,
|
|
80
|
+
"rx" => input.square ? nil : result.size * 2,
|
|
81
|
+
"fill" => "#FFFFFF"
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
Svg::Element.new(name: "mask", attributes: attributes, children: [rectangle])
|
|
86
|
+
end
|
|
87
|
+
private_class_method :mask_element
|
|
88
|
+
end
|
|
89
|
+
private_constant :Renderer
|
|
90
|
+
end
|
|
91
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoringAvatars
|
|
4
|
+
module Svg
|
|
5
|
+
Element = Data.define(:name, :attributes, :children) do
|
|
6
|
+
def initialize(name:, attributes: {}, children: [])
|
|
7
|
+
super(
|
|
8
|
+
name: name.to_s.freeze,
|
|
9
|
+
attributes: attributes.reject { |_key, value| value.nil? }.freeze,
|
|
10
|
+
children: children.freeze
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "element"
|
|
4
|
+
require_relative "value"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Svg
|
|
8
|
+
module Serializer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def call(element)
|
|
12
|
+
serialize_element(element).force_encoding(Encoding::UTF_8)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid_xml_string?(value)
|
|
16
|
+
value.valid_encoding? && value.codepoints.all? do |codepoint|
|
|
17
|
+
codepoint == 0x09 || codepoint == 0x0A || codepoint == 0x0D ||
|
|
18
|
+
codepoint.between?(0x20, 0xD7FF) ||
|
|
19
|
+
codepoint.between?(0xE000, 0xFFFD) ||
|
|
20
|
+
codepoint.between?(0x10000, 0x10FFFF)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def escape(value)
|
|
25
|
+
string = value.to_s
|
|
26
|
+
raise ArgumentError, "value contains characters forbidden by XML 1.0" unless valid_xml_string?(string)
|
|
27
|
+
|
|
28
|
+
string
|
|
29
|
+
.gsub("&", "&")
|
|
30
|
+
.gsub("<", "<")
|
|
31
|
+
.gsub(">", ">")
|
|
32
|
+
.gsub('"', """)
|
|
33
|
+
.gsub("'", "'")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def serialize_element(element)
|
|
37
|
+
attributes = element.attributes.map do |name, value|
|
|
38
|
+
%(#{name}="#{escape(attribute_value(value))}")
|
|
39
|
+
end
|
|
40
|
+
opening = attributes.empty? ? "<#{element.name}" : "<#{element.name} #{attributes.join(' ')}"
|
|
41
|
+
|
|
42
|
+
return "#{opening}/>" if element.children.empty?
|
|
43
|
+
|
|
44
|
+
content = element.children.map do |child|
|
|
45
|
+
child.is_a?(Element) ? serialize_element(child) : escape(child)
|
|
46
|
+
end.join
|
|
47
|
+
"#{opening}>#{content}</#{element.name}>"
|
|
48
|
+
end
|
|
49
|
+
private_class_method :serialize_element
|
|
50
|
+
|
|
51
|
+
def attribute_value(value)
|
|
52
|
+
case value
|
|
53
|
+
when String
|
|
54
|
+
value
|
|
55
|
+
when Numeric
|
|
56
|
+
Value.number(value)
|
|
57
|
+
when true, false
|
|
58
|
+
value.to_s
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "unsupported SVG attribute value: #{value.class}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
private_class_method :attribute_value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoringAvatars
|
|
4
|
+
module Svg
|
|
5
|
+
module Value
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def number(value)
|
|
9
|
+
return "0" if value.zero?
|
|
10
|
+
return value.to_i.to_s if value.is_a?(Float) && value.finite? && value == value.to_i
|
|
11
|
+
|
|
12
|
+
value.to_s
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoringAvatars
|
|
4
|
+
module Utilities
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def digit(number, position)
|
|
8
|
+
(number / (10**position)).floor % 10
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def boolean(number, position)
|
|
12
|
+
digit(number, position).even?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def unit(number, range, position = nil)
|
|
16
|
+
value = number % range
|
|
17
|
+
position && !position.zero? && digit(number, position).even? ? -value : value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def random_color(number, colors)
|
|
21
|
+
colors[number % colors.length]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def contrast(color)
|
|
25
|
+
red = color[1, 2].to_i(16)
|
|
26
|
+
green = color[3, 2].to_i(16)
|
|
27
|
+
blue = color[5, 2].to_i(16)
|
|
28
|
+
yiq = ((red * 299) + (green * 587) + (blue * 114)) / 1000.0
|
|
29
|
+
|
|
30
|
+
yiq >= 128 ? "#000000" : "#FFFFFF"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utilities"
|
|
4
|
+
require_relative "../variants"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Variants
|
|
8
|
+
module Bauhaus
|
|
9
|
+
extend VariantHelpers
|
|
10
|
+
|
|
11
|
+
SIZE = 80
|
|
12
|
+
ELEMENTS = 4
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def render(input:, hash:, ids:)
|
|
17
|
+
properties = Array.new(ELEMENTS) do |index|
|
|
18
|
+
multiplier = index + 1
|
|
19
|
+
{
|
|
20
|
+
color: Utilities.random_color(hash + index, input.colors),
|
|
21
|
+
translate_x: Utilities.unit(hash * multiplier, (SIZE / 2) - (index + 17), 1),
|
|
22
|
+
translate_y: Utilities.unit(hash * multiplier, (SIZE / 2) - (index + 17), 2),
|
|
23
|
+
rotate: Utilities.unit(hash * multiplier, 360),
|
|
24
|
+
square: Utilities.boolean(hash, 2)
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
content = [
|
|
29
|
+
element("rect", { "width" => SIZE, "height" => SIZE, "fill" => properties[0][:color] }),
|
|
30
|
+
element(
|
|
31
|
+
"rect",
|
|
32
|
+
{
|
|
33
|
+
"x" => (SIZE - 60) / 2,
|
|
34
|
+
"y" => (SIZE - 20) / 2,
|
|
35
|
+
"width" => SIZE,
|
|
36
|
+
"height" => properties[1][:square] ? SIZE : SIZE / 8,
|
|
37
|
+
"fill" => properties[1][:color],
|
|
38
|
+
"transform" => transform(properties[1])
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
element(
|
|
42
|
+
"circle",
|
|
43
|
+
{
|
|
44
|
+
"cx" => SIZE / 2,
|
|
45
|
+
"cy" => SIZE / 2,
|
|
46
|
+
"fill" => properties[2][:color],
|
|
47
|
+
"r" => SIZE / 5,
|
|
48
|
+
"transform" => "translate(#{number(properties[2][:translate_x])} #{number(properties[2][:translate_y])})"
|
|
49
|
+
}
|
|
50
|
+
),
|
|
51
|
+
element(
|
|
52
|
+
"line",
|
|
53
|
+
{
|
|
54
|
+
"x1" => 0,
|
|
55
|
+
"y1" => SIZE / 2,
|
|
56
|
+
"x2" => SIZE,
|
|
57
|
+
"y2" => SIZE / 2,
|
|
58
|
+
"stroke-width" => 2,
|
|
59
|
+
"stroke" => properties[3][:color],
|
|
60
|
+
"transform" => transform(properties[3])
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
VariantResult.new(size: SIZE, content: content)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def transform(property)
|
|
69
|
+
"translate(#{number(property[:translate_x])} #{number(property[:translate_y])}) " \
|
|
70
|
+
"rotate(#{number(property[:rotate])} #{SIZE / 2} #{SIZE / 2})"
|
|
71
|
+
end
|
|
72
|
+
private_class_method :transform
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utilities"
|
|
4
|
+
require_relative "../variants"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Variants
|
|
8
|
+
module Beam
|
|
9
|
+
extend VariantHelpers
|
|
10
|
+
|
|
11
|
+
SIZE = 36
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def render(input:, hash:, ids:)
|
|
16
|
+
data = generate_data(hash, input.colors)
|
|
17
|
+
content = [
|
|
18
|
+
element("rect", { "width" => SIZE, "height" => SIZE, "fill" => data[:background_color] }),
|
|
19
|
+
element(
|
|
20
|
+
"rect",
|
|
21
|
+
{
|
|
22
|
+
"x" => 0,
|
|
23
|
+
"y" => 0,
|
|
24
|
+
"width" => SIZE,
|
|
25
|
+
"height" => SIZE,
|
|
26
|
+
"transform" => wrapper_transform(data),
|
|
27
|
+
"fill" => data[:wrapper_color],
|
|
28
|
+
"rx" => data[:is_circle] ? SIZE : SIZE / 6
|
|
29
|
+
}
|
|
30
|
+
),
|
|
31
|
+
element("g", { "transform" => face_transform(data) }, face_elements(data))
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
VariantResult.new(size: SIZE, content: content)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def generate_data(hash, colors)
|
|
38
|
+
wrapper_color = Utilities.random_color(hash, colors)
|
|
39
|
+
pre_translate_x = Utilities.unit(hash, 10, 1)
|
|
40
|
+
wrapper_translate_x = pre_translate_x < 5 ? pre_translate_x + (SIZE / 9) : pre_translate_x
|
|
41
|
+
pre_translate_y = Utilities.unit(hash, 10, 2)
|
|
42
|
+
wrapper_translate_y = pre_translate_y < 5 ? pre_translate_y + (SIZE / 9) : pre_translate_y
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
wrapper_color: wrapper_color,
|
|
46
|
+
face_color: Utilities.contrast(wrapper_color),
|
|
47
|
+
background_color: Utilities.random_color(hash + 13, colors),
|
|
48
|
+
wrapper_translate_x: wrapper_translate_x,
|
|
49
|
+
wrapper_translate_y: wrapper_translate_y,
|
|
50
|
+
wrapper_rotate: Utilities.unit(hash, 360),
|
|
51
|
+
wrapper_scale: 1 + (Utilities.unit(hash, SIZE / 12) / 10.0),
|
|
52
|
+
is_mouth_open: Utilities.boolean(hash, 2),
|
|
53
|
+
is_circle: Utilities.boolean(hash, 1),
|
|
54
|
+
eye_spread: Utilities.unit(hash, 5),
|
|
55
|
+
mouth_spread: Utilities.unit(hash, 3),
|
|
56
|
+
face_rotate: Utilities.unit(hash, 10, 3),
|
|
57
|
+
face_translate_x: wrapper_translate_x > SIZE / 6 ? wrapper_translate_x / 2.0 : Utilities.unit(hash, 8, 1),
|
|
58
|
+
face_translate_y: wrapper_translate_y > SIZE / 6 ? wrapper_translate_y / 2.0 : Utilities.unit(hash, 7, 2)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
private_class_method :generate_data
|
|
62
|
+
|
|
63
|
+
def wrapper_transform(data)
|
|
64
|
+
"translate(#{number(data[:wrapper_translate_x])} #{number(data[:wrapper_translate_y])}) " \
|
|
65
|
+
"rotate(#{number(data[:wrapper_rotate])} #{SIZE / 2} #{SIZE / 2}) " \
|
|
66
|
+
"scale(#{number(data[:wrapper_scale])})"
|
|
67
|
+
end
|
|
68
|
+
private_class_method :wrapper_transform
|
|
69
|
+
|
|
70
|
+
def face_transform(data)
|
|
71
|
+
"translate(#{number(data[:face_translate_x])} #{number(data[:face_translate_y])}) " \
|
|
72
|
+
"rotate(#{number(data[:face_rotate])} #{SIZE / 2} #{SIZE / 2})"
|
|
73
|
+
end
|
|
74
|
+
private_class_method :face_transform
|
|
75
|
+
|
|
76
|
+
def face_elements(data)
|
|
77
|
+
mouth_y = 19 + data[:mouth_spread]
|
|
78
|
+
mouth = if data[:is_mouth_open]
|
|
79
|
+
element(
|
|
80
|
+
"path",
|
|
81
|
+
{
|
|
82
|
+
"d" => "M15 #{number(mouth_y)}c2 1 4 1 6 0",
|
|
83
|
+
"stroke" => data[:face_color],
|
|
84
|
+
"fill" => "none",
|
|
85
|
+
"stroke-linecap" => "round"
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
else
|
|
89
|
+
element(
|
|
90
|
+
"path",
|
|
91
|
+
{
|
|
92
|
+
"d" => "M13,#{number(mouth_y)} a1,0.75 0 0,0 10,0",
|
|
93
|
+
"fill" => data[:face_color]
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
[
|
|
99
|
+
mouth,
|
|
100
|
+
element(
|
|
101
|
+
"rect",
|
|
102
|
+
{
|
|
103
|
+
"x" => 14 - data[:eye_spread],
|
|
104
|
+
"y" => 14,
|
|
105
|
+
"width" => 1.5,
|
|
106
|
+
"height" => 2,
|
|
107
|
+
"rx" => 1,
|
|
108
|
+
"stroke" => "none",
|
|
109
|
+
"fill" => data[:face_color]
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
element(
|
|
113
|
+
"rect",
|
|
114
|
+
{
|
|
115
|
+
"x" => 20 + data[:eye_spread],
|
|
116
|
+
"y" => 14,
|
|
117
|
+
"width" => 1.5,
|
|
118
|
+
"height" => 2,
|
|
119
|
+
"rx" => 1,
|
|
120
|
+
"stroke" => "none",
|
|
121
|
+
"fill" => data[:face_color]
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
end
|
|
126
|
+
private_class_method :face_elements
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utilities"
|
|
4
|
+
require_relative "../variants"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Variants
|
|
8
|
+
module Marble
|
|
9
|
+
extend VariantHelpers
|
|
10
|
+
|
|
11
|
+
SIZE = 80
|
|
12
|
+
ELEMENTS = 3
|
|
13
|
+
FIRST_PATH = "M32.414 59.35L50.376 70.5H72.5v-71H33.728L26.5 13.381l19.057 27.08L32.414 59.35z"
|
|
14
|
+
SECOND_PATH = "M22.216 24L0 46.75l14.108 38.129L78 86l-3.081-59.276-22.378 4.005 12.972 20.186-23.35 27.395L22.215 24z"
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def render(input:, hash:, ids:)
|
|
19
|
+
properties = Array.new(ELEMENTS) do |index|
|
|
20
|
+
multiplier = index + 1
|
|
21
|
+
{
|
|
22
|
+
color: Utilities.random_color(hash + index, input.colors),
|
|
23
|
+
translate_x: Utilities.unit(hash * multiplier, SIZE / 10, 1),
|
|
24
|
+
translate_y: Utilities.unit(hash * multiplier, SIZE / 10, 2),
|
|
25
|
+
scale: 1.2 + (Utilities.unit(hash * multiplier, SIZE / 20) / 10.0),
|
|
26
|
+
rotate: Utilities.unit(hash * multiplier, 360, 1)
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
content = [
|
|
31
|
+
element("rect", { "width" => SIZE, "height" => SIZE, "fill" => properties[0][:color] }),
|
|
32
|
+
element(
|
|
33
|
+
"path",
|
|
34
|
+
{
|
|
35
|
+
"filter" => "url(##{ids[:filter]})",
|
|
36
|
+
"d" => FIRST_PATH,
|
|
37
|
+
"fill" => properties[1][:color],
|
|
38
|
+
"transform" => transform(properties[1], properties[2][:scale])
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
element(
|
|
42
|
+
"path",
|
|
43
|
+
{
|
|
44
|
+
"filter" => "url(##{ids[:filter]})",
|
|
45
|
+
"style" => "mix-blend-mode:overlay",
|
|
46
|
+
"d" => SECOND_PATH,
|
|
47
|
+
"fill" => properties[2][:color],
|
|
48
|
+
"transform" => transform(properties[2], properties[2][:scale])
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
filter = element(
|
|
54
|
+
"filter",
|
|
55
|
+
{
|
|
56
|
+
"id" => ids[:filter],
|
|
57
|
+
"filterUnits" => "userSpaceOnUse",
|
|
58
|
+
"color-interpolation-filters" => "sRGB"
|
|
59
|
+
},
|
|
60
|
+
[
|
|
61
|
+
element("feFlood", { "flood-opacity" => 0, "result" => "BackgroundImageFix" }),
|
|
62
|
+
element(
|
|
63
|
+
"feBlend",
|
|
64
|
+
{ "in" => "SourceGraphic", "in2" => "BackgroundImageFix", "result" => "shape" }
|
|
65
|
+
),
|
|
66
|
+
element(
|
|
67
|
+
"feGaussianBlur",
|
|
68
|
+
{ "stdDeviation" => 7, "result" => "effect1_foregroundBlur" }
|
|
69
|
+
)
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
VariantResult.new(size: SIZE, content: content, defs: [filter])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def transform(property, scale)
|
|
77
|
+
"translate(#{number(property[:translate_x])} #{number(property[:translate_y])}) " \
|
|
78
|
+
"rotate(#{number(property[:rotate])} #{SIZE / 2} #{SIZE / 2}) " \
|
|
79
|
+
"scale(#{number(scale)})"
|
|
80
|
+
end
|
|
81
|
+
private_class_method :transform
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utilities"
|
|
4
|
+
require_relative "../variants"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Variants
|
|
8
|
+
module Pixel
|
|
9
|
+
extend VariantHelpers
|
|
10
|
+
|
|
11
|
+
SIZE = 80
|
|
12
|
+
ELEMENTS = 64
|
|
13
|
+
TOP_ROW_X = [0, 20, 40, 60, 10, 30, 50, 70].freeze
|
|
14
|
+
COLUMN_X = [0, 20, 40, 60, 10, 30, 50, 70].freeze
|
|
15
|
+
Y_COORDINATES = (10..70).step(10).to_a.freeze
|
|
16
|
+
COORDINATES = (
|
|
17
|
+
TOP_ROW_X.map { |x| [x, 0] } +
|
|
18
|
+
COLUMN_X.flat_map { |x| Y_COORDINATES.map { |y| [x, y] } }
|
|
19
|
+
).freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def render(input:, hash:, ids:)
|
|
24
|
+
colors = Array.new(ELEMENTS) do |index|
|
|
25
|
+
Utilities.random_color(hash % (index + 1), input.colors)
|
|
26
|
+
end
|
|
27
|
+
content = COORDINATES.each_with_index.map do |(x, y), index|
|
|
28
|
+
element(
|
|
29
|
+
"rect",
|
|
30
|
+
{
|
|
31
|
+
"x" => x.zero? ? nil : x,
|
|
32
|
+
"y" => y.zero? ? nil : y,
|
|
33
|
+
"width" => 10,
|
|
34
|
+
"height" => 10,
|
|
35
|
+
"fill" => colors[index]
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
VariantResult.new(
|
|
41
|
+
size: SIZE,
|
|
42
|
+
content: content,
|
|
43
|
+
mask_attributes: { "mask-type" => "alpha" }
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utilities"
|
|
4
|
+
require_relative "../variants"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Variants
|
|
8
|
+
module Ring
|
|
9
|
+
extend VariantHelpers
|
|
10
|
+
|
|
11
|
+
SIZE = 90
|
|
12
|
+
COLORS = 5
|
|
13
|
+
PATHS = [
|
|
14
|
+
"M0 0h90v45H0z",
|
|
15
|
+
"M0 45h90v45H0z",
|
|
16
|
+
"M83 45a38 38 0 00-76 0h76z",
|
|
17
|
+
"M83 45a38 38 0 01-76 0h76z",
|
|
18
|
+
"M77 45a32 32 0 10-64 0h64z",
|
|
19
|
+
"M77 45a32 32 0 11-64 0h64z",
|
|
20
|
+
"M71 45a26 26 0 00-52 0h52z",
|
|
21
|
+
"M71 45a26 26 0 01-52 0h52z"
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def render(input:, hash:, ids:)
|
|
27
|
+
shuffled = Array.new(COLORS) { |index| Utilities.random_color(hash + index, input.colors) }
|
|
28
|
+
colors = [
|
|
29
|
+
shuffled[0], shuffled[1], shuffled[1], shuffled[2], shuffled[2],
|
|
30
|
+
shuffled[3], shuffled[3], shuffled[0], shuffled[4]
|
|
31
|
+
]
|
|
32
|
+
content = PATHS.each_with_index.map do |path, index|
|
|
33
|
+
element("path", { "d" => path, "fill" => colors[index] })
|
|
34
|
+
end
|
|
35
|
+
content << element("circle", { "cx" => 45, "cy" => 45, "r" => 23, "fill" => colors[8] })
|
|
36
|
+
|
|
37
|
+
VariantResult.new(size: SIZE, content: content)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utilities"
|
|
4
|
+
require_relative "../variants"
|
|
5
|
+
|
|
6
|
+
module BoringAvatars
|
|
7
|
+
module Variants
|
|
8
|
+
module Sunset
|
|
9
|
+
extend VariantHelpers
|
|
10
|
+
|
|
11
|
+
SIZE = 80
|
|
12
|
+
ELEMENTS = 4
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def render(input:, hash:, ids:)
|
|
17
|
+
colors = Array.new(ELEMENTS) { |index| Utilities.random_color(hash + index, input.colors) }
|
|
18
|
+
content = [
|
|
19
|
+
element(
|
|
20
|
+
"path",
|
|
21
|
+
{ "fill" => "url(##{ids[:gradient_0]})", "d" => "M0 0h80v40H0z" }
|
|
22
|
+
),
|
|
23
|
+
element(
|
|
24
|
+
"path",
|
|
25
|
+
{ "fill" => "url(##{ids[:gradient_1]})", "d" => "M0 40h80v40H0z" }
|
|
26
|
+
)
|
|
27
|
+
]
|
|
28
|
+
defs = [
|
|
29
|
+
gradient(ids[:gradient_0], 0, SIZE / 2, colors[0], colors[1]),
|
|
30
|
+
gradient(ids[:gradient_1], SIZE / 2, SIZE, colors[2], colors[3])
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
VariantResult.new(size: SIZE, content: content, defs: defs)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def gradient(id, y1, y2, start_color, end_color)
|
|
37
|
+
element(
|
|
38
|
+
"linearGradient",
|
|
39
|
+
{
|
|
40
|
+
"id" => id,
|
|
41
|
+
"x1" => SIZE / 2,
|
|
42
|
+
"y1" => y1,
|
|
43
|
+
"x2" => SIZE / 2,
|
|
44
|
+
"y2" => y2,
|
|
45
|
+
"gradientUnits" => "userSpaceOnUse"
|
|
46
|
+
},
|
|
47
|
+
[
|
|
48
|
+
element("stop", { "stop-color" => start_color }),
|
|
49
|
+
element("stop", { "offset" => 1, "stop-color" => end_color })
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
private_class_method :gradient
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|