diff --git a/lib/game_icons/icon.rb b/lib/game_icons/icon.rb
index a0b0688..775a446 100644
--- a/lib/game_icons/icon.rb
+++ b/lib/game_icons/icon.rb
@@ -14,14 +14,184 @@ def string
# Modify the background and foreground colors and their opacities
def recolor(bg: '#000', fg: '#fff', bg_opacity: "1.0", fg_opacity: "1.0")
+ modify(bg: bg, fg: fg, bg_opacity: bg_opacity.to_f * 100, fg_opacity: fg_opacity.to_f * 100)
+ self
+ end
+
+ ## Modify the icon according to your wishes. Lots of options available.
+ # bg: background color
+ # bg_opacity: background color opacity, a number between 0 and 100
+ # bg_gradient: false, 'o' ('radial'), '-' ('horizontal'), '|' ('vertical'),
+ # '\' ('diagonal_down'), or '/' ('diagonal_up')
+ # bg2: second background color (when bg_gradient is not false)
+ # bg2_opacity: opacity of second background color (when bg_gradient is not false),
+ # a number between 0 and 100
+ # shape: 'circle', 'triangle', 'square', 'square-alt', 'rounded-square', 'star5', 'star5-alt',
+ # 'star6', 'star6-alt', 'star7', 'start7-alt', 'hexa', 'hexa-alt', 'octa' or 'octa-alt'
+ # cr: corner radius for the shape 'rounded-square', a number between 0 and 256,
+ # where 0 makes a square and 256 makes a circle
+ # frame: true or false
+ # f: frame color
+ # f_opacity: frame opacity, a number between 0 and 100
+ # f_width: frame width, where 1 equals 1/512th of the width of the icon
+ # fg: foreground color
+ # fg_opacity: foreground color opacity, a number between 0 and 100
+ # fg_gradient: false, 'o' ('radial'), '-' ('horizontal'), '|' ('vertical'),
+ # '\' ('diagonal_down'), or '/' ('diagonal_up')
+ # fg2: second foreground color (when fg_gradient is not false)
+ # fg2_opacity: opacity of second foreground color (when fg_gradient is not false),
+ # a number between 0 and 100
+ # shadow: shadow/glow effect, true or false
+ # sh: shadow color
+ # sh_opacity: shadow color opacity, a number between 0 and 100
+ # sh_blur: the size of the shadow blur, where 1 equals 1/512th of the width of the icon
+ # sh_x: horizontal offset for the shadow, where 1 equals 1/512th of the width of the icon
+ # sh_y: vertical offset for the shadow, where 1 equals 1/512th of the width of the icon
+ # sh_side: 'in' or 'out'
+ # stroke: true or false
+ # s: stroke color
+ # s_opacity: stroke opacity, a number between 0 and 100
+ # s_width: stroke width, where 1 equals 1/512th of the width of the icon
+ # x: horizontal foreground offset, where 1 equals 1/512th of the width of the icon
+ # y: vertical foreground offset, where 1 equals 1/512th of the width of the icon
+ # clip: keep the foreground inside the bounds of the background, true or false
+ # flip_h: flip horizontally, true or false
+ # flip_v: flip vertically, true or false
+ # rotate: rotation of the foreground in degrees, use positive number for clockwise rotation
+ # or negative number for anti-clockwise rotation
+ # scale: scale of foreground, a number between 0 and 100,
+ # use this to shrink the icon without shrinking the background
+ # preset: use an in-built color preset
+ # possible values: false, 'original', 'negatif', 'transparent', 'fire', 'ice',
+ # 'forest', 'silver', 'gold', 'terminal', 'candy' or 'gordon'
+ # if not false, any given arguments for colors, gradients, frame and stroke will be ignored
+ def modify(bg: '#000', bg_opacity: 100, bg_gradient: false, bg2: '#fff', bg2_opacity: 100, shape: 'square', cr: 32, frame: false, f: '#fff', f_opacity: 100, f_width: 8, fg: '#fff', fg_opacity: 100, fg_gradient: false, fg2: '#000', fg2_opacity: 100, shadow: false, sh: '#fff', sh_opacity: 100, sh_blur: 15, sh_x: 0, sh_y: 0, sh_side: 'out', stroke: false, s: '#ccc', s_opacity: 100, s_width: 8, x: 0, y: 0, clip: false, flip_h: false, flip_v: false, rotate: 0, scale: 100, preset: false)
OptionalDeps.require_nokogiri
- bg.prepend('#') unless bg.start_with? '#'
- fg.prepend('#') unless fg.start_with? '#'
- doc = Nokogiri::XML(self.string)
- doc.css('path')[0]['fill'] = bg # dark backdrop
- doc.css('path')[1]['fill'] = fg # light drawing
- doc.css('path')[0]['fill-opacity'] = bg_opacity.to_s # dark backdrop
- doc.css('path')[1]['fill-opacity'] = fg_opacity.to_s # light drawing
+ doc = Nokogiri::XML(File.open(@file) { |f| f.read })
+
+ # set color arguments according to the given preset
+ if preset
+ bg, fg, shadow, sh, sh_blur, sh_x, sh_y, sh_side, stroke, s, s_width =
+ PRESET_BG[preset], PRESET_FG[preset], PRESET_SHADOW[preset], PRESET_SH[preset], PRESET_SH_BLUR[preset], PRESET_SH_X[preset], PRESET_SH_Y[preset], PRESET_SH_SIDE[preset], PRESET_STROKE[preset], PRESET_S[preset], PRESET_S_WIDTH[preset]
+ bg_opacity = preset == 'transparent' ? 0 : 100
+ bg_gradient = false
+ frame = false
+ fg_opacity = 100
+ fg_gradient = false
+ sh_opacity = 100
+ s_opacity = 100
+ end
+
+ # we will need to add some definitions later
+ if bg_gradient || fg_gradient || shadow || clip
+ doc.root.first_element_child.before('')
+ defs = doc.at_css('defs')
+ end
+
+ # convert opacities and scale to floats in range [0..1]
+ bg_opacity, bg2_opacity, f_opacity, fg_opacity, fg2_opacity, sh_opacity, s_opacity, scale =
+ bg_opacity/100.0, bg2_opacity/100.0, f_opacity/100.0, fg_opacity/100.0, fg2_opacity/100.0, sh_opacity/100.0, s_opacity/100.0, scale/100.0
+
+ # convert all the colors into #rrggbb or rgba format
+ bg, bg2, f, fg, fg2, s, sh =
+ to_rgb(bg), to_rgb(bg2), to_rgb(f), to_rgb(fg), to_rgb(fg2), to_rgb(s), to_rgba(sh, sh_opacity)
+
+ # icon path
+ foreground = doc.css('path')[1]
+
+ # background shape
+ case shape
+ when 'square'
+ background = doc.at_css('path')
+ when 'circle'
+ doc.at_css('path').remove
+ foreground.before(SHAPE[shape])
+ background = doc.at_css('circle')
+ when 'rounded-square'
+ doc.at_css('path').remove
+ foreground.before(SHAPE[shape])
+ background = doc.at_css('rect')
+ background['rx'] = cr
+ background['ry'] = cr
+ else
+ doc.at_css('path').remove
+ foreground.before(SHAPE[shape])
+ background = doc.at_css('polygon')
+ end
+
+ # background colors
+ if bg_gradient
+ gradient = defs.add_child(GRADIENT[bg_gradient])[0]
+ gradient['id'] = 'gradient-bg'
+ gradient.add_child("")
+ background['fill'] = 'url(#gradient-bg)'
+ else
+ background['fill'] = bg
+ background['fill-opacity'] = bg_opacity.to_s unless bg_opacity >= 1
+ end
+
+ # background frame (TODO: background should be shrunk in order to fit the frame inside the icon)
+ if frame
+ background['stroke'] = f
+ background['stroke-opacity'] = f_opacity unless f_opacity >= 1
+ background['stroke-width'] = f_width
+ end
+
+ # foreground colors
+ if fg_gradient
+ gradient = defs.add_child(GRADIENT[fg_gradient])[0]
+ gradient['id'] = 'gradient-fg'
+ gradient.add_child("")
+ foreground['fill'] = 'url(#gradient-fg)'
+ else
+ foreground['fill'] = fg
+ foreground['fill-opacity'] = fg_opacity.to_s unless fg_opacity >= 1
+ end
+
+ # foreground stroke
+ if stroke
+ foreground['stroke'] = s
+ foreground['stroke-opacity'] = s_opacity unless s_opacity >= 1
+ foreground['stroke-width'] = s_width
+ end
+
+ # foreground shadow/glow effect
+ if shadow
+ filter = defs.add_child("")[0]
+ if sh_side == 'in'
+ filter.at_css('feComposite')['operator'] = 'out'
+ filter.add_child('')
+ else
+ filter.at_css('feComposite')['operator'] = 'atop'
+ filter.add_child('')
+ end
+ foreground['filter'] = 'url(#shadow)'
+ end
+
+ # clip foreground to background
+ if clip
+ clip_path = defs.add_child('' + background.to_xml + '')[0].first_element_child
+ foreground['clip-path'] = 'url(#icon-bg)'
+ end
+
+ # transform: translate, scale, rotate and flip
+ if x != 0 || y != 0 || scale != 1.0 || flip_h || flip_v || rotate != 0
+ x += 256 * (1 - scale)
+ y += 256 * (1 - scale)
+ x_f = flip_h ? x + 512 : x
+ y_f = flip_v ? y + 512 : y
+ x_c = flip_h ? x + 512 : -x
+ y_c = flip_v ? y + 512 : -y
+ scale_x = flip_h ? -scale : scale
+ scale_y = flip_v ? -scale : scale
+ scale_x_c = flip_h ? -2 - scale : 2 - scale
+ scale_y_c = flip_v ? -2 - scale : 2 - scale
+ foreground['transform'] = "translate(#{x_f}, #{y_f}) scale(#{scale_x}, #{scale_y}) rotate(#{rotate}, 256, 256)"
+ if clip # TODO: clip does not work correctly for all possible transformation combinations (but it already works better than in the studio on game-icons.net)
+ clip_path['transform'] = "translate(#{x_c}, #{y_c}) scale(#{scale_x_c}, #{scale_y_c}) rotate(#{-rotate}, 256, 256)"
+ end
+ end
+
@svgstr = doc.to_xml
self
end
@@ -31,11 +201,212 @@ def recolor(bg: '#000', fg: '#fff', bg_opacity: "1.0", fg_opacity: "1.0")
def correct_pathdata
10.times do # this is a bit of a hack b/c my regex isn't perfect
@svgstr = self.string
- .gsub(/(\d)\-/,'\1 -') # separate negatives
+ .gsub(/(\d)\-/,'\1 -') # separate negatives
.gsub(/(\.)(\d+)(\.)/,'\1\2 \3') # separate multi-decimals
end
self
end
+ private
+
+ # Convert the color to #rrggbb format
+ def to_rgb(color)
+ begin
+ Cairo::Color.parse(color).to_s[0..-3]
+ rescue ArgumentError
+ Cairo::Color.parse(color.prepend('#')).to_s[0..-3]
+ end
+ end
+
+ # Convert the color and opacity to rgba(red, green, blue, alpha) format
+ def to_rgba(color, opacity)
+ color = to_rgb(color)
+ 'rgba(' + color[1..2].to_i(16).to_s + ', ' + color[3..4].to_i(16).to_s + ', ' + color[5..6].to_i(16).to_s + ', ' + opacity.to_s + ')'
+ end
+
+ # Constants
+ PRESET_BG = {
+ 'original' => '#000',
+ 'negatif' => '#fff',
+ 'transparent' => '#fff',
+ 'fire' => '#F44242',
+ 'ice' => '#156DE2',
+ 'forest' => '#7C432F',
+ 'silver' => '#6B6B6B',
+ 'gold' => '#F8E71C',
+ 'terminal' => '#334033',
+ 'candy' => '#F8E0C9',
+ 'gordon' => '#3E320A'
+ }
+
+ PRESET_FG = {
+ 'original' => '#fff',
+ 'negatif' => '#000',
+ 'transparent' => '#000',
+ 'fire' => '#FDEB05',
+ 'ice' => '#5AE9FF',
+ 'forest' => '#2AD422',
+ 'silver' => '#E8E8E8',
+ 'gold' => '#E3AA00',
+ 'terminal' => '#0f0',
+ 'candy' => '#F31070',
+ 'gordon' => '#F5C823'
+ }
+
+ PRESET_SHADOW = {
+ 'original' => false,
+ 'negatif' => false,
+ 'transparent' => false,
+ 'fire' => true,
+ 'ice' => true,
+ 'forest' => false,
+ 'silver' => true,
+ 'gold' => true,
+ 'terminal' => true,
+ 'candy' => true,
+ 'gordon' => true
+ }
+
+ PRESET_SH = {
+ 'original' => '#fff',
+ 'negatif' => '#000',
+ 'transparent' => '#000',
+ 'fire' => '#FDEB05',
+ 'ice' => '#fff',
+ 'forest' => '#fff',
+ 'silver' => '#fff',
+ 'gold' => '#E3AA00',
+ 'terminal' => '#0f0',
+ 'candy' => '#D0021B',
+ 'gordon' => '#F3D771'
+ }
+
+ PRESET_SH_BLUR = {
+ 'original' => 15,
+ 'negatif' => 15,
+ 'transparent' => 15,
+ 'fire' => 20,
+ 'ice' => 15,
+ 'forest' => 15,
+ 'silver' => 0,
+ 'gold' => 15,
+ 'terminal' => 15,
+ 'candy' => 0,
+ 'gordon' => 10
+ }
+
+ PRESET_SH_X = {
+ 'original' => 0,
+ 'negatif' => 0,
+ 'transparent' => 0,
+ 'fire' => 0,
+ 'ice' => 15,
+ 'forest' => 0,
+ 'silver' => 0,
+ 'gold' => 0,
+ 'terminal' => 0,
+ 'candy' => 0,
+ 'gordon' => 0
+ }
+
+ PRESET_SH_Y = {
+ 'original' => 0,
+ 'negatif' => 0,
+ 'transparent' => 0,
+ 'fire' => 0,
+ 'ice' => 0,
+ 'forest' => 0,
+ 'silver' => 10,
+ 'gold' => 0,
+ 'terminal' => 0,
+ 'candy' => 10,
+ 'gordon' => 0
+ }
+
+ PRESET_SH_SIDE = {
+ 'original' => 'out',
+ 'negatif' => 'out',
+ 'transparent' => 'out',
+ 'fire' => 'out',
+ 'ice' => 'in',
+ 'forest' => 'out',
+ 'silver' => 'out',
+ 'gold' => 'out',
+ 'terminal' => 'out',
+ 'candy' => 'out',
+ 'gordon' => 'out'
+ }
+
+ PRESET_STROKE = {
+ 'original' => false,
+ 'negatif' => false,
+ 'transparent' => false,
+ 'fire' => false,
+ 'ice' => false,
+ 'forest' => false,
+ 'silver' => false,
+ 'gold' => false,
+ 'terminal' => false,
+ 'candy' => false,
+ 'gordon' => true
+ }
+
+ PRESET_S = {
+ 'original' => '#ccc',
+ 'negatif' => '#ccc',
+ 'transparent' => '#ccc',
+ 'fire' => '#ccc',
+ 'ice' => '#ccc',
+ 'forest' => '#ccc',
+ 'silver' => '#ccc',
+ 'gold' => '#ccc',
+ 'terminal' => '#ccc',
+ 'candy' => '#ccc',
+ 'gordon' => '#F3D771'
+ }
+
+ PRESET_S_WIDTH = {
+ 'original' => 8,
+ 'negatif' => 8,
+ 'transparent' => 8,
+ 'fire' => 8,
+ 'ice' => 8,
+ 'forest' => 8,
+ 'silver' => 8,
+ 'gold' => 8,
+ 'terminal' => 8,
+ 'candy' => 8,
+ 'gordon' => 2
+ }
+
+ SHAPE = {
+ 'circle' => '',
+ 'rounded-square' => '',
+ 'triangle' => '',
+ 'star5' => '',
+ 'star5-alt' => '',
+ 'star6' => '',
+ 'star6-alt' => '',
+ 'star7' => '',
+ 'star7-alt' => '',
+ 'hexa' => '',
+ 'hexa-alt' => '',
+ 'octa' => '',
+ 'octa-alt' => ''
+ }
+
+ GRADIENT = {
+ 'o' => '',
+ 'radial' => '',
+ '-' => '',
+ 'horizontal' => '',
+ '|' => '',
+ 'vertical' => '',
+ '\\' => '',
+ 'diagonal_down' => '',
+ '/' => '',
+ 'diagonal_up' => ''
+ }
+
end
end