psd 2.1.2 → 3.1.2
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 +4 -4
- data/README.md +1 -1
- data/lib/psd.rb +8 -6
- data/lib/psd/blend_mode.rb +46 -38
- data/lib/psd/channel_image.rb +9 -5
- data/lib/psd/descriptor.rb +39 -16
- data/lib/psd/header.rb +33 -32
- data/lib/psd/image_formats/rle.rb +4 -10
- data/lib/psd/image_modes/rgb.rb +4 -4
- data/lib/psd/layer.rb +1 -15
- data/lib/psd/layer/blend_modes.rb +12 -12
- data/lib/psd/layer/helpers.rb +8 -10
- data/lib/psd/layer/info.rb +9 -7
- data/lib/psd/layer/position_and_channels.rb +0 -4
- data/lib/psd/layer_info.rb +0 -4
- data/lib/psd/layer_info/blend_clipping_elements.rb +4 -2
- data/lib/psd/layer_info/blend_interior_elements.rb +4 -2
- data/lib/psd/layer_info/fill_opacity.rb +4 -2
- data/lib/psd/layer_info/layer_group.rb +4 -2
- data/lib/psd/layer_info/layer_id.rb +4 -2
- data/lib/psd/layer_info/layer_name_source.rb +4 -2
- data/lib/psd/layer_info/layer_section_divider.rb +4 -2
- data/lib/psd/layer_info/legacy_typetool.rb +5 -3
- data/lib/psd/layer_info/locked.rb +4 -2
- data/lib/psd/layer_info/metadata_setting.rb +4 -2
- data/lib/psd/layer_info/object_effects.rb +4 -2
- data/lib/psd/layer_info/pattern.rb +14 -0
- data/lib/psd/layer_info/placed_layer.rb +4 -2
- data/lib/psd/layer_info/reference_point.rb +4 -2
- data/lib/psd/layer_info/sheet_color.rb +18 -0
- data/lib/psd/layer_info/solid_color.rb +36 -0
- data/lib/psd/layer_info/typetool.rb +4 -2
- data/lib/psd/layer_info/unicode_name.rb +4 -2
- data/lib/psd/layer_info/vector_mask.rb +4 -2
- data/lib/psd/layer_info/vector_origination.rb +14 -0
- data/lib/psd/layer_info/vector_stroke.rb +4 -2
- data/lib/psd/layer_info/vector_stroke_content.rb +4 -2
- data/lib/psd/layer_mask.rb +2 -8
- data/lib/psd/lazy_execute.rb +5 -1
- data/lib/psd/node.rb +112 -48
- data/lib/psd/nodes/ancestry.rb +80 -75
- data/lib/psd/nodes/build_preview.rb +4 -4
- data/lib/psd/nodes/group.rb +35 -0
- data/lib/psd/nodes/layer.rb +40 -0
- data/lib/psd/nodes/root.rb +90 -0
- data/lib/psd/nodes/search.rb +19 -19
- data/lib/psd/path_record.rb +1 -71
- data/lib/psd/renderer.rb +6 -5
- data/lib/psd/renderer/blender.rb +10 -5
- data/lib/psd/renderer/cairo_helpers.rb +46 -0
- data/lib/psd/renderer/canvas.rb +39 -19
- data/lib/psd/renderer/canvas_management.rb +2 -2
- data/lib/psd/renderer/clipping_mask.rb +5 -4
- data/lib/psd/renderer/compose.rb +61 -68
- data/lib/psd/renderer/layer_styles.rb +15 -5
- data/lib/psd/renderer/layer_styles/color_overlay.rb +46 -27
- data/lib/psd/renderer/mask.rb +26 -22
- data/lib/psd/renderer/mask_canvas.rb +12 -0
- data/lib/psd/renderer/vector_shape.rb +239 -0
- data/lib/psd/resource_section.rb +4 -7
- data/lib/psd/resources.rb +4 -19
- data/lib/psd/resources/base.rb +27 -0
- data/lib/psd/resources/guides.rb +6 -4
- data/lib/psd/resources/layer_comps.rb +6 -4
- data/lib/psd/resources/slices.rb +7 -5
- data/lib/psd/version.rb +1 -1
- data/psd.gemspec +1 -2
- data/spec/files/blendmodes.psd +0 -0
- data/spec/hierarchy_spec.rb +5 -0
- metadata +27 -26
- data/lib/psd/layer_info/vector_mask_2.rb +0 -10
- data/lib/psd/node_exporting.rb +0 -20
- data/lib/psd/node_group.rb +0 -86
- data/lib/psd/node_layer.rb +0 -81
- data/lib/psd/node_root.rb +0 -93
- data/lib/psd/nodes/has_children.rb +0 -13
- data/lib/psd/nodes/lock_to_origin.rb +0 -7
- data/lib/psd/nodes/parse_layers.rb +0 -18
- data/lib/psd/renderer/layer_styles/drop_shadow.rb +0 -75
- 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
|
data/lib/psd/renderer/canvas.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
1
|
class PSD
|
2
2
|
class Renderer
|
3
3
|
class Canvas
|
4
|
-
attr_reader :canvas, :node, :
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
61
|
-
|
60
|
+
def has_fill?
|
61
|
+
!@opts[:base] && @node.layer? && @node.solid_color
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
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
|
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.
|
11
|
-
|
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
|
35
|
-
@canvas
|
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
|
data/lib/psd/renderer/compose.rb
CHANGED
@@ -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,
|
19
|
-
return apply_opacity(fg,
|
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,
|
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,
|
36
|
-
return apply_opacity(fg,
|
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,
|
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,
|
48
|
-
return apply_opacity(fg,
|
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,
|
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,
|
60
|
-
return apply_opacity(fg,
|
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,
|
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,
|
82
|
-
return apply_opacity(fg,
|
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,
|
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,
|
99
|
-
return apply_opacity(fg,
|
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,
|
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,
|
112
|
-
return apply_opacity(fg,
|
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,
|
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,
|
125
|
-
return apply_opacity(fg,
|
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,
|
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,
|
142
|
-
return apply_opacity(fg,
|
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,
|
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,
|
160
|
-
return apply_opacity(fg,
|
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,
|
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,
|
181
|
-
return apply_opacity(fg,
|
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,
|
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,
|
200
|
-
return apply_opacity(fg,
|
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,
|
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,
|
221
|
-
return apply_opacity(fg,
|
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,
|
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,
|
242
|
-
return apply_opacity(fg,
|
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,
|
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,
|
263
|
-
return apply_opacity(fg,
|
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,
|
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,
|
284
|
-
return apply_opacity(fg,
|
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,
|
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,
|
301
|
-
return apply_opacity(fg,
|
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,
|
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,
|
314
|
-
return apply_opacity(fg,
|
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,
|
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,
|
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
|
346
|
-
|
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)
|