psd 2.1.2 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/psd.rb +8 -6
  4. data/lib/psd/blend_mode.rb +46 -38
  5. data/lib/psd/channel_image.rb +9 -5
  6. data/lib/psd/descriptor.rb +39 -16
  7. data/lib/psd/header.rb +33 -32
  8. data/lib/psd/image_formats/rle.rb +4 -10
  9. data/lib/psd/image_modes/rgb.rb +4 -4
  10. data/lib/psd/layer.rb +1 -15
  11. data/lib/psd/layer/blend_modes.rb +12 -12
  12. data/lib/psd/layer/helpers.rb +8 -10
  13. data/lib/psd/layer/info.rb +9 -7
  14. data/lib/psd/layer/position_and_channels.rb +0 -4
  15. data/lib/psd/layer_info.rb +0 -4
  16. data/lib/psd/layer_info/blend_clipping_elements.rb +4 -2
  17. data/lib/psd/layer_info/blend_interior_elements.rb +4 -2
  18. data/lib/psd/layer_info/fill_opacity.rb +4 -2
  19. data/lib/psd/layer_info/layer_group.rb +4 -2
  20. data/lib/psd/layer_info/layer_id.rb +4 -2
  21. data/lib/psd/layer_info/layer_name_source.rb +4 -2
  22. data/lib/psd/layer_info/layer_section_divider.rb +4 -2
  23. data/lib/psd/layer_info/legacy_typetool.rb +5 -3
  24. data/lib/psd/layer_info/locked.rb +4 -2
  25. data/lib/psd/layer_info/metadata_setting.rb +4 -2
  26. data/lib/psd/layer_info/object_effects.rb +4 -2
  27. data/lib/psd/layer_info/pattern.rb +14 -0
  28. data/lib/psd/layer_info/placed_layer.rb +4 -2
  29. data/lib/psd/layer_info/reference_point.rb +4 -2
  30. data/lib/psd/layer_info/sheet_color.rb +18 -0
  31. data/lib/psd/layer_info/solid_color.rb +36 -0
  32. data/lib/psd/layer_info/typetool.rb +4 -2
  33. data/lib/psd/layer_info/unicode_name.rb +4 -2
  34. data/lib/psd/layer_info/vector_mask.rb +4 -2
  35. data/lib/psd/layer_info/vector_origination.rb +14 -0
  36. data/lib/psd/layer_info/vector_stroke.rb +4 -2
  37. data/lib/psd/layer_info/vector_stroke_content.rb +4 -2
  38. data/lib/psd/layer_mask.rb +2 -8
  39. data/lib/psd/lazy_execute.rb +5 -1
  40. data/lib/psd/node.rb +112 -48
  41. data/lib/psd/nodes/ancestry.rb +80 -75
  42. data/lib/psd/nodes/build_preview.rb +4 -4
  43. data/lib/psd/nodes/group.rb +35 -0
  44. data/lib/psd/nodes/layer.rb +40 -0
  45. data/lib/psd/nodes/root.rb +90 -0
  46. data/lib/psd/nodes/search.rb +19 -19
  47. data/lib/psd/path_record.rb +1 -71
  48. data/lib/psd/renderer.rb +6 -5
  49. data/lib/psd/renderer/blender.rb +10 -5
  50. data/lib/psd/renderer/cairo_helpers.rb +46 -0
  51. data/lib/psd/renderer/canvas.rb +39 -19
  52. data/lib/psd/renderer/canvas_management.rb +2 -2
  53. data/lib/psd/renderer/clipping_mask.rb +5 -4
  54. data/lib/psd/renderer/compose.rb +61 -68
  55. data/lib/psd/renderer/layer_styles.rb +15 -5
  56. data/lib/psd/renderer/layer_styles/color_overlay.rb +46 -27
  57. data/lib/psd/renderer/mask.rb +26 -22
  58. data/lib/psd/renderer/mask_canvas.rb +12 -0
  59. data/lib/psd/renderer/vector_shape.rb +239 -0
  60. data/lib/psd/resource_section.rb +4 -7
  61. data/lib/psd/resources.rb +4 -19
  62. data/lib/psd/resources/base.rb +27 -0
  63. data/lib/psd/resources/guides.rb +6 -4
  64. data/lib/psd/resources/layer_comps.rb +6 -4
  65. data/lib/psd/resources/slices.rb +7 -5
  66. data/lib/psd/version.rb +1 -1
  67. data/psd.gemspec +1 -2
  68. data/spec/files/blendmodes.psd +0 -0
  69. data/spec/hierarchy_spec.rb +5 -0
  70. metadata +27 -26
  71. data/lib/psd/layer_info/vector_mask_2.rb +0 -10
  72. data/lib/psd/node_exporting.rb +0 -20
  73. data/lib/psd/node_group.rb +0 -86
  74. data/lib/psd/node_layer.rb +0 -81
  75. data/lib/psd/node_root.rb +0 -93
  76. data/lib/psd/nodes/has_children.rb +0 -13
  77. data/lib/psd/nodes/lock_to_origin.rb +0 -7
  78. data/lib/psd/nodes/parse_layers.rb +0 -18
  79. data/lib/psd/renderer/layer_styles/drop_shadow.rb +0 -75
  80. data/lib/psd/section.rb +0 -26
