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
data/lib/psd/layer_mask.rb
CHANGED
@@ -2,8 +2,6 @@ class PSD
|
|
2
2
|
# Covers parsing the global mask and controls parsing of all the
|
3
3
|
# layers/folders in the document.
|
4
4
|
class LayerMask
|
5
|
-
include Section
|
6
|
-
|
7
5
|
attr_reader :layers, :global_mask
|
8
6
|
|
9
7
|
# Store a reference to the file and the header and initialize the defaults.
|
@@ -25,11 +23,8 @@ class PSD
|
|
25
23
|
return self
|
26
24
|
end
|
27
25
|
|
28
|
-
# Parse this section, including all of the layers and folders.
|
29
|
-
# will also trigger parsing of the channel images for each layer.
|
26
|
+
# Parse this section, including all of the layers and folders.
|
30
27
|
def parse
|
31
|
-
start_section
|
32
|
-
|
33
28
|
mask_size = @file.read_int
|
34
29
|
finish = @file.tell + mask_size
|
35
30
|
|
@@ -46,7 +41,7 @@ class PSD
|
|
46
41
|
end
|
47
42
|
|
48
43
|
if layer_count * (18 + 6 * @header.channels) > layer_info_size
|
49
|
-
|
44
|
+
PSD.logger.error "Unlikely number of layers parsed: #{layer_count}"
|
50
45
|
end
|
51
46
|
|
52
47
|
@layer_section_start = @file.tell
|
@@ -67,7 +62,6 @@ class PSD
|
|
67
62
|
|
68
63
|
# Ensure we're at the end of this section
|
69
64
|
@file.seek finish
|
70
|
-
end_section
|
71
65
|
|
72
66
|
return self
|
73
67
|
end
|
data/lib/psd/lazy_execute.rb
CHANGED
@@ -41,7 +41,11 @@ class PSD
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def inspect
|
44
|
-
|
44
|
+
if loaded?
|
45
|
+
@obj.inspect
|
46
|
+
else
|
47
|
+
"<PSD::LazyExecute @obj=#{@obj.class.name}, @pos=#{@start_pos}, @load_method=:#{@load_method}>"
|
48
|
+
end
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|
data/lib/psd/node.rb
CHANGED
@@ -1,70 +1,134 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'psd/nodes/ancestry'
|
2
|
+
require 'psd/nodes/search'
|
3
|
+
require 'psd/nodes/build_preview'
|
3
4
|
|
4
5
|
# Internal structure to help us build trees of a Photoshop documents.
|
5
6
|
# A lot of method names borrowed from the Ruby ancestry gem.
|
6
7
|
class PSD
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
module Node
|
9
|
+
class Base
|
10
|
+
include Enumerable
|
11
|
+
include Ancestry
|
12
|
+
include Search
|
13
|
+
include BuildPreview
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
+
# Default properties that all nodes contain
|
16
|
+
PROPERTIES = [:name, :left, :right, :top, :bottom, :height, :width]
|
15
17
|
|
16
|
-
|
18
|
+
attr_reader :name, :parent
|
19
|
+
attr_accessor :children, :layer, :force_visible, :top_offset, :left_offset
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
delegate :psd, to: :parent
|
22
|
+
delegate :name, to: :layer
|
23
|
+
delegate :each, to: :children
|
24
|
+
delegate :document_dimensions, to: :parent
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
26
|
+
def initialize(layer, parent = nil)
|
27
|
+
@layer = layer
|
28
|
+
@layer.node = self
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
@parent = parent
|
31
|
+
@children = []
|
32
|
+
|
33
|
+
@force_visible = nil
|
34
|
+
@top = @layer.top.to_i
|
35
|
+
@bottom = @layer.bottom.to_i
|
36
|
+
@left = @layer.left.to_i
|
37
|
+
@right = @layer.right.to_i
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
39
|
+
@top_offset = 0
|
40
|
+
@left_offset = 0
|
41
|
+
end
|
34
42
|
|
35
|
-
|
36
|
-
|
37
|
-
|
43
|
+
def top
|
44
|
+
@top + @top_offset
|
45
|
+
end
|
38
46
|
|
39
|
-
|
40
|
-
|
41
|
-
|
47
|
+
def bottom
|
48
|
+
@bottom + @top_offset
|
49
|
+
end
|
42
50
|
|
43
|
-
|
44
|
-
|
45
|
-
|
51
|
+
def left
|
52
|
+
@left + @left_offset
|
53
|
+
end
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
def right
|
56
|
+
@right + @left_offset
|
57
|
+
end
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
visible: visible?,
|
55
|
-
opacity: @layer.opacity / 255.0,
|
56
|
-
blending_mode: @layer.blending_mode
|
57
|
-
}
|
59
|
+
def width
|
60
|
+
right - left
|
61
|
+
end
|
58
62
|
|
59
|
-
|
60
|
-
|
63
|
+
def height
|
64
|
+
bottom - top
|
61
65
|
end
|
62
66
|
|
63
|
-
|
64
|
-
|
67
|
+
def hidden?
|
68
|
+
!visible?
|
69
|
+
end
|
70
|
+
|
71
|
+
def visible?
|
72
|
+
return false if @layer.clipped? && !clipping_mask.visible?
|
73
|
+
@force_visible.nil? ? @layer.visible? : @force_visible
|
74
|
+
end
|
75
|
+
|
76
|
+
def clipping_mask
|
77
|
+
return nil unless @layer.clipped?
|
65
78
|
|
66
|
-
|
67
|
-
|
79
|
+
@clipping_mask ||= (
|
80
|
+
mask_node = next_sibling
|
81
|
+
while mask_node.clipped?
|
82
|
+
mask_node = mask_node.next_sibling
|
83
|
+
end
|
84
|
+
|
85
|
+
mask_node
|
86
|
+
)
|
87
|
+
end
|
88
|
+
alias_method :clipped_by, :clipping_mask
|
89
|
+
|
90
|
+
def layer?
|
91
|
+
is_a?(PSD::Node::Layer)
|
92
|
+
end
|
93
|
+
|
94
|
+
def group?(include_root = true)
|
95
|
+
is_a?(PSD::Node::Group) || (include_root && is_a?(PSD::Node::Root))
|
96
|
+
end
|
97
|
+
|
98
|
+
def debug_name
|
99
|
+
root? ? ":root:" : name
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_hash
|
103
|
+
hash = {
|
104
|
+
type: nil,
|
105
|
+
visible: visible?,
|
106
|
+
opacity: @layer.opacity / 255.0,
|
107
|
+
blending_mode: @layer.blending_mode
|
108
|
+
}
|
109
|
+
|
110
|
+
PROPERTIES.each do |p|
|
111
|
+
hash[p] = self.send(p)
|
112
|
+
end
|
113
|
+
|
114
|
+
hash
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
|
119
|
+
def update_dimensions!
|
120
|
+
return if layer?
|
121
|
+
|
122
|
+
children.each { |child| child.update_dimensions! }
|
123
|
+
|
124
|
+
return if root?
|
125
|
+
|
126
|
+
non_empty_children = children.reject(&:empty?)
|
127
|
+
@left = non_empty_children.map(&:left).min || 0
|
128
|
+
@top = non_empty_children.map(&:top).min || 0
|
129
|
+
@bottom = non_empty_children.map(&:bottom).max || 0
|
130
|
+
@right = non_empty_children.map(&:right).max || 0
|
131
|
+
end
|
68
132
|
end
|
69
133
|
end
|
70
134
|
end
|
data/lib/psd/nodes/ancestry.rb
CHANGED
@@ -1,98 +1,103 @@
|
|
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
|
-
|
6
|
-
def root
|
7
|
-
return self if is_root?
|
8
|
-
return parent.root
|
9
|
-
end
|
5
|
+
extend ActiveSupport::Concern
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
included do
|
8
|
+
# Returns the root node
|
9
|
+
def root
|
10
|
+
return self if is_root?
|
11
|
+
return parent.root
|
12
|
+
end
|
16
13
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
14
|
+
# Is this node the root node?
|
15
|
+
def root?
|
16
|
+
self.is_a?(PSD::Node::Root)
|
17
|
+
end
|
18
|
+
alias :is_root? :root?
|
23
19
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
# Returns all ancestors in the path of this node. This
|
21
|
+
# does NOT return the root node.
|
22
|
+
def ancestors
|
23
|
+
return [] if parent.nil? || parent.is_root?
|
24
|
+
return parent.ancestors + [parent]
|
25
|
+
end
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
# Does this node have any children nodes?
|
28
|
+
def has_children?
|
29
|
+
children.length > 0
|
30
|
+
end
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
parent.children
|
39
|
-
end
|
32
|
+
# Inverse of has_children?
|
33
|
+
def childless?
|
34
|
+
!has_children?
|
35
|
+
end
|
40
36
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
# Returns all sibling nodes including the current node. Can also
|
38
|
+
# be thought of as all children of the parent of this node.
|
39
|
+
def siblings
|
40
|
+
return [] if parent.nil?
|
41
|
+
parent.children
|
42
|
+
end
|
46
43
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
44
|
+
def next_sibling
|
45
|
+
return nil if parent.nil?
|
46
|
+
index = siblings.index(self)
|
47
|
+
siblings[index + 1]
|
48
|
+
end
|
52
49
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
50
|
+
def prev_sibling
|
51
|
+
return nil if parent.nil?
|
52
|
+
index = siblings.index(self)
|
53
|
+
siblings[index - 1]
|
54
|
+
end
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
# Does this node have any siblings?
|
57
|
+
def has_siblings?
|
58
|
+
siblings.length > 1
|
59
|
+
end
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
# Is this node the only descendant of its parent?
|
62
|
+
def only_child?
|
63
|
+
siblings.length == 1
|
64
|
+
end
|
67
65
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
66
|
+
# Recursively get all descendant nodes, not including this node.
|
67
|
+
def descendants
|
68
|
+
children.map(&:subtree).flatten
|
69
|
+
end
|
72
70
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
# Same as descendants, except it includes this node.
|
72
|
+
def subtree
|
73
|
+
[self] + descendants
|
74
|
+
end
|
77
75
|
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
# Depth from the root node. Root depth is 0.
|
77
|
+
def depth
|
78
|
+
return ancestors.length + 1
|
79
|
+
end
|
81
80
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
m = self.respond_to?(test[1]) ? test[1] : "#{test[1]}s"
|
86
|
-
self.send(m).select &method("#{test[2]}_only")
|
87
|
-
else
|
88
|
-
super
|
81
|
+
def path(as_array = false)
|
82
|
+
path = (ancestors.map(&:name) + [name])
|
83
|
+
as_array ? path : path.join('/')
|
89
84
|
end
|
90
|
-
end
|
91
85
|
|
92
|
-
|
86
|
+
def method_missing(method, *args, &block)
|
87
|
+
test = /^(.+)_(layers|groups)$/.match(method)
|
88
|
+
if test
|
89
|
+
m = self.respond_to?(test[1]) ? test[1] : "#{test[1]}s"
|
90
|
+
self.send(m).select &method("#{test[2]}_only")
|
91
|
+
else
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
93
97
|
|
94
|
-
|
95
|
-
|
98
|
+
def layers_only(d); d.layer?; end
|
99
|
+
def groups_only(d); d.group?(false); end
|
100
|
+
end
|
96
101
|
end
|
97
102
|
end
|
98
103
|
end
|