psd 2.1.2 → 3.9.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 +5 -5
- data/.travis.yml +2 -0
- data/README.md +60 -20
- data/lib/psd/blend_mode.rb +46 -38
- data/lib/psd/channel_image.rb +11 -5
- data/lib/psd/color.rb +14 -5
- data/lib/psd/descriptor.rb +39 -16
- data/lib/psd/file.rb +9 -6
- data/lib/psd/header.rb +38 -33
- data/lib/psd/helpers.rb +2 -2
- data/lib/psd/image.rb +21 -16
- data/lib/psd/image_formats/layer_rle.rb +2 -2
- data/lib/psd/image_formats/rle.rb +4 -10
- data/lib/psd/image_modes/cmyk.rb +18 -5
- data/lib/psd/image_modes/greyscale.rb +6 -1
- data/lib/psd/image_modes/rgb.rb +17 -5
- data/lib/psd/layer/blend_modes.rb +15 -13
- data/lib/psd/layer/helpers.rb +8 -10
- data/lib/psd/layer/info/black_white.rb +33 -0
- data/lib/psd/{layer_info → layer/info}/blend_clipping_elements.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/blend_interior_elements.rb +4 -2
- data/lib/psd/layer/info/brightness_contrast.rb +21 -0
- data/lib/psd/layer/info/channel_blending_restrictions.rb +27 -0
- data/lib/psd/layer/info/channel_mixer.rb +27 -0
- data/lib/psd/layer/info/color_balance.rb +23 -0
- data/lib/psd/layer/info/color_lookup.rb +14 -0
- data/lib/psd/layer/info/content_generator.rb +42 -0
- data/lib/psd/layer/info/curves.rb +78 -0
- data/lib/psd/layer/info/effects_layer.rb +174 -0
- data/lib/psd/layer/info/exposure.rb +20 -0
- data/lib/psd/{layer_info → layer/info}/fill_opacity.rb +4 -2
- data/lib/psd/layer/info/gradient_fill.rb +14 -0
- data/lib/psd/layer/info/gradient_map.rb +69 -0
- data/lib/psd/layer/info/hue_saturation.rb +41 -0
- data/lib/psd/layer/info/invert.rb +17 -0
- data/lib/psd/layer/info/knockout.rb +22 -0
- data/lib/psd/layer/info/layer_effects.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/layer_group.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/layer_id.rb +4 -2
- data/lib/psd/layer/info/layer_mask_as_global_mask.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/layer_name_source.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/layer_section_divider.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/legacy_typetool.rb +6 -4
- data/lib/psd/layer/info/levels.rb +48 -0
- data/lib/psd/{layer_info → layer/info}/locked.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/metadata_setting.rb +5 -3
- data/lib/psd/{layer_info → layer/info}/object_effects.rb +5 -5
- data/lib/psd/layer/info/pattern.rb +14 -0
- data/lib/psd/layer/info/pattern_fill.rb +15 -0
- data/lib/psd/layer/info/photo_filter.rb +44 -0
- data/lib/psd/{layer_info → layer/info}/placed_layer.rb +4 -2
- data/lib/psd/layer/info/posterize.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/reference_point.rb +4 -2
- data/lib/psd/layer/info/selective_color.rb +32 -0
- data/lib/psd/layer/info/sheet_color.rb +36 -0
- data/lib/psd/layer/info/solid_color.rb +36 -0
- data/lib/psd/layer/info/threshold.rb +16 -0
- data/lib/psd/layer/info/transparency_shapes_layer.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/typetool.rb +27 -13
- data/lib/psd/{layer_info → layer/info}/unicode_name.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/vector_mask.rb +7 -5
- data/lib/psd/layer/info/vector_mask_as_global_mask.rb +16 -0
- data/lib/psd/layer/info/vector_origination.rb +14 -0
- data/lib/psd/{layer_info → layer/info}/vector_stroke.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/vector_stroke_content.rb +4 -2
- data/lib/psd/layer/info/vibrance.rb +22 -0
- data/lib/psd/layer/info.rb +106 -19
- data/lib/psd/layer/position_and_channels.rb +2 -6
- data/lib/psd/layer.rb +13 -16
- data/lib/psd/layer_info.rb +0 -4
- data/lib/psd/layer_mask.rb +61 -50
- data/lib/psd/lazy_execute.rb +5 -1
- data/lib/psd/mask.rb +7 -1
- data/lib/psd/node.rb +142 -49
- data/lib/psd/nodes/ancestry.rb +7 -6
- data/lib/psd/nodes/build_preview.rb +4 -4
- data/lib/psd/nodes/group.rb +36 -0
- data/lib/psd/nodes/layer.rb +45 -0
- data/lib/psd/nodes/layer_comps.rb +93 -0
- data/lib/psd/nodes/locking.rb +36 -0
- data/lib/psd/nodes/root.rb +87 -0
- data/lib/psd/nodes/search.rb +8 -65
- data/lib/psd/path_record.rb +5 -70
- data/lib/psd/renderer/blender.rb +10 -5
- data/lib/psd/renderer/cairo_helpers.rb +46 -0
- data/lib/psd/renderer/canvas.rb +41 -20
- data/lib/psd/renderer/canvas_management.rb +2 -2
- data/lib/psd/renderer/clipping_mask.rb +5 -4
- data/lib/psd/renderer/compose.rb +59 -69
- data/lib/psd/renderer/layer_styles/color_overlay.rb +45 -27
- data/lib/psd/renderer/layer_styles.rb +15 -5
- 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/renderer.rb +18 -7
- data/lib/psd/resource_section.rb +13 -6
- data/lib/psd/resources/base.rb +33 -0
- data/lib/psd/resources/guides.rb +6 -4
- data/lib/psd/resources/layer_comps.rb +6 -4
- data/lib/psd/resources/resolution_info.rb +48 -0
- data/lib/psd/resources/saved_path.rb +19 -0
- data/lib/psd/resources/slices.rb +7 -5
- data/lib/psd/resources/work_path.rb +22 -0
- data/lib/psd/resources/xmp_metadata.rb +46 -0
- data/lib/psd/resources.rb +17 -21
- data/lib/psd/slice.rb +44 -0
- data/lib/psd/slices.rb +13 -0
- data/lib/psd/util.rb +4 -2
- data/lib/psd/version.rb +1 -1
- data/lib/psd.rb +31 -45
- data/psd.gemspec +2 -3
- data/spec/files/alignment_modes.psd +0 -0
- data/spec/files/blendmodes.psd +0 -0
- data/spec/files/example.psb +0 -0
- data/spec/hierarchy_spec.rb +5 -0
- data/spec/locked_spec.rb +8 -8
- data/spec/psb_parsing_spec.rb +57 -0
- data/spec/text_spec.rb +13 -1
- metadata +115 -75
- data/circle.yml +0 -6
- 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
@@ -1,8 +1,26 @@
|
|
1
1
|
class PSD
|
2
2
|
class LayerStyles
|
3
3
|
class ColorOverlay
|
4
|
-
|
5
|
-
|
4
|
+
# TODO: CMYK support
|
5
|
+
def self.should_apply?(canvas, data)
|
6
|
+
data.has_key?('SoFi') &&
|
7
|
+
data['SoFi']['enab'] &&
|
8
|
+
canvas.node.header.rgb?
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.can_apply?(canvas, data)
|
12
|
+
data.has_key?('SoFi') &&
|
13
|
+
data['SoFi']['enab'] &&
|
14
|
+
canvas.node.header.rgb?
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for_canvas(canvas)
|
18
|
+
data = canvas.node.object_effects
|
19
|
+
return nil if data.nil?
|
20
|
+
return nil unless can_apply?(canvas, data.data)
|
21
|
+
|
22
|
+
styles = LayerStyles.new(canvas)
|
23
|
+
self.new(styles)
|
6
24
|
end
|
7
25
|
|
8
26
|
def initialize(styles)
|
@@ -12,41 +30,22 @@ class PSD
|
|
12
30
|
end
|
13
31
|
|
14
32
|
def apply!
|
15
|
-
# TODO - implement CMYK color overlay
|
16
|
-
return if @node.header.cmyk?
|
17
|
-
|
18
|
-
width = @canvas.width
|
19
|
-
height = @canvas.height
|
20
|
-
|
21
|
-
# puts width, height
|
22
|
-
# puts @canvas.canvas.width, @canvas.canvas.height
|
23
|
-
|
24
33
|
PSD.logger.debug "Layer style: layer = #{@node.name}, type = color overlay, blend mode = #{blending_mode}"
|
25
34
|
|
26
|
-
height.times do |y|
|
27
|
-
width.times do |x|
|
35
|
+
@canvas.height.times do |y|
|
36
|
+
@canvas.width.times do |x|
|
28
37
|
pixel = @canvas[x, y]
|
29
38
|
alpha = ChunkyPNG::Color.a(pixel)
|
30
39
|
next if alpha == 0
|
31
40
|
|
32
|
-
|
33
|
-
@canvas[x, y] =
|
41
|
+
new_pixel = Compose.send(blending_mode, overlay_color, pixel, overlay_opacity)
|
42
|
+
@canvas[x, y] = (new_pixel & 0xFFFFFF00) | alpha
|
34
43
|
end
|
35
44
|
end
|
36
45
|
end
|
37
46
|
|
38
|
-
|
39
|
-
|
40
|
-
def blending_mode
|
41
|
-
@blending_mode ||= BlendMode::BLEND_MODES[BLEND_TRANSLATION[overlay_data['Md ']].to_sym]
|
42
|
-
end
|
43
|
-
|
44
|
-
def overlay_data
|
45
|
-
@data['SoFi']
|
46
|
-
end
|
47
|
-
|
48
|
-
def color_data
|
49
|
-
overlay_data['Clr ']
|
47
|
+
def overlay_color
|
48
|
+
@overlay_color ||= ChunkyPNG::Color.rgb(r, g, b)
|
50
49
|
end
|
51
50
|
|
52
51
|
def r
|
@@ -60,6 +59,25 @@ class PSD
|
|
60
59
|
def b
|
61
60
|
@b ||= color_data['Bl '].round
|
62
61
|
end
|
62
|
+
|
63
|
+
def a
|
64
|
+
@a ||= (overlay_data['Opct'][:value] * 2.55).ceil
|
65
|
+
end
|
66
|
+
alias_method :overlay_opacity, :a
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def blending_mode
|
71
|
+
@blending_mode ||= BlendMode::BLEND_MODES[BLEND_TRANSLATION[overlay_data['Md '][:value]].to_sym]
|
72
|
+
end
|
73
|
+
|
74
|
+
def overlay_data
|
75
|
+
@data['SoFi']
|
76
|
+
end
|
77
|
+
|
78
|
+
def color_data
|
79
|
+
overlay_data['Clr ']
|
80
|
+
end
|
63
81
|
end
|
64
82
|
end
|
65
83
|
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require_relative 'layer_styles/drop_shadow'
|
1
|
+
require 'psd/renderer/layer_styles/color_overlay'
|
3
2
|
|
4
3
|
class PSD
|
5
4
|
class LayerStyles
|
@@ -31,12 +30,16 @@ class PSD
|
|
31
30
|
'Lmns' => 'lum'
|
32
31
|
}.freeze
|
33
32
|
|
33
|
+
SUPPORTED_STYLES = [
|
34
|
+
ColorOverlay
|
35
|
+
]
|
36
|
+
|
34
37
|
attr_reader :canvas, :node, :data
|
35
38
|
|
36
39
|
def initialize(canvas)
|
37
40
|
@canvas = canvas
|
38
41
|
@node = @canvas.node
|
39
|
-
@data = @node.
|
42
|
+
@data = @node.object_effects
|
40
43
|
|
41
44
|
if @data.nil?
|
42
45
|
@applied = true
|
@@ -48,9 +51,16 @@ class PSD
|
|
48
51
|
|
49
52
|
def apply!
|
50
53
|
return if @applied || data.nil?
|
54
|
+
return unless styles_enabled?
|
55
|
+
|
56
|
+
SUPPORTED_STYLES.each do |style|
|
57
|
+
next unless style.should_apply?(@canvas, data)
|
58
|
+
style.new(self).apply!
|
59
|
+
end
|
60
|
+
end
|
51
61
|
|
52
|
-
|
53
|
-
|
62
|
+
def styles_enabled?
|
63
|
+
data['masterFXSwitch']
|
54
64
|
end
|
55
65
|
end
|
56
66
|
end
|
data/lib/psd/renderer/mask.rb
CHANGED
@@ -3,11 +3,18 @@ class PSD
|
|
3
3
|
class Mask
|
4
4
|
attr_accessor :mask_data
|
5
5
|
|
6
|
-
def initialize(canvas)
|
6
|
+
def initialize(canvas, mask_layer = nil)
|
7
7
|
@canvas = canvas
|
8
8
|
@layer = canvas.node
|
9
|
+
@mask_layer = mask_layer || @layer
|
9
10
|
|
10
|
-
@mask_data = @
|
11
|
+
@mask_data = @mask_layer.image.mask_data
|
12
|
+
@mask = @mask_layer.mask
|
13
|
+
|
14
|
+
@mask_width = @mask.width.to_i
|
15
|
+
@mask_height = @mask.height.to_i
|
16
|
+
@mask_left = @mask.left.to_i + @mask_layer.left_offset
|
17
|
+
@mask_top = @mask.top.to_i + @mask_layer.top_offset
|
11
18
|
|
12
19
|
@doc_width = @layer.header.width.to_i
|
13
20
|
@doc_height = @layer.header.height.to_i
|
@@ -15,29 +22,26 @@ class PSD
|
|
15
22
|
|
16
23
|
def apply!
|
17
24
|
PSD.logger.debug "Applying mask to #{@layer.name}"
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# We're off the document canvas. Crop.
|
33
|
-
if doc_x < 0 || doc_x > @doc_width || doc_y < 0 || doc_y > @doc_height
|
25
|
+
|
26
|
+
@canvas.height.times do |y|
|
27
|
+
@canvas.width.times do |x|
|
28
|
+
doc_x = @canvas.left + x
|
29
|
+
doc_y = @canvas.top + y
|
30
|
+
|
31
|
+
mask_x = doc_x - @mask_left
|
32
|
+
mask_y = doc_y - @mask_top
|
33
|
+
|
34
|
+
color = ChunkyPNG::Color.to_truecolor_alpha_bytes(@canvas.get_pixel(x, y))
|
35
|
+
|
36
|
+
if doc_x < 0 || doc_x >= @doc_width || doc_y < 0 || doc_y >= @doc_height
|
37
|
+
color[3] = 0
|
38
|
+
elsif mask_x < 0 || mask_x >= @mask_width || mask_y < 0 || mask_y >= @mask_height
|
34
39
|
color[3] = 0
|
35
40
|
else
|
36
|
-
color[3] = color[3] * @mask_data[
|
37
|
-
end
|
41
|
+
color[3] = color[3] * @mask_data[@mask_width * mask_y + mask_x] / 255
|
42
|
+
end
|
38
43
|
|
39
|
-
@canvas
|
40
|
-
i += 1
|
44
|
+
@canvas.set_pixel x, y, ChunkyPNG::Color.rgba(*color)
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'psd/renderer/cairo_helpers'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
class Renderer
|
5
|
+
class VectorShape
|
6
|
+
include CairoHelpers
|
7
|
+
|
8
|
+
def self.can_render?(canvas)
|
9
|
+
canvas.opts[:render_vectors] && !canvas.node.vector_mask.nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
DPI = 72.0.freeze
|
13
|
+
|
14
|
+
def initialize(canvas)
|
15
|
+
@canvas = canvas
|
16
|
+
@node = @canvas.node
|
17
|
+
@path = @node.vector_mask.paths.map(&:to_hash)
|
18
|
+
|
19
|
+
@stroke_data = @node.vector_stroke ? @node.vector_stroke.data : {}
|
20
|
+
@fill_data = @node.vector_stroke_content ? @node.vector_stroke_content.data : {}
|
21
|
+
|
22
|
+
@paths = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def render!
|
26
|
+
PSD.logger.debug "Beginning vector render for #{@node.name}"
|
27
|
+
|
28
|
+
find_points
|
29
|
+
render_shapes
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def find_points
|
35
|
+
PSD.logger.debug "Formatting vector points..."
|
36
|
+
|
37
|
+
cur_path = nil
|
38
|
+
@path.each do |data|
|
39
|
+
next if [6, 7, 8].include? data[:record_type]
|
40
|
+
|
41
|
+
if [0, 3].include? data[:record_type]
|
42
|
+
@paths << cur_path
|
43
|
+
cur_path = []
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
cur_path << data.tap do |d|
|
48
|
+
if [1, 2, 4, 5].include? data[:record_type]
|
49
|
+
[:preceding, :anchor, :leaving].each do |type|
|
50
|
+
d[type][:horiz] = (d[type][:horiz] * horiz_factor) - @node.left
|
51
|
+
d[type][:vert] = (d[type][:vert] * vert_factor) - @node.top
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@paths << cur_path
|
58
|
+
@paths.compact!
|
59
|
+
|
60
|
+
PSD.logger.debug "Vector shape has #{@paths.size} path(s)"
|
61
|
+
end
|
62
|
+
|
63
|
+
# TODO: stroke alignment
|
64
|
+
# Right now we assume the stroke style is always a overlap stroke.
|
65
|
+
def render_shapes
|
66
|
+
PSD.logger.debug "Rendering #{@paths.size} vector paths with cairo"
|
67
|
+
|
68
|
+
cairo_image_surface(@canvas.width + stroke_size, @canvas.height + stroke_size) do |cr|
|
69
|
+
cr.set_fill_rule Cairo::FILL_RULE_EVEN_ODD
|
70
|
+
cr.set_line_join stroke_join
|
71
|
+
cr.set_line_cap stroke_cap
|
72
|
+
|
73
|
+
cr.translate stroke_size / 2.0, stroke_size / 2.0
|
74
|
+
|
75
|
+
@paths.each do |path|
|
76
|
+
cr.move_to path[0][:anchor][:horiz], path[0][:anchor][:vert]
|
77
|
+
|
78
|
+
path.size.times do |i|
|
79
|
+
point_a = path[i]
|
80
|
+
point_b = path[i+1] || path[0]
|
81
|
+
|
82
|
+
cr.curve_to(
|
83
|
+
point_a[:leaving][:horiz],
|
84
|
+
point_a[:leaving][:vert],
|
85
|
+
point_b[:preceding][:horiz],
|
86
|
+
point_b[:preceding][:vert],
|
87
|
+
point_b[:anchor][:horiz],
|
88
|
+
point_b[:anchor][:vert]
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
cr.close_path if path.last[:closed]
|
93
|
+
end
|
94
|
+
|
95
|
+
cr.set_source_rgba fill_color
|
96
|
+
cr.fill_preserve
|
97
|
+
|
98
|
+
if has_stroke?
|
99
|
+
cr.set_source_rgba stroke_color
|
100
|
+
cr.set_line_width stroke_size
|
101
|
+
cr.stroke
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# For debugging purposes only
|
107
|
+
def draw_debug(canvas)
|
108
|
+
@paths.each do |path|
|
109
|
+
path.each do |point|
|
110
|
+
canvas.circle(point[:anchor][:horiz].to_i, point[:anchor][:vert].to_i, 3, ChunkyPNG::Color::BLACK, ChunkyPNG::Color::BLACK)
|
111
|
+
[:leaving, :preceding].each do |type|
|
112
|
+
canvas.circle(point[type][:horiz].to_i, point[type][:vert].to_i, 3, ChunkyPNG::Color.rgb(255, 0, 0), ChunkyPNG::Color.rgb(255, 0, 0))
|
113
|
+
canvas.line(
|
114
|
+
point[:anchor][:horiz].to_i, point[:anchor][:vert].to_i,
|
115
|
+
point[type][:horiz].to_i, point[type][:vert].to_i,
|
116
|
+
ChunkyPNG::Color::BLACK
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def apply_to_canvas(output)
|
124
|
+
# draw_debug(output)
|
125
|
+
output.resample_nearest_neighbor!(@canvas.width, @canvas.height)
|
126
|
+
@canvas.canvas.compose!(output, 0, 0)
|
127
|
+
end
|
128
|
+
|
129
|
+
def formatted_points
|
130
|
+
@formatted_points ||= @curve_points.map(&:to_a)
|
131
|
+
end
|
132
|
+
|
133
|
+
def horiz_factor
|
134
|
+
@horiz_factor ||= @node.root.width.to_f
|
135
|
+
end
|
136
|
+
|
137
|
+
def vert_factor
|
138
|
+
@vert_factor ||= @node.root.height.to_f
|
139
|
+
end
|
140
|
+
|
141
|
+
def stroke_color
|
142
|
+
@stroke_color ||= (
|
143
|
+
if @stroke_data['strokeEnabled']
|
144
|
+
colors = @stroke_data['strokeStyleContent']['Clr ']
|
145
|
+
[
|
146
|
+
colors['Rd '] / 255.0,
|
147
|
+
colors['Grn '] / 255.0,
|
148
|
+
colors['Bl '] / 255.0,
|
149
|
+
@stroke_data['strokeStyleOpacity'][:value] / 100.0
|
150
|
+
]
|
151
|
+
else
|
152
|
+
[0.0, 0.0, 0.0, 0.0]
|
153
|
+
end
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
def fill_color
|
158
|
+
@fill_color ||= (
|
159
|
+
overlay = PSD::LayerStyles::ColorOverlay.for_canvas(@canvas)
|
160
|
+
|
161
|
+
if overlay
|
162
|
+
[
|
163
|
+
overlay.r / 255.0,
|
164
|
+
overlay.g / 255.0,
|
165
|
+
overlay.b / 255.0,
|
166
|
+
overlay.a / 255.0
|
167
|
+
]
|
168
|
+
elsif @stroke_data['fillEnabled']
|
169
|
+
colors = @fill_data['Clr ']
|
170
|
+
[
|
171
|
+
colors['Rd '] / 255.0,
|
172
|
+
colors['Grn '] / 255.0,
|
173
|
+
colors['Bl '] / 255.0,
|
174
|
+
@stroke_data['strokeStyleOpacity'][:value] / 100.0
|
175
|
+
]
|
176
|
+
elsif !@node.solid_color.nil?
|
177
|
+
[
|
178
|
+
@node.solid_color.r / 255.0,
|
179
|
+
@node.solid_color.g / 255.0,
|
180
|
+
@node.solid_color.b / 255.0,
|
181
|
+
1.0
|
182
|
+
]
|
183
|
+
else
|
184
|
+
[0.0, 0.0, 0.0, 0.0]
|
185
|
+
end
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
def stroke_size
|
190
|
+
@stroke_size ||= (
|
191
|
+
if @stroke_data['strokeStyleLineWidth']
|
192
|
+
value = @stroke_data['strokeStyleLineWidth'][:value]
|
193
|
+
|
194
|
+
# Convert to pixels
|
195
|
+
if @stroke_data['strokeStyleLineWidth'][:id] == '#Pnt'
|
196
|
+
value = @stroke_data['strokeStyleResolution'] * value / 72.27
|
197
|
+
end
|
198
|
+
|
199
|
+
value.to_i
|
200
|
+
else
|
201
|
+
0
|
202
|
+
end
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
def stroke_cap
|
207
|
+
@stroke_cap ||= (
|
208
|
+
if @stroke_data['strokeStyleLineCapType']
|
209
|
+
case @stroke_data['strokeStyleLineCapType']
|
210
|
+
when 'strokeStyleButtCap' then Cairo::LINE_CAP_BUTT
|
211
|
+
when 'strokeStyleRoundCap' then Cairo::LINE_CAP_ROUND
|
212
|
+
when 'strokeStyleSquareCap' then Cairo::LINE_CAP_SQUARE
|
213
|
+
end
|
214
|
+
else
|
215
|
+
Cairo::LINE_CAP_BUTT
|
216
|
+
end
|
217
|
+
)
|
218
|
+
end
|
219
|
+
|
220
|
+
def stroke_join
|
221
|
+
@stroke_join ||= (
|
222
|
+
if @stroke_data['strokeStyleLineJoinType']
|
223
|
+
case @stroke_data['strokeStyleLineJoinType']
|
224
|
+
when 'strokeStyleMiterJoin' then Cairo::LINE_JOIN_MITER
|
225
|
+
when 'strokeStyleRoundJoin' then Cairo::LINE_JOIN_ROUND
|
226
|
+
when 'strokeStyleBevelJoin' then Cairo::LINE_JOIN_BEVEL
|
227
|
+
end
|
228
|
+
else
|
229
|
+
Cairo::LINE_JOIN_MITER
|
230
|
+
end
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
def has_stroke?
|
235
|
+
stroke_size > 0
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
data/lib/psd/renderer.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
-
|
1
|
+
require 'psd/renderer/blender'
|
2
|
+
require 'psd/renderer/canvas'
|
3
|
+
require 'psd/renderer/canvas_management'
|
4
|
+
require 'psd/renderer/clipping_mask'
|
5
|
+
require 'psd/renderer/compose'
|
6
|
+
require 'psd/renderer/layer_styles'
|
7
|
+
require 'psd/renderer/mask'
|
8
|
+
require 'psd/renderer/mask_canvas'
|
2
9
|
|
3
10
|
class PSD
|
4
11
|
class Renderer
|
5
12
|
include CanvasManagement
|
6
13
|
|
7
|
-
def initialize(node)
|
14
|
+
def initialize(node, opts = {})
|
8
15
|
@root_node = node
|
16
|
+
@opts = opts
|
17
|
+
@render_hidden = opts.delete(:render_hidden)
|
9
18
|
|
10
19
|
# Our canvas always starts as the full document size because
|
11
20
|
# all measurements are relative to this size. We can later crop
|
@@ -23,7 +32,7 @@ class PSD
|
|
23
32
|
PSD.logger.debug "Beginning render process"
|
24
33
|
|
25
34
|
# Create our base canvas
|
26
|
-
create_group_canvas(active_node, active_node.width, active_node.height)
|
35
|
+
create_group_canvas(active_node, active_node.width, active_node.height, base: true)
|
27
36
|
|
28
37
|
# Begin the rendering process
|
29
38
|
execute_pipeline
|
@@ -34,8 +43,10 @@ class PSD
|
|
34
43
|
def execute_pipeline
|
35
44
|
PSD.logger.debug "Executing pipeline on #{active_node.debug_name}"
|
36
45
|
children.reverse.each do |child|
|
37
|
-
# We skip over hidden nodes
|
38
|
-
|
46
|
+
# We skip over hidden nodes unless explicitly set otherwise.
|
47
|
+
# If rendering a single PSD::Node::Layer, this is automatically
|
48
|
+
# set so that hidden layers will render.
|
49
|
+
next if !@render_hidden && !child.visible?
|
39
50
|
|
40
51
|
if child.group?
|
41
52
|
push_node(child)
|
@@ -56,7 +67,7 @@ class PSD
|
|
56
67
|
pop_node and next
|
57
68
|
end
|
58
69
|
|
59
|
-
canvas = Canvas.new(child)
|
70
|
+
canvas = Canvas.new(child, nil, nil, @opts)
|
60
71
|
canvas.paint_to active_canvas
|
61
72
|
end
|
62
73
|
end
|
@@ -88,4 +99,4 @@ class PSD
|
|
88
99
|
@node_stack.last
|
89
100
|
end
|
90
101
|
end
|
91
|
-
end
|
102
|
+
end
|
data/lib/psd/resource_section.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
1
|
class PSD
|
2
2
|
class Resource
|
3
|
-
|
3
|
+
module Section
|
4
4
|
def self.factory(file, resource)
|
5
|
+
if path_resource?(resource)
|
6
|
+
section = Section::SavedPath.new(file, resource)
|
7
|
+
section.parse
|
8
|
+
return section.name
|
9
|
+
end
|
10
|
+
|
5
11
|
Section.constants.each do |c|
|
12
|
+
next if c == :Base
|
13
|
+
|
6
14
|
section = Section.const_get(c)
|
7
|
-
next unless section.
|
15
|
+
next unless section.resource_id == resource.id
|
8
16
|
|
9
17
|
section.new(file, resource).parse
|
10
18
|
return section.name
|
@@ -13,10 +21,9 @@ class PSD
|
|
13
21
|
return nil
|
14
22
|
end
|
15
23
|
|
16
|
-
def
|
17
|
-
|
18
|
-
@resource = resource
|
24
|
+
def self.path_resource?(resource)
|
25
|
+
resource.id >= 2000 && resource.id <= 2997
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
22
|
-
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class PSD
|
2
|
+
class Resource
|
3
|
+
module Section
|
4
|
+
class Base
|
5
|
+
def self.resource_id(id = nil)
|
6
|
+
@resource_id = id unless id.nil?
|
7
|
+
@resource_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.name(name = nil)
|
11
|
+
@name = name unless name.nil?
|
12
|
+
@name
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(file, resource)
|
16
|
+
@file = file
|
17
|
+
@resource = resource
|
18
|
+
end
|
19
|
+
|
20
|
+
def resource_id; self.class.resource_id; end
|
21
|
+
def name; self.class.name; end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'psd/resources/guides'
|
28
|
+
require 'psd/resources/layer_comps'
|
29
|
+
require 'psd/resources/resolution_info'
|
30
|
+
require 'psd/resources/saved_path'
|
31
|
+
require 'psd/resources/slices'
|
32
|
+
require 'psd/resources/work_path'
|
33
|
+
require 'psd/resources/xmp_metadata'
|
data/lib/psd/resources/guides.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
require 'psd/resources/base'
|
2
|
+
|
1
3
|
class PSD
|
2
4
|
class Resource
|
3
|
-
|
4
|
-
class Guides <
|
5
|
-
|
6
|
-
|
5
|
+
module Section
|
6
|
+
class Guides < Base
|
7
|
+
resource_id 1032
|
8
|
+
name :guides
|
7
9
|
|
8
10
|
def parse
|
9
11
|
# Descriptor version
|
@@ -1,9 +1,11 @@
|
|
1
|
+
require 'psd/resources/base'
|
2
|
+
|
1
3
|
class PSD
|
2
4
|
class Resource
|
3
|
-
|
4
|
-
class LayerComps <
|
5
|
-
|
6
|
-
|
5
|
+
module Section
|
6
|
+
class LayerComps < Base
|
7
|
+
resource_id 1065
|
8
|
+
name :layer_comps
|
7
9
|
|
8
10
|
def self.visibility_captured?(comp)
|
9
11
|
comp[:captured_info] & 0b001 > 0
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'psd/resources/base'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
class Resource
|
5
|
+
module Section
|
6
|
+
class ResolutionInfo < Base
|
7
|
+
RES_UNIT_NAMES = %w(pixel/inch pixel/cm).freeze
|
8
|
+
UNIT_NAMES = %w(in cm pt picas columns).freeze
|
9
|
+
|
10
|
+
resource_id 1005
|
11
|
+
name :resolution_info
|
12
|
+
|
13
|
+
attr_reader :h_res, :h_res_unit, :width_unit
|
14
|
+
attr_reader :v_res, :v_res_unit, :height_unit
|
15
|
+
|
16
|
+
def parse
|
17
|
+
# 32-bit fixed-point number (16.16)
|
18
|
+
@h_res = @file.read_uint.to_f / (1 << 16)
|
19
|
+
@h_res_unit = @file.read_ushort
|
20
|
+
@width_unit = @file.read_ushort
|
21
|
+
|
22
|
+
# 32-bit fixed-point number (16.16)
|
23
|
+
@v_res = @file.read_uint.to_f / (1 << 16)
|
24
|
+
@v_res_unit = @file.read_ushort
|
25
|
+
@height_unit = @file.read_ushort
|
26
|
+
|
27
|
+
@resource.data = self
|
28
|
+
end
|
29
|
+
|
30
|
+
def h_res_unit_name
|
31
|
+
RES_UNIT_NAMES.fetch(h_res_unit - 1, 'unknown')
|
32
|
+
end
|
33
|
+
|
34
|
+
def v_res_unit_name
|
35
|
+
RES_UNIT_NAMES.fetch(v_res_unit - 1, 'unknown')
|
36
|
+
end
|
37
|
+
|
38
|
+
def width_unit_name
|
39
|
+
UNIT_NAMES.fetch(width_unit - 1, 'unknown')
|
40
|
+
end
|
41
|
+
|
42
|
+
def height_unit_name
|
43
|
+
UNIT_NAMES.fetch(height_unit - 1, 'unknown')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'psd/resources/base'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
class Resource
|
5
|
+
module Section
|
6
|
+
class SavedPath < Base
|
7
|
+
def parse
|
8
|
+
paths = []
|
9
|
+
record_count = @resource.size / 26
|
10
|
+
record_count.times do
|
11
|
+
paths << PathRecord.new(@file)
|
12
|
+
end
|
13
|
+
|
14
|
+
@resource.data = paths
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|