@@ -0,0 +1,46 @@
1
+ class PSD
2
+ class Renderer
3
+ # Adapted from
4
+ # http://www.hokstad.com/simple-drawing-in-ruby-with-cairo
5
+ module CairoHelpers
6
+ def cairo_image_surface(w, h, bg=nil)
7
+ surface = Cairo::ImageSurface.new(w, h)
8
+ cr = Cairo::Context.new(surface)
9
+
10
+ if bg
11
+ cr.set_source_rgba(*bg)
12
+ cr.paint
13
+ end
14
+
15
+ yield cr
16
+
17
+ surface.finish
18
+
19
+ data = cr.target.data.to_s[0, 4 * w * h]
20
+
21
+ # Cairo data is stored as BGRA, ugh.
22
+ data = data.unpack("N*").map do |color|
23
+ color = ChunkyPNG::Color.to_truecolor_alpha_bytes(color)
24
+ ChunkyPNG::Color.rgba(color[2], color[1], color[0], color[3])
25
+ end
26
+
27
+ ChunkyPNG::Canvas.new(w, h, data)
28
+ end
29
+
30
+ def cairo_path(cr, *pairs)
31
+ first = true
32
+ pairs.each do |cmd|
33
+ if cmd == :c
34
+ cr.close_path
35
+ first = true
36
+ elsif first
37
+ cr.move_to(*cmd)
38
+ first = false
39
+ else
40
+ cr.curve_to(*cmd)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,34 +1,34 @@
1
1
  class PSD
2
2
  class Renderer
3
3
  class Canvas
4
- attr_reader :canvas, :node, :width, :height, :left, :right, :top, :bottom, :opacity, :fill_opacity
4
+ attr_reader :canvas, :node, :opts, :width, :height, :opacity, :fill_opacity
5
+
6
+ [:top, :right, :bottom, :left].each { |dir| delegate dir, to: :node }
5
7
 
6
- def initialize(node, width = nil, height = nil, color = ChunkyPNG::Color::TRANSPARENT)
8
+ delegate :[], :[]=, :get_pixel, :set_pixel, to: :canvas
9
+
10
+ def initialize(node, width = nil, height = nil, opts = {})
7
11
  @node = node
12
+ @opts = opts
8
13
  @pixel_data = @node.group? ? [] : @node.image.pixel_data
9
14
 
10
15
  @width = (width || @node.width).to_i
11
16
  @height = (height || @node.height).to_i
12
- @left = @node.left.to_i
13
- @right = @node.right.to_i
14
- @top = @node.top.to_i
15
- @bottom = @node.bottom.to_i
16
17
 
17
18
  @opacity = @node.opacity.to_f
18
19
  @fill_opacity = @node.fill_opacity.to_f
19
20
 
20
- @canvas = ChunkyPNG::Canvas.new(@width, @height, color)
21
-
22
- initialize_canvas unless @node.group?
21
+ initialize_canvas
23
22
  end
24
23
 
25
24
  def paint_to(base)
26
25
  PSD.logger.debug "Painting #{node.name} to #{base.node.debug_name}"
27
26
 
28
- apply_mask
27
+ apply_masks
29
28
  apply_clipping_mask
30
29
  apply_layer_styles
31
30
  apply_layer_opacity
31
+
32
32
  compose_pixels(base)
33
33
  end
34
34
 
@@ -38,9 +38,6 @@ class PSD
38
38
  @height = @canvas.height
39
39
  end
