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
data/lib/psd/mask.rb
CHANGED
@@ -15,6 +15,8 @@ class PSD
|
|
15
15
|
@left = 0
|
16
16
|
@bottom = 0
|
17
17
|
@right = 0
|
18
|
+
@default_color = 0
|
19
|
+
@flags = 0
|
18
20
|
end
|
19
21
|
|
20
22
|
def parse
|
@@ -54,6 +56,10 @@ class PSD
|
|
54
56
|
(@flags & (0x01 << 2)) > 0
|
55
57
|
end
|
56
58
|
|
59
|
+
def from_other_data
|
60
|
+
(@flags & (0x01 << 3)) > 0
|
61
|
+
end
|
62
|
+
|
57
63
|
def to_hash
|
58
64
|
return {} if @size == 0
|
59
65
|
|
@@ -71,4 +77,4 @@ class PSD
|
|
71
77
|
}
|
72
78
|
end
|
73
79
|
end
|
74
|
-
end
|
80
|
+
end
|
data/lib/psd/node.rb
CHANGED
@@ -1,70 +1,163 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'psd/nodes/ancestry'
|
2
|
+
require 'psd/nodes/build_preview'
|
3
|
+
require 'psd/nodes/search'
|
4
|
+
require 'psd/nodes/layer_comps'
|
5
|
+
require 'psd/nodes/locking'
|
3
6
|
|
4
7
|
# Internal structure to help us build trees of a Photoshop documents.
|
5
8
|
# A lot of method names borrowed from the Ruby ancestry gem.
|
6
9
|
class PSD
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
include Search
|
11
|
-
include BuildPreview
|
10
|
+
module Node
|
11
|
+
class Base
|
12
|
+
extend Forwardable
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
include Enumerable
|
15
|
+
include Ancestry
|
16
|
+
include Search
|
17
|
+
include LayerComps
|
18
|
+
include BuildPreview
|
19
|
+
include Locking
|
15
20
|
|
16
|
-
|
21
|
+
# Default properties that all nodes contain
|
22
|
+
PROPERTIES = [:name, :left, :right, :top, :bottom, :height, :width]
|
17
23
|
|
18
|
-
|
19
|
-
|
24
|
+
attr_reader :id, :name, :parent
|
25
|
+
attr_accessor :children, :layer, :force_visible, :top_offset, :left_offset
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
27
|
+
def_delegators :parent, :psd, :document_dimensions
|
28
|
+
def_delegator :layer, :name
|
29
|
+
def_delegator :children, :each
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
31
|
+
def initialize(layer, parent = nil)
|
32
|
+
@layer = layer
|
33
|
+
@layer.node = self
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
@parent = parent
|
36
|
+
@children = []
|
37
|
+
|
38
|
+
@id = begin layer.layer_id.id rescue nil end
|
39
|
+
@force_visible = nil
|
40
|
+
@top = @layer.top.to_i
|
41
|
+
@bottom = @layer.bottom.to_i
|
42
|
+
@left = @layer.left.to_i
|
43
|
+
@right = @layer.right.to_i
|
34
44
|
|
35
|
-
|
36
|
-
|
37
|
-
|
45
|
+
@top_offset = 0
|
46
|
+
@left_offset = 0
|
47
|
+
end
|
38
48
|
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
def top
|
50
|
+
@top + @top_offset
|
51
|
+
end
|
42
52
|
|
43
|
-
|
44
|
-
|
45
|
-
|
53
|
+
def bottom
|
54
|
+
@bottom + @top_offset
|
55
|
+
end
|
46
56
|
|
47
|
-
|
48
|
-
|
49
|
-
|
57
|
+
def left
|
58
|
+
@left + @left_offset
|
59
|
+
end
|
50
60
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
visible: visible?,
|
55
|
-
opacity: @layer.opacity / 255.0,
|
56
|
-
blending_mode: @layer.blending_mode
|
57
|
-
}
|
61
|
+
def right
|
62
|
+
@right + @left_offset
|
63
|
+
end
|
58
64
|
|
59
|
-
|
60
|
-
|
65
|
+
def width
|
66
|
+
right - left
|
61
67
|
end
|
62
68
|
|
63
|
-
|
64
|
-
|
69
|
+
def height
|
70
|
+
bottom - top
|
71
|
+
end
|
72
|
+
|
73
|
+
def hidden?
|
74
|
+
!visible?
|
75
|
+
end
|
76
|
+
|
77
|
+
def visible?
|
78
|
+
return false if @layer.clipped? && !clipping_mask.visible?
|
79
|
+
@force_visible.nil? ? @layer.visible? : @force_visible
|
80
|
+
end
|
81
|
+
|
82
|
+
def clipping_mask
|
83
|
+
return nil unless @layer.clipped?
|
84
|
+
|
85
|
+
@clipping_mask ||= (
|
86
|
+
mask_node = next_sibling
|
87
|
+
while mask_node.clipped?
|
88
|
+
mask_node = mask_node.next_sibling
|
89
|
+
end
|
90
|
+
|
91
|
+
mask_node
|
92
|
+
)
|
93
|
+
end
|
94
|
+
alias_method :clipped_by, :clipping_mask
|
95
|
+
|
96
|
+
# Color label is a little tricky. If you set the color of a group, all
|
97
|
+
# of it's descendants inhert the color unless manually overridden. So,
|
98
|
+
# if this node has no defined color, we have to walk up the ancestry tree
|
99
|
+
# to make sure the color isn't set somewhere else.
|
100
|
+
def color_label
|
101
|
+
color = layer.sheet_color.color
|
102
|
+
return color if color != :no_color || node.parent.root?
|
103
|
+
|
104
|
+
parent.color_label
|
105
|
+
end
|
106
|
+
|
107
|
+
def layer?
|
108
|
+
is_a?(PSD::Node::Layer)
|
109
|
+
end
|
110
|
+
|
111
|
+
def group?(include_root = true)
|
112
|
+
is_a?(PSD::Node::Group) || (include_root && is_a?(PSD::Node::Root))
|
113
|
+
end
|
114
|
+
|
115
|
+
def debug_name
|
116
|
+
root? ? ":root:" : name
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_hash
|
120
|
+
hash = {
|
121
|
+
type: nil,
|
122
|
+
visible: visible?,
|
123
|
+
opacity: @layer.opacity / 255.0,
|
124
|
+
blending_mode: @layer.blending_mode,
|
125
|
+
layer_comps: {}
|
126
|
+
}
|
65
127
|
|
66
|
-
|
67
|
-
|
128
|
+
PROPERTIES.each do |p|
|
129
|
+
hash[p] = self.send(p)
|
130
|
+
end
|
131
|
+
|
132
|
+
root.psd.layer_comps.each do |comp|
|
133
|
+
hash[:layer_comps][comp[:name]] = {
|
134
|
+
visible: visible_in_comp?(comp[:id]),
|
135
|
+
position: position_in_comp(comp[:id])
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
hash
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def update_dimensions!
|
145
|
+
return if layer?
|
146
|
+
|
147
|
+
children.each { |child| child.update_dimensions! }
|
148
|
+
|
149
|
+
return if root?
|
150
|
+
|
151
|
+
non_empty_children = children.reject(&:empty?)
|
152
|
+
@left = non_empty_children.map(&:left).min || 0
|
153
|
+
@top = non_empty_children.map(&:top).min || 0
|
154
|
+
@bottom = non_empty_children.map(&:bottom).max || 0
|
155
|
+
@right = non_empty_children.map(&:right).max || 0
|
156
|
+
end
|
68
157
|
end
|
69
158
|
end
|
70
|
-
end
|
159
|
+
end
|
160
|
+
|
161
|
+
require 'psd/nodes/group'
|
162
|
+
require 'psd/nodes/layer'
|
163
|
+
require 'psd/nodes/root'
|
data/lib/psd/nodes/ancestry.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class PSD
|
2
|
-
|
2
|
+
module Node
|
3
3
|
# Collection of methods to help in traversing the PSD tree structure.
|
4
4
|
module Ancestry
|
5
5
|
# Returns the root node
|
@@ -75,8 +75,9 @@ class PSD
|
|
75
75
|
return ancestors.length + 1
|
76
76
|
end
|
77
77
|
|
78
|
-
def path
|
79
|
-
(ancestors.map(&:name) + [name])
|
78
|
+
def path(as_array = false)
|
79
|
+
path = (ancestors.map(&:name) + [name])
|
80
|
+
as_array ? path : path.join('/')
|
80
81
|
end
|
81
82
|
|
82
83
|
def method_missing(method, *args, &block)
|
@@ -91,8 +92,8 @@ class PSD
|
|
91
92
|
|
92
93
|
private
|
93
94
|
|
94
|
-
def layers_only(d); d.
|
95
|
-
def groups_only(d); d.
|
95
|
+
def layers_only(d); d.layer?; end
|
96
|
+
def groups_only(d); d.group?(false); end
|
96
97
|
end
|
97
98
|
end
|
98
|
-
end
|
99
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
class PSD
|
2
|
-
|
2
|
+
module Node
|
3
3
|
module BuildPreview
|
4
|
-
def renderer
|
5
|
-
PSD::Renderer.new(self)
|
4
|
+
def renderer(opts = {})
|
5
|
+
PSD::Renderer.new(self, opts)
|
6
6
|
end
|
7
7
|
|
8
8
|
def to_png
|
9
|
-
renderer.to_png
|
9
|
+
@png ||= renderer(render_hidden: self.layer?).to_png
|
10
10
|
end
|
11
11
|
|
12
12
|
def save_as_png(output)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'psd/node'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
module Node
|
5
|
+
# Represents a group, or folder, in the PSD document. It can have
|
6
|
+
# zero or more children nodes.
|
7
|
+
class Group < PSD::Node::Base
|
8
|
+
def passthru_blending?
|
9
|
+
blending_mode == 'passthru'
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty?
|
13
|
+
children.each do |child|
|
14
|
+
return false unless child.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
|
20
|
+
# Export this layer and it's children to a hash recursively.
|
21
|
+
def to_hash
|
22
|
+
super.merge({
|
23
|
+
type: :group,
|
24
|
+
open: section_divider ? section_divider.layer_type == 'open folder' : false,
|
25
|
+
children: children.map(&:to_hash)
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
# If the method is missing, we blindly send it to the layer.
|
30
|
+
# The layer handles the case in which the method doesn't exist.
|
31
|
+
def method_missing(method, *args, &block)
|
32
|
+
@layer.send(method, *args, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'psd/node'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
module Node
|
5
|
+
class Layer < PSD::Node::Base
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :layer
|
9
|
+
|
10
|
+
def_delegators :@layer, :text, :ref_x, :ref_y, :blending_mode
|
11
|
+
def_delegators :@layer, :text=, :ref_x=, :ref_y=, :blending_mode=
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
width == 0 || height == 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# Exports this layer to a Hash.
|
18
|
+
def to_hash
|
19
|
+
super.merge({
|
20
|
+
type: :layer,
|
21
|
+
text: @layer.text,
|
22
|
+
ref_x: reference_point.x,
|
23
|
+
ref_y: reference_point.y,
|
24
|
+
mask: @layer.mask.to_hash,
|
25
|
+
image: {
|
26
|
+
width: @layer.image.width,
|
27
|
+
height: @layer.image.height,
|
28
|
+
channels: @layer.channels_info
|
29
|
+
}
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
# In case the layer doesn't have a reference point
|
34
|
+
def reference_point
|
35
|
+
@layer.reference_point || Struct.new(:x, :y).new(0, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
# If the method is missing, we blindly send it to the layer.
|
39
|
+
# The layer handles the case in which the method doesn't exist.
|
40
|
+
def method_missing(method, *args, &block)
|
41
|
+
@layer.send(method, *args, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
class PSD
|
2
|
+
module Node
|
3
|
+
module LayerComps
|
4
|
+
# Given a layer comp ID or name, create a new
|
5
|
+
# tree with layer/group visibility altered based on the layer comp.
|
6
|
+
def filter_by_comp(id)
|
7
|
+
comp = find_comp(id)
|
8
|
+
root = PSD::Node::Root.new(psd)
|
9
|
+
|
10
|
+
# Force layers to be visible if they are enabled for the comp
|
11
|
+
root.descendants.each do |c|
|
12
|
+
set_visibility(comp, c) if Resource::Section::LayerComps.visibility_captured?(comp)
|
13
|
+
set_position(comp, c) if Resource::Section::LayerComps.position_captured?(comp)
|
14
|
+
|
15
|
+
PSD.logger.debug "#{c.path}: visible = #{c.visible?}, position = #{c.left}, #{c.top}"
|
16
|
+
end
|
17
|
+
|
18
|
+
return root
|
19
|
+
end
|
20
|
+
|
21
|
+
def visible_in_comp?(id)
|
22
|
+
determine_visibility(find_comp(id), self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def position_in_comp(id)
|
26
|
+
offset = determine_position_offset(find_comp(id), self)
|
27
|
+
|
28
|
+
{
|
29
|
+
top: top + offset[:y],
|
30
|
+
right: right + offset[:x],
|
31
|
+
bottom: bottom + offset[:y],
|
32
|
+
left: left + offset[:x]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_comp(id)
|
39
|
+
comp = if id.is_a?(String)
|
40
|
+
psd.layer_comps.select { |c| c[:name] == id }.first
|
41
|
+
else
|
42
|
+
psd.layer_comps.select { |c| c[:id] == id }.first
|
43
|
+
end
|
44
|
+
|
45
|
+
raise "Layer comp not found" if comp.nil?
|
46
|
+
comp
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_visibility(comp, c)
|
50
|
+
c.force_visible = determine_visibility(comp, c)
|
51
|
+
end
|
52
|
+
|
53
|
+
def determine_visibility(comp, c)
|
54
|
+
visible = true
|
55
|
+
found = false
|
56
|
+
|
57
|
+
c
|
58
|
+
.metadata
|
59
|
+
.data[:layer_comp]['layerSettings'].each do |l|
|
60
|
+
visible = l['enab'] if l.has_key?('enab')
|
61
|
+
found = true and break if l['compList'].include?(comp[:id])
|
62
|
+
end
|
63
|
+
|
64
|
+
found && visible
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_position(comp, c)
|
68
|
+
offset = determine_position_offset(comp, c)
|
69
|
+
|
70
|
+
c.left_offset = offset[:x]
|
71
|
+
c.top_offset = offset[:y]
|
72
|
+
end
|
73
|
+
|
74
|
+
def determine_position_offset(comp, c)
|
75
|
+
x = 0
|
76
|
+
y = 0
|
77
|
+
|
78
|
+
c
|
79
|
+
.metadata
|
80
|
+
.data[:layer_comp]['layerSettings'].each do |l|
|
81
|
+
if l.has_key?('Ofst')
|
82
|
+
x = l['Ofst']['Hrzn']
|
83
|
+
y = l['Ofst']['Vrtc']
|
84
|
+
end
|
85
|
+
|
86
|
+
break if l['compList'].include?(comp[:id])
|
87
|
+
end
|
88
|
+
|
89
|
+
{ x: x, y: y }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class PSD
|
2
|
+
module Node
|
3
|
+
# Locking is inherited by descendants, but Photoshop doesn't reflect
|
4
|
+
# this directly in the file, so we have to recursively traverse the
|
5
|
+
# ancestry tree to make sure an ancestor isn't locked.
|
6
|
+
module Locking
|
7
|
+
def all_locked?
|
8
|
+
return true if layer.all_locked?
|
9
|
+
return false if parent.root?
|
10
|
+
return parent.all_locked?
|
11
|
+
end
|
12
|
+
|
13
|
+
def any_locked?
|
14
|
+
position_locked? || composite_locked? || transparency_locked?
|
15
|
+
end
|
16
|
+
|
17
|
+
def position_locked?
|
18
|
+
return true if layer.position_locked?
|
19
|
+
return false if parent.root?
|
20
|
+
return parent.position_locked?
|
21
|
+
end
|
22
|
+
|
23
|
+
def composite_locked?
|
24
|
+
return true if layer.composite_locked?
|
25
|
+
return false if parent.root?
|
26
|
+
return parent.composite_locked?
|
27
|
+
end
|
28
|
+
|
29
|
+
def transparency_locked?
|
30
|
+
return true if layer.transparency_locked?
|
31
|
+
return false if parent.root?
|
32
|
+
return parent.transparency_locked?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'psd/node'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
module Node
|
5
|
+
# Represents the root node of a Photoshop document
|
6
|
+
class Root < PSD::Node::Base
|
7
|
+
attr_accessor :children
|
8
|
+
attr_reader :psd
|
9
|
+
|
10
|
+
alias_method :document_width, :width
|
11
|
+
alias_method :document_height, :height
|
12
|
+
|
13
|
+
RootLayer = Struct.new("RootLayer", :node, *Base::PROPERTIES)
|
14
|
+
|
15
|
+
def self.layer_for_psd(psd)
|
16
|
+
RootLayer.new.tap do |layer|
|
17
|
+
layer.top = 0
|
18
|
+
layer.left = 0
|
19
|
+
layer.right = psd.header.width.to_i
|
20
|
+
layer.bottom = psd.header.height.to_i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Stores a reference to the parsed PSD and builds the
|
25
|
+
# tree hierarchy.
|
26
|
+
def initialize(psd)
|
27
|
+
super self.class.layer_for_psd(psd)
|
28
|
+
|
29
|
+
@psd = psd
|
30
|
+
build_hierarchy
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the width and height of the entire PSD document.
|
34
|
+
def document_dimensions
|
35
|
+
[document_width, document_height]
|
36
|
+
end
|
37
|
+
|
38
|
+
# The depth of the root node is always 0.
|
39
|
+
def depth
|
40
|
+
0
|
41
|
+
end
|
42
|
+
|
43
|
+
def opacity; 255; end
|
44
|
+
def fill_opacity; 255; end
|
45
|
+
|
46
|
+
# Recursively exports the hierarchy to a Hash
|
47
|
+
def to_hash
|
48
|
+
{
|
49
|
+
children: children.map(&:to_hash),
|
50
|
+
document: {
|
51
|
+
width: document_width,
|
52
|
+
height: document_height,
|
53
|
+
depth: psd.header.depth,
|
54
|
+
resources: {
|
55
|
+
layer_comps: @psd.layer_comps,
|
56
|
+
guides: @psd.guides,
|
57
|
+
slices: @psd.slices.map(&:to_hash)
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def build_hierarchy
|
66
|
+
current_group = self
|
67
|
+
parse_stack = []
|
68
|
+
|
69
|
+
# First we build the hierarchy
|
70
|
+
@psd.layers.each do |layer|
|
71
|
+
if layer.folder?
|
72
|
+
parse_stack.push current_group
|
73
|
+
current_group = PSD::Node::Group.new(layer, parse_stack.last)
|
74
|
+
elsif layer.folder_end?
|
75
|
+
parent = parse_stack.pop
|
76
|
+
parent.children.push current_group
|
77
|
+
current_group = parent
|
78
|
+
else
|
79
|
+
current_group.children.push PSD::Node::Layer.new(layer, current_group)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
update_dimensions!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/psd/nodes/search.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
class PSD
|
2
|
-
|
2
|
+
module Node
|
3
3
|
module Search
|
4
|
+
def find_by_id(id)
|
5
|
+
subtree.select { |n| n.id == id }.first
|
6
|
+
end
|
7
|
+
|
4
8
|
# Searches the tree structure for a node at the given path. The path is
|
5
9
|
# defined by the layer/folder names. Because the PSD format does not
|
6
10
|
# require unique layer/folder names, we always return an array of all
|
@@ -8,6 +12,7 @@ class PSD
|
|
8
12
|
def children_at_path(path, opts={})
|
9
13
|
path = path.split('/').delete_if { |p| p == "" } unless path.is_a?(Array)
|
10
14
|
|
15
|
+
path = path.dup
|
11
16
|
query = path.shift
|
12
17
|
matches = children.select do |c|
|
13
18
|
if opts[:case_sensitive]
|
@@ -20,72 +25,10 @@ class PSD
|
|
20
25
|
if path.length == 0
|
21
26
|
return matches
|
22
27
|
else
|
23
|
-
return matches.map { |m| m.children_at_path(path, opts) }.flatten
|
28
|
+
return matches.map { |m| m.children_at_path(path.dup, opts) }.flatten
|
24
29
|
end
|
25
30
|
end
|
26
31
|
alias :children_with_path :children_at_path
|
27
|
-
|
28
|
-
# Given a layer comp ID, name, or :last for last document state, create a new
|
29
|
-
# tree with layer/group visibility altered based on the layer comp.
|
30
|
-
def filter_by_comp(id)
|
31
|
-
if id.is_a?(String)
|
32
|
-
comp = psd.layer_comps.select { |c| c[:name] == id }.first
|
33
|
-
raise "Layer comp not found" if comp.nil?
|
34
|
-
|
35
|
-
id = comp[:id]
|
36
|
-
else
|
37
|
-
comp = psd.layer_comps.select { |c| c[:id] == id }.first
|
38
|
-
raise "Layer comp not found" if comp.nil?
|
39
|
-
end
|
40
|
-
|
41
|
-
root = PSD::Node::Root.new(psd)
|
42
|
-
filter_for_comp!(comp, root)
|
43
|
-
|
44
|
-
return root
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def filter_for_comp!(comp, node)
|
50
|
-
# Force layers to be visible if they are enabled for the comp
|
51
|
-
node.children.each do |c|
|
52
|
-
set_visibility(comp, c) if Resource::Section::LayerComps.visibility_captured?(comp)
|
53
|
-
set_position(comp, c) if Resource::Section::LayerComps.position_captured?(comp)
|
54
|
-
|
55
|
-
filter_for_comp!(comp, c) if c.group?
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def set_visibility(comp, c)
|
60
|
-
visible = true
|
61
|
-
|
62
|
-
c
|
63
|
-
.metadata
|
64
|
-
.data[:layer_comp]['layerSettings'].each do |l|
|
65
|
-
visible = l['enab'] if l.has_key?('enab')
|
66
|
-
break if l['compList'].include?(comp[:id])
|
67
|
-
end
|
68
|
-
|
69
|
-
c.force_visible = visible
|
70
|
-
end
|
71
|
-
|
72
|
-
def set_position(comp, c)
|
73
|
-
x = 0
|
74
|
-
y = 0
|
75
|
-
|
76
|
-
c
|
77
|
-
.metadata
|
78
|
-
.data[:layer_comp]['layerSettings'].each do |l|
|
79
|
-
next unless l.has_key?('Ofst')
|
80
|
-
|
81
|
-
x = l['Ofst']['Hrzn']
|
82
|
-
y = l['Ofst']['Vrtc']
|
83
|
-
break if l['compList'].include?(comp[:id])
|
84
|
-
end
|
85
|
-
|
86
|
-
c.left += x
|
87
|
-
c.top += y
|
88
|
-
end
|
89
32
|
end
|
90
33
|
end
|
91
|
-
end
|
34
|
+
end
|