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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e01eed6ed75e5db88c644ea838ff2f29d89c9df2
|
4
|
+
data.tar.gz: ca303780610167552c62d169a886595d71b55a6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f69ddfe1cf1ce6f9600151080928539f5529ae94fdbf0cb94c32bc7e4f0b6e2eef8058dc7af1cc9094202181bce10265a67c6fb850f182772aacbc48ca5ff3b5
|
7
|
+
data.tar.gz: cf3f1ae5c8a4c25eeaadc90e93f0d36b479d024fe7da33909a25f2b2b8d00c1ceb1d2d2fe058af67f106781400d29a7e35bdd7da57c7f6fdb4d50a2e35f4eb68
|
data/README.md
CHANGED
@@ -100,7 +100,7 @@ psd.tree.children_at_path("Version A/Matte")
|
|
100
100
|
|
101
101
|
### Layer Comps
|
102
102
|
|
103
|
-
You can also filter nodes based on a layer comp. To generate a new tree
|
103
|
+
You can also filter nodes based on a layer comp. To generate a new tree with layer visibility and position set according to the layer comp data:
|
104
104
|
|
105
105
|
``` ruby
|
106
106
|
# Get information about all the available layer comps
|
data/lib/psd.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require "
|
1
|
+
require 'psd/enginedata'
|
2
|
+
require 'chunky_png'
|
3
|
+
# require "cairo"
|
4
4
|
|
5
|
-
|
5
|
+
require 'active_support/core_ext/module/delegation'
|
6
|
+
require 'active_support/concern'
|
6
7
|
|
7
8
|
dir_root = File.dirname(File.absolute_path(__FILE__)) + '/psd'
|
8
9
|
[
|
@@ -23,7 +24,6 @@ end
|
|
23
24
|
class PSD
|
24
25
|
include Logger
|
25
26
|
include Helpers
|
26
|
-
include NodeExporting
|
27
27
|
|
28
28
|
attr_reader :file, :opts
|
29
29
|
alias :options :opts
|
@@ -86,7 +86,9 @@ class PSD
|
|
86
86
|
def header
|
87
87
|
return @header if @header
|
88
88
|
|
89
|
-
@header = Header.
|
89
|
+
@header = Header.new(@file)
|
90
|
+
@header.parse!
|
91
|
+
|
90
92
|
PSD.logger.debug @header.inspect
|
91
93
|
end
|
92
94
|
|
data/lib/psd/blend_mode.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
class PSD
|
2
|
-
|
3
|
-
|
4
|
-
endian :big
|
5
|
-
|
6
|
-
string :sig, read_length: 4
|
7
|
-
string :blend_key, read_length: 4, trim_value: true
|
8
|
-
uint8 :opacity
|
9
|
-
uint8 :clipping
|
10
|
-
bit8 :flags
|
11
|
-
skip length: 1
|
2
|
+
class BlendMode
|
3
|
+
attr_reader :blend_key, :opacity, :clipping
|
12
4
|
|
13
5
|
# All of the blend modes are stored in the PSD file with a specific key.
|
14
6
|
# This is the mapping of that key to its readable name.
|
@@ -24,57 +16,73 @@ class PSD
|
|
24
16
|
scrn: 'screen',
|
25
17
|
diss: 'dissolve',
|
26
18
|
over: 'overlay',
|
27
|
-
hLit: '
|
28
|
-
sLit: '
|
19
|
+
hLit: 'hard_light',
|
20
|
+
sLit: 'soft_light',
|
29
21
|
diff: 'difference',
|
30
22
|
smud: 'exclusion',
|
31
|
-
div: '
|
32
|
-
idiv: '
|
33
|
-
lbrn: '
|
34
|
-
lddg: '
|
35
|
-
vLit: '
|
36
|
-
lLit: '
|
37
|
-
pLit: '
|
38
|
-
hMix: '
|
23
|
+
div: 'color_dodge',
|
24
|
+
idiv: 'color_burn',
|
25
|
+
lbrn: 'linear_burn',
|
26
|
+
lddg: 'linear_dodge',
|
27
|
+
vLit: 'vivid_light',
|
28
|
+
lLit: 'linear_light',
|
29
|
+
pLit: 'pin_light',
|
30
|
+
hMix: 'hard_mix',
|
39
31
|
pass: 'passthru',
|
40
|
-
dkCl: '
|
41
|
-
lgCl: '
|
32
|
+
dkCl: 'darker_color',
|
33
|
+
lgCl: 'lighter_color',
|
42
34
|
fsub: 'subtract',
|
43
35
|
fdiv: 'divide'
|
44
|
-
}
|
36
|
+
}.freeze
|
45
37
|
|
46
|
-
|
47
|
-
|
48
|
-
|
38
|
+
def initialize(file)
|
39
|
+
@file = file
|
40
|
+
|
41
|
+
@blend_key = nil
|
42
|
+
@opacity = nil
|
43
|
+
@clipping = nil
|
44
|
+
@flags = nil
|
49
45
|
end
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
def parse!
|
48
|
+
@file.seek 4, IO::SEEK_CUR
|
49
|
+
|
50
|
+
@blend_key = @file.read_string(4).strip
|
51
|
+
@opacity = @file.read_byte
|
52
|
+
@clipping = @file.read_byte
|
53
|
+
@flags = @file.read_byte
|
54
|
+
|
55
|
+
@file.seek 1, IO::SEEK_CUR
|
54
56
|
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
+
def mode
|
59
|
+
BLEND_MODES[@blend_key.to_sym]
|
60
|
+
end
|
61
|
+
alias_method :blending_mode, :mode
|
62
|
+
|
58
63
|
def opacity_percentage
|
59
|
-
opacity * 100 / 255
|
64
|
+
@opacity_percentage ||= @opacity * 100 / 255
|
65
|
+
end
|
66
|
+
|
67
|
+
def clipped?
|
68
|
+
@clipping == 1
|
60
69
|
end
|
61
70
|
|
62
71
|
def transparency_protected
|
63
|
-
flags & 0x01
|
72
|
+
@flags & 0x01
|
64
73
|
end
|
65
74
|
|
66
|
-
# Is this layer/folder visible?
|
67
75
|
def visible
|
68
|
-
!((flags & (0x01 << 1)) > 0)
|
76
|
+
!((@flags & (0x01 << 1)) > 0)
|
69
77
|
end
|
70
78
|
|
71
79
|
def obsolete
|
72
|
-
(flags & (0x01 << 2)) > 0
|
80
|
+
(@flags & (0x01 << 2)) > 0
|
73
81
|
end
|
74
82
|
|
75
83
|
def pixel_data_irrelevant
|
76
|
-
return nil unless (flags & (0x01 << 3)) > 0
|
77
|
-
(flags & (0x01 << 4)) > 0
|
84
|
+
return nil unless (@flags & (0x01 << 3)) > 0
|
85
|
+
(@flags & (0x01 << 4)) > 0
|
78
86
|
end
|
79
87
|
end
|
80
88
|
end
|
data/lib/psd/channel_image.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'psd/image'
|
2
2
|
|
3
3
|
class PSD
|
4
4
|
# Represents an image for a single layer, which differs slightly in format from
|
@@ -18,7 +18,7 @@ class PSD
|
|
18
18
|
super(file, header)
|
19
19
|
|
20
20
|
@channels_info = @layer.channels_info
|
21
|
-
@has_mask = @
|
21
|
+
@has_mask = @channels_info.any? { |chan| chan[:id] == -2 }
|
22
22
|
@opacity = @layer.opacity / 255.0
|
23
23
|
@mask_data = []
|
24
24
|
end
|
@@ -47,7 +47,7 @@ class PSD
|
|
47
47
|
|
48
48
|
# If the ID of this current channel is -2, then we assume the dimensions
|
49
49
|
# of the layer mask.
|
50
|
-
if ch_info[:id]
|
50
|
+
if ch_info[:id] < -1
|
51
51
|
@width = @layer.mask.width
|
52
52
|
@height = @layer.mask.height
|
53
53
|
else
|
@@ -97,8 +97,12 @@ class PSD
|
|
97
97
|
def parse_user_mask
|
98
98
|
return unless has_mask?
|
99
99
|
|
100
|
-
|
101
|
-
|
100
|
+
mask_id = -2
|
101
|
+
|
102
|
+
PSD.logger.debug "Using mask in channel #{mask_id}"
|
103
|
+
|
104
|
+
channel = @channels_info.select { |c| c[:id] == mask_id }.first
|
105
|
+
index = @channels_info.index { |c| c[:id] == mask_id }
|
102
106
|
return if channel.nil?
|
103
107
|
|
104
108
|
start = @channel_length * index
|
data/lib/psd/descriptor.rb
CHANGED
@@ -84,13 +84,27 @@ class PSD
|
|
84
84
|
def parse_identifier; @file.read_int; end
|
85
85
|
def parse_index; @file.read_int; end
|
86
86
|
def parse_offset; @file.read_int; end
|
87
|
-
|
87
|
+
|
88
|
+
def parse_property
|
89
|
+
{
|
90
|
+
class: parse_class,
|
91
|
+
id: parse_id
|
92
|
+
}
|
93
|
+
end
|
88
94
|
|
89
|
-
# Discard the first ID becasue it's the same as the key
|
90
|
-
# parsed from the Key/Item. Also, YOLO.
|
91
95
|
def parse_enum
|
92
|
-
|
93
|
-
|
96
|
+
{
|
97
|
+
type: parse_id,
|
98
|
+
value: parse_id
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_enum_reference
|
103
|
+
{
|
104
|
+
class: parse_class,
|
105
|
+
type: parse_id,
|
106
|
+
value: parse_id
|
107
|
+
}
|
94
108
|
end
|
95
109
|
|
96
110
|
def parse_alias
|
@@ -149,18 +163,27 @@ class PSD
|
|
149
163
|
end
|
150
164
|
|
151
165
|
def parse_reference
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
166
|
+
num_items = @file.read_int
|
167
|
+
items = []
|
168
|
+
|
169
|
+
num_items.times do
|
170
|
+
type = @file.read_string(4)
|
171
|
+
PSD.logger.debug "Reference type = #{type}"
|
172
|
+
|
173
|
+
value = case type
|
174
|
+
when 'prop' then parse_property
|
175
|
+
when 'Clss' then parse_class
|
176
|
+
when 'Enmr' then parse_enum_reference
|
177
|
+
when 'Idnt' then parse_identifier
|
178
|
+
when 'indx' then parse_index
|
179
|
+
when 'name' then @file.read_unicode_string
|
180
|
+
when 'rele' then parse_offset
|
181
|
+
end
|
182
|
+
|
183
|
+
items << {type: type, value: value}
|
163
184
|
end
|
185
|
+
|
186
|
+
items
|
164
187
|
end
|
165
188
|
|
166
189
|
def parse_unit_double
|
data/lib/psd/header.rb
CHANGED
@@ -1,22 +1,6 @@
|
|
1
1
|
class PSD
|
2
|
-
|
3
|
-
|
4
|
-
endian :big
|
5
|
-
|
6
|
-
string :sig, read_length: 4
|
7
|
-
uint16 :version
|
8
|
-
|
9
|
-
# Reserved bytes
|
10
|
-
skip length: 6
|
11
|
-
|
12
|
-
uint16 :channels
|
13
|
-
uint32 :rows
|
14
|
-
uint32 :cols
|
15
|
-
uint16 :depth
|
16
|
-
uint16 :mode
|
17
|
-
|
18
|
-
uint32 :color_data_len
|
19
|
-
skip length: :color_data_len
|
2
|
+
class Header
|
3
|
+
attr_reader :sig, :version, :channels, :rows, :cols, :depth, :mode
|
20
4
|
|
21
5
|
# All of the color modes are stored internally as a short from 0-15.
|
22
6
|
# This is a mapping of that value to a human-readable name.
|
@@ -37,25 +21,42 @@ class PSD
|
|
37
21
|
'CMYK64',
|
38
22
|
'DeepMultichannel',
|
39
23
|
'Duotone16'
|
40
|
-
]
|
24
|
+
].freeze
|
41
25
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
26
|
+
alias_method :width, :cols
|
27
|
+
alias_method :height, :rows
|
28
|
+
|
29
|
+
def initialize(file)
|
30
|
+
@file = file
|
31
|
+
|
32
|
+
@sig = nil
|
33
|
+
@version = nil
|
34
|
+
@channels = nil
|
35
|
+
@rows = nil
|
36
|
+
@cols = nil
|
37
|
+
@depth = nil
|
38
|
+
@mode = nil
|
49
39
|
end
|
50
40
|
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
def parse!
|
42
|
+
@sig = @file.read_string(4)
|
43
|
+
@version = @file.read_ushort
|
44
|
+
|
45
|
+
# Reserved bytes, must be 0
|
46
|
+
@file.seek 6, IO::SEEK_CUR
|
47
|
+
|
48
|
+
@channels = @file.read_ushort
|
49
|
+
@rows = @file.read_uint
|
50
|
+
@cols = @file.read_uint
|
51
|
+
@depth = @file.read_ushort
|
52
|
+
@mode = @file.read_ushort
|
53
|
+
|
54
|
+
color_data_len = @file.read_uint
|
55
|
+
@file.seek color_data_len, IO::SEEK_CUR
|
54
56
|
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
rows
|
58
|
+
def mode_name
|
59
|
+
MODES[@mode]
|
59
60
|
end
|
60
61
|
|
61
62
|
def rgb?
|
@@ -35,24 +35,18 @@ class PSD
|
|
35
35
|
finish = @file.tell + byte_count
|
36
36
|
|
37
37
|
while @file.tell < finish
|
38
|
-
len = @file.
|
38
|
+
len = @file.read_byte
|
39
39
|
|
40
40
|
if len < 128
|
41
41
|
len += 1
|
42
|
-
|
43
|
-
@channel_data[k] = @file.read(1).bytes.to_a[0]
|
44
|
-
end
|
45
|
-
|
42
|
+
@channel_data.insert @chan_pos, *@file.read(len).bytes.to_a
|
46
43
|
@chan_pos += len
|
47
44
|
elsif len > 128
|
48
45
|
len ^= 0xff
|
49
46
|
len += 2
|
50
47
|
|
51
|
-
val = @file.read(1).bytes.to_a
|
52
|
-
|
53
|
-
@channel_data[k] = val
|
54
|
-
end
|
55
|
-
|
48
|
+
val = @file.read(1).bytes.to_a
|
49
|
+
@channel_data.insert @chan_pos, *(val * len)
|
56
50
|
@chan_pos += len
|
57
51
|
end
|
58
52
|
end
|
data/lib/psd/image_modes/rgb.rb
CHANGED
@@ -7,16 +7,16 @@ class PSD
|
|
7
7
|
def combine_rgb_channel
|
8
8
|
PSD.logger.debug "Beginning RGB processing"
|
9
9
|
|
10
|
+
rgb_channels = @channels_info.map { |ch| ch[:id] }.reject { |ch| ch < -1 }
|
11
|
+
|
10
12
|
(0...@num_pixels).step(pixel_step) do |i|
|
11
13
|
r = g = b = 0
|
12
14
|
a = 255
|
13
15
|
|
14
|
-
|
15
|
-
next if chan[:id] == -2
|
16
|
-
|
16
|
+
rgb_channels.each_with_index do |chan, index|
|
17
17
|
val = @channel_data[i + (@channel_length * index)]
|
18
18
|
|
19
|
-
case chan
|
19
|
+
case chan
|
20
20
|
when -1 then a = val
|
21
21
|
when 0 then r = val
|
22
22
|
when 1 then g = val
|
data/lib/psd/layer.rb
CHANGED
@@ -2,17 +2,16 @@ class PSD
|
|
2
2
|
# Represents a single layer and all of the data associated with
|
3
3
|
# that layer.
|
4
4
|
class Layer
|
5
|
-
include Section
|
6
5
|
include BlendModes
|
7
6
|
include BlendingRanges
|
8
7
|
include ChannelImage
|
9
8
|
include Exporting
|
10
|
-
include Helpers
|
11
9
|
include Info
|
12
10
|
include Mask
|
13
11
|
include Name
|
14
12
|
include PathComponents
|
15
13
|
include PositionAndChannels
|
14
|
+
include Helpers
|
16
15
|
|
17
16
|
attr_reader :id, :info_keys, :header
|
18
17
|
attr_accessor :group_layer, :node, :file
|
@@ -29,9 +28,6 @@ class PSD
|
|
29
28
|
@blend_mode = {}
|
30
29
|
@group_layer = nil
|
31
30
|
|
32
|
-
@blending_mode = 'normal'
|
33
|
-
@opacity = 255
|
34
|
-
|
35
31
|
# Just used for tracking which layer adjustments we're parsing.
|
36
32
|
# Not essential.
|
37
33
|
@info_keys = []
|
@@ -39,8 +35,6 @@ class PSD
|
|
39
35
|
|
40
36
|
# Parse the layer and all of it's sub-sections.
|
41
37
|
def parse(index=nil)
|
42
|
-
start_section
|
43
|
-
|
44
38
|
@id = index
|
45
39
|
|
46
40
|
parse_position_and_channels
|
@@ -58,7 +52,6 @@ class PSD
|
|
58
52
|
|
59
53
|
@file.seek @layer_end # Skip over any filler zeros
|
60
54
|
|
61
|
-
end_section
|
62
55
|
return self
|
63
56
|
end
|
64
57
|
|
@@ -66,12 +59,5 @@ class PSD
|
|
66
59
|
def [](val)
|
67
60
|
self.send(val)
|
68
61
|
end
|
69
|
-
|
70
|
-
# We delegate all missing method calls to the extra layer info to make it easier
|
71
|
-
# to access that data.
|
72
|
-
def method_missing(method, *args, &block)
|
73
|
-
return @adjustments[method] if @adjustments.has_key?(method)
|
74
|
-
super
|
75
|
-
end
|
76
62
|
end
|
77
63
|
end
|