40
40
 
41
- def [](x, y); @canvas[x, y]; end
42
- def []=(x, y, value); @canvas[x, y] = value; end
43
-
44
41
  def method_missing(method, *args, &block)
45
42
  @canvas.send(method, *args, &block)
46
43
  end
@@ -48,20 +45,43 @@ class PSD
48
45
  private
49
46
 
50
47
  def initialize_canvas
51
- PSD.logger.debug "Initializing canvas for #{node.debug_name}"
48
+ PSD.logger.debug "Initializing canvas for #{node.debug_name}; color = #{ChunkyPNG::Color.to_truecolor_alpha_bytes(fill_color)}"
49
+
50
+ @canvas = ChunkyPNG::Canvas.new(@width, @height, fill_color)
51
+ return if @node.group? || has_fill? || @opts[:base]
52
52
 
53
53
  # Sorry, ChunkyPNG.
54
54
  @canvas.send(:replace_canvas!, width, height, @pixel_data)
55
-
55
+ ensure
56
56
  # This can now be referenced by @canvas.pixels
57
57
  @pixel_data = nil
58
58
  end
59
59
 
60
- def apply_mask
61
- return unless @node.image.has_mask?
60
+ def has_fill?
61
+ !@opts[:base] && @node.layer? && @node.solid_color
62
+ end
62
63
 
63
- PSD.logger.debug "Applying layer mask to #{node.name}"
64
- Mask.new(self).apply!
64
+ def fill_color
65
+ if has_fill?
66
+ @node.solid_color.color
67
+ else
68
+ ChunkyPNG::Color::TRANSPARENT
69
+ end
70
+ end
71
+
72
+ def apply_masks
73
+ ([@node] + @node.ancestors).each do |n|
74
+ next unless n.raster_mask?
75
+ break if n.group? && !n.passthru_blending?
76
+
77
+ if n.layer?
78
+ PSD.logger.debug "Applying raster mask to #{@node.name}"
79
+ Mask.new(self).apply!
80
+ else
81
+ PSD.logger.debug "Applying raster mask to #{@node.name} from #{n.name}"
82
+ Mask.new(self, n).apply!
83
+ end
84
+ end
65
85
  end
66
86
 
67
87
  def apply_clipping_mask
@@ -5,9 +5,9 @@ class PSD
5
5
  @canvas_stack.last
6
6
  end
7
7
 
8
- def create_group_canvas(node, width=@width, height=@height)
8
+ def create_group_canvas(node, width = @width, height = @height, opts = {})
9
9
  PSD.logger.debug "Group canvas created. Node = #{node.name || ":root:"}, width = #{width}, height = #{height}"
10
- push_canvas Canvas.new(node, width, height)
10
+ push_canvas Canvas.new(node, width, height, @opts.merge(opts))
11
11
  end
12
12
 
13
13
  def push_canvas(canvas)
@@ -7,8 +7,9 @@ class PSD
7
7
  @canvas = canvas
8
8
  @node = @canvas.node
9
9
 
10
- mask_node = @canvas.node.next_sibling
11
- @mask = Canvas.new(mask_node)
10
+ mask_node = @canvas.node.clipping_mask
11
+
12
+ @mask = MaskCanvas.new(mask_node)
12
13
  end
13
14
 
14
15
  def apply!
@@ -31,8 +32,8 @@ class PSD
31
32
  alpha = pixel.nil? ? 0 : ChunkyPNG::Color.a(pixel)
32
33
  end
33
34
 
34
- color = @canvas[x, y]
35
- @canvas[x, y] = (color & 0xffffff00) | (ChunkyPNG::Color.a(color) * alpha / 255)
35
+ color = @canvas.get_pixel(x, y)
36
+ @canvas.set_pixel x, y, (color & 0xffffff00) | (ChunkyPNG::Color.a(color) * alpha / 255)
36
37
  end
37
38
  end
38
39
  end
@@ -6,20 +6,15 @@ class PSD
6
6
  module Compose
7
7
  extend self
8
8
 
9
- DEFAULT_OPTS = {
10
- opacity: 255,
11
- fill_opacity: 255
12
- }
13
-
14
9
  #
15
10
  # Normal blend modes
16
11
  #
17
12
 
18
- def normal(fg, bg, opts={})
19
- return apply_opacity(fg, opts) if fully_transparent?(bg)
13
+ def normal(fg, bg, opacity)
14
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
20
15
  return bg if fully_transparent?(fg)
21
16
 
22
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
17
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
23
18
  new_r = blend_channel(r(bg), r(fg), mix_alpha)
24
19
  new_g = blend_channel(g(bg), g(fg), mix_alpha)
25
20
  new_b = blend_channel(b(bg), b(fg), mix_alpha)
@@ -32,11 +27,11 @@ class PSD
32
27
  # Subtractive blend modes
33
28
  #
34
29
 
35
- def darken(fg, bg, opts={})
36
- return apply_opacity(fg, opts) if fully_transparent?(bg)
30
+ def darken(fg, bg, opacity)
31
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
37
32
  return bg if fully_transparent?(fg)
38
33
 
39
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
34
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
40
35
  new_r = r(fg) <= r(bg) ? blend_channel(r(bg), r(fg), mix_alpha) : r(bg)
41
36
  new_g = g(fg) <= g(bg) ? blend_channel(g(bg), g(fg), mix_alpha) : g(bg)
42
37
  new_b = b(fg) <= b(bg) ? blend_channel(b(bg), b(fg), mix_alpha) : b(bg)
@@ -44,11 +39,11 @@ class PSD
44
39
  rgba(new_r, new_g, new_b, dst_alpha)
45
40
  end
46
41
 
47
- def multiply(fg, bg, opts={})
48
- return apply_opacity(fg, opts) if fully_transparent?(bg)
42
+ def multiply(fg, bg, opacity)
43
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
49
44
  return bg if fully_transparent?(fg)
50
45
 
51
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
46
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
52
47
  new_r = blend_channel(r(bg), r(fg) * r(bg) >> 8, mix_alpha)
53
48
  new_g = blend_channel(g(bg), g(fg) * g(bg) >> 8, mix_alpha)
54
49
  new_b = blend_channel(b(bg), b(fg) * b(bg) >> 8, mix_alpha)
@@ -56,11 +51,11 @@ class PSD
56
51
  rgba(new_r, new_g, new_b, dst_alpha)
57
52
  end
58
53
 
59
- def color_burn(fg, bg, opts={})
60
- return apply_opacity(fg, opts) if fully_transparent?(bg)
54
+ def color_burn(fg, bg, opacity)
55
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
61
56
  return bg if fully_transparent?(fg)
62
57
 
63
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
58
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
64
59
 
65
60
  calculate_foreground = Proc.new do |b, f|
66
61
  if f > 0
@@ -78,11 +73,11 @@ class PSD
78
73
  rgba(new_r, new_g, new_b, dst_alpha)
79
74
  end
80
75
 
81
- def linear_burn(fg, bg, opts={})
82
- return apply_opacity(fg, opts) if fully_transparent?(bg)
76
+ def linear_burn(fg, bg, opacity)
77
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
83
78
  return bg if fully_transparent?(fg)
84
79
 
85
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
80
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
86
81
 
87
82
  new_r = blend_channel(r(bg), (r(fg) < (255 - r(bg))) ? 0 : r(fg) - (255 - r(bg)), mix_alpha)
88
83
  new_g = blend_channel(g(bg), (g(fg) < (255 - g(bg))) ? 0 : g(fg) - (255 - g(bg)), mix_alpha)
@@ -95,11 +90,11 @@ class PSD
95
90
  # Additive blend modes
96
91
  #
97
92
 
98
- def lighten(fg, bg, opts={})
99
- return apply_opacity(fg, opts) if fully_transparent?(bg)
93
+ def lighten(fg, bg, opacity)
94
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
100
95
  return bg if fully_transparent?(fg)
101
96
 
102
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
97
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
103
98
 
104
99
  new_r = r(fg) >= r(bg) ? blend_channel(r(bg), r(fg), mix_alpha) : r(bg)
105
100
  new_g = g(fg) >= g(bg) ? blend_channel(g(bg), g(fg), mix_alpha) : g(bg)
@@ -108,11 +103,11 @@ class PSD
108
103
  rgba(new_r, new_g, new_b, dst_alpha)
109
104
  end
110
105
 
111
- def screen(fg, bg, opts={})
112
- return apply_opacity(fg, opts) if fully_transparent?(bg)
106
+ def screen(fg, bg, opacity)
107
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
113
108
  return bg if fully_transparent?(fg)
114
109
 
115
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
110
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
116
111
 
117
112
  new_r = blend_channel(r(bg), 255 - ((255 - r(bg)) * (255 - r(fg)) >> 8), mix_alpha)
118
113
  new_g = blend_channel(g(bg), 255 - ((255 - g(bg)) * (255 - g(fg)) >> 8), mix_alpha)
@@ -121,11 +116,11 @@ class PSD
121
116
  rgba(new_r, new_g, new_b, dst_alpha)
122
117
  end
123
118
 
124
- def color_dodge(fg, bg, opts={})
125
- return apply_opacity(fg, opts) if fully_transparent?(bg)
119
+ def color_dodge(fg, bg, opacity)
120
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
126
121
  return bg if fully_transparent?(fg)
127
122
 
128
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
123
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
129
124
 
130
125
  calculate_foreground = Proc.new do |b, f|
131
126
  f < 255 ? [(b << 8) / (255 - f), 255].min : b
@@ -138,11 +133,11 @@ class PSD
138
133
  rgba(new_r, new_g, new_b, dst_alpha)
139
134
  end
140
135
 
141
- def linear_dodge(fg, bg, opts={})
142
- return apply_opacity(fg, opts) if fully_transparent?(bg)
136
+ def linear_dodge(fg, bg, opacity)
137
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
143
138
  return bg if fully_transparent?(fg)
144
139
 
145
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
140
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
146
141
 
147
142
  new_r = blend_channel(r(bg), (r(bg) + r(fg)) > 255 ? 255 : r(bg) + r(fg), mix_alpha)
148
143
  new_g = blend_channel(g(bg), (g(bg) + g(fg)) > 255 ? 255 : g(bg) + g(fg), mix_alpha)
@@ -156,11 +151,11 @@ class PSD
156
151
  # Contrasting blend modes
157
152
  #
158
153
 
159
- def overlay(fg, bg, opts={})
160
- return apply_opacity(fg, opts) if fully_transparent?(bg)
154
+ def overlay(fg, bg, opacity)
155
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
161
156
  return bg if fully_transparent?(fg)
162
157
 
163
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
158
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
164
159
 
165
160
  calculate_foreground = Proc.new do |b, f|
166
161
  if b < 128
@@ -177,11 +172,11 @@ class PSD
177
172
  rgba(new_r, new_g, new_b, dst_alpha)
178
173
  end
179
174
 
180
- def soft_light(fg, bg, opts={})
181
- return apply_opacity(fg, opts) if fully_transparent?(bg)
175
+ def soft_light(fg, bg, opacity)
176
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
182
177
  return bg if fully_transparent?(fg)
183
178
 
184
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
179
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
185
180
 
186
181
  calculate_foreground = Proc.new do |b, f|
187
182
  c1 = b * f >> 8
@@ -196,11 +191,11 @@ class PSD
196
191
  rgba(new_r, new_g, new_b, dst_alpha)
197
192
  end
198
193
 
199
- def hard_light(fg, bg, opts={})
200
- return apply_opacity(fg, opts) if fully_transparent?(bg)
194
+ def hard_light(fg, bg, opacity)
195
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
201
196
  return bg if fully_transparent?(fg)
202
197
 
203
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
198
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
204
199
 
205
200
  calculate_foreground = Proc.new do |b, f|
206
201
  if f < 128
@@ -217,11 +212,11 @@ class PSD
217
212
  rgba(new_r, new_g, new_b, dst_alpha)
218
213
  end
219
214
 
220
- def vivid_light(fg, bg, opts={})
221
- return apply_opacity(fg, opts) if fully_transparent?(bg)
215
+ def vivid_light(fg, bg, opacity)
216
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
222
217
  return bg if fully_transparent?(fg)
223
218
 
224
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
219
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
225
220
 
226
221
  calculate_foreground = Proc.new do |b, f|
227
222
  if f < 255
@@ -238,11 +233,11 @@ class PSD
238
233
  rgba(new_r, new_g, new_b, dst_alpha)
239
234
  end
240
235
 
241
- def linear_light(fg, bg, opts={})
242
- return apply_opacity(fg, opts) if fully_transparent?(bg)
236
+ def linear_light(fg, bg, opacity)
237
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
243
238
  return bg if fully_transparent?(fg)
244
239
 
245
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
240
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
246
241
 
247
242
  calculate_foreground = Proc.new do |b, f|
248
243
  if b < 255
@@ -259,11 +254,11 @@ class PSD
259
254
  rgba(new_r, new_g, new_b, dst_alpha)
260
255
  end
261
256
 
262
- def pin_light(fg, bg, opts={})
263
- return apply_opacity(fg, opts) if fully_transparent?(bg)
257
+ def pin_light(fg, bg, opacity)
258
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
264
259
  return bg if fully_transparent?(fg)
265
260
 
266
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
261
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
267
262
 
268
263
  calculate_foreground = Proc.new do |b, f|
269
264
  if f >= 128
@@ -280,11 +275,11 @@ class PSD
280
275
  rgba(new_r, new_g, new_b, dst_alpha)
281
276
  end
282
277
 
283
- def hard_mix(fg, bg, opts={})
284
- return apply_opacity(fg, opts) if fully_transparent?(bg)
278
+ def hard_mix(fg, bg, opacity)
279
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
285
280
  return bg if fully_transparent?(fg)
286
281
 
287
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
282
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
288
283
 
289
284
  new_r = blend_channel(r(bg), (r(bg) + r(fg) <= 255) ? 0 : 255, mix_alpha)
290
285
  new_g = blend_channel(g(bg), (g(bg) + g(fg) <= 255) ? 0 : 255, mix_alpha)
@@ -297,11 +292,11 @@ class PSD
297
292
  # Inversion blend modes
298
293
  #
299
294
 
300
- def difference(fg, bg, opts={})
301
- return apply_opacity(fg, opts) if fully_transparent?(bg)
295
+ def difference(fg, bg, opacity)
296
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
302
297
  return bg if fully_transparent?(fg)
303
298
 
304
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
299
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
305
300
 
306
301
  new_r = blend_channel(r(bg), (r(bg) - r(fg)).abs, mix_alpha)
307
302
  new_g = blend_channel(g(bg), (g(bg) - g(fg)).abs, mix_alpha)
@@ -310,11 +305,11 @@ class PSD
310
305
  rgba(new_r, new_g, new_b, dst_alpha)
311
306
  end
312
307
 
313
- def exclusion(fg, bg, opts={})
314
- return apply_opacity(fg, opts) if fully_transparent?(bg)
308
+ def exclusion(fg, bg, opacity)
309
+ return apply_opacity(fg, opacity) if fully_transparent?(bg)
315
310
  return bg if fully_transparent?(fg)
316
311
 
317
- mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
312
+ mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity)
318
313
 
319
314
  new_r = blend_channel(r(bg), r(bg) + r(fg) - (r(bg) * r(fg) >> 7), mix_alpha)
320
315
  new_g = blend_channel(g(bg), g(bg) + g(fg) - (g(bg) * g(fg) >> 7), mix_alpha)
@@ -323,16 +318,18 @@ class PSD
323
318
  rgba(new_r, new_g, new_b, dst_alpha)
324
319
  end
325
320
 
321
+ [:r, :g, :b, :a, :rgba, :fully_transparent?].each do |meth|
322
+ define_method(meth) { |*args| ChunkyPNG::Color.send(meth, *args) }
323
+ end
324
+
326
325
  # If the blend mode is missing, we fall back to normal composition.
327
326
  def method_missing(method, *args, &block)
328
- return ChunkyPNG::Color.send(method, *args) if ChunkyPNG::Color.respond_to?(method)
329
327
  normal(*args)
330
328
  end
331
329
 
332
330
  private
333
331
 
334
- def calculate_alphas(fg, bg, opts)
335
- opacity = calculate_opacity(opts)
332
+ def calculate_alphas(fg, bg, opacity)
336
333
  src_alpha = a(fg) * opacity >> 8
337
334
  dst_alpha = a(bg)
338
335
 
@@ -342,12 +339,8 @@ class PSD
342
339
  return mix_alpha, dst_alpha
343
340
  end
344
341
 
345
- def calculate_opacity(opts)
346
- opts[:opacity] * opts[:fill_opacity] / 255
347
- end
348
-
349
- def apply_opacity(color, opts)
350
- (color & 0xffffff00) | ((color & 0x000000ff) * calculate_opacity(opts) / 255)
342
+ def apply_opacity(color, opacity)
343
+ (color & 0xffffff00) | ((color & 0x000000ff) * opacity / 255)
351
344
  end
352
345
 
353
346
  def blend_channel(bg, fg, alpha)