psd 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 07d04a8de80525605f55cc1346b195d159448da4
4
- data.tar.gz: 0eb80cec71da604ffa67e727ad90377b881dd028
3
+ metadata.gz: 448518141d3b745904f809280d0e826e08bf8dc0
4
+ data.tar.gz: 4c0725157152e2f924b95cf023603a9b86bd1f54
5
5
  SHA512:
6
- metadata.gz: 5dab1760c194b6abffe95ee77d061987dfb1d0cf8e7c59375d2f78726c912d36ef786fd5e240d7305d8edefe2f2650fea1546bdfc55b41ce2620d142383ed251
7
- data.tar.gz: e04a6725116b6be409215c47fb1d208c7aaacdb4959d84cbc9390d43cd9f2f96378858ac31880be427a02ada478320523a7cec37923bdc7b914d15f7e9d47197
6
+ metadata.gz: e559032f4b160eb26451c38aa4bc3fa7869aa6984f60c5d062596087cf32a38fdc181e3c703a56b22ba88fc965abe727fa158eb427f76042e46c55f694593ab3
7
+ data.tar.gz: 4e31eb123c902d3381e3895d262589f8d269aca4e30c8a74f541d03ef6a9e527aefc9c895df5e358f62e5e8f5b584e4a6a5070b4cf83a10892c629ba46550d96
data/lib/psd.rb CHANGED
@@ -9,7 +9,8 @@ dir_root = File.dirname(File.absolute_path(__FILE__)) + '/psd'
9
9
  '/image_modes/*',
10
10
  '/image_exports/*',
11
11
  '/nodes/*',
12
- '/layer_info/**/*',
12
+ '/layer_info/*',
13
+ '/layer/*',
13
14
  '/**/*'
14
15
  ].each do |path|
15
16
  Dir.glob(dir_root + path) { |file| require file if File.file?(file) }
@@ -3,38 +3,24 @@ class PSD
3
3
  # that layer.
4
4
  class Layer
5
5
  include Section
6
-
7
- attr_reader :id, :mask, :blending_ranges, :adjustments, :channels_info
8
- attr_reader :blend_mode, :layer_type, :blending_mode, :opacity, :fill_opacity
9
- attr_reader :channels, :image
10
-
11
- attr_accessor :group_layer
12
- attr_accessor :top, :left, :bottom, :right, :rows, :cols, :ref_x, :ref_y, :node, :file
13
-
14
- alias :info :adjustments
15
- alias :width :cols
16
- alias :height :rows
17
-
18
- # All of the extra layer info sections that we know how to parse.
19
- LAYER_INFO = {
20
- type: TypeTool,
21
- legacy_type: LegacyTypeTool,
22
- metadata: MetadataSetting,
23
- layer_name_source: LayerNameSource,
24
- object_effects: ObjectEffects,
25
- name: UnicodeName,
26
- section_divider: LayerSectionDivider,
27
- reference_point: ReferencePoint,
28
- layer_id: LayerID,
29
- fill_opacity: FillOpacity,
30
- placed_layer: PlacedLayer,
31
- vector_mask: VectorMask
32
- }
6
+ include BlendModes
7
+ include BlendingRanges
8
+ include ChannelImage
9
+ include Exporting
10
+ include Helpers
11
+ include Info
12
+ include Mask
13
+ include Name
14
+ include PathComponents
15
+ include PositionAndChannels
16
+
17
+ attr_reader :id, :info_keys
18
+ attr_accessor :group_layer, :node, :file
33
19
 
34
20
  # Initializes all of the defaults for the layer.
35
21
  def initialize(file)
36
22
  @file = file
37
- @image = nil
23
+
38
24
  @mask = {}
39
25
  @blending_ranges = {}
40
26
  @adjustments = {}
@@ -42,10 +28,8 @@ class PSD
42
28
  @blend_mode = {}
43
29
  @group_layer = nil
44
30
 
45
- @layer_type = 'normal'
46
31
  @blending_mode = 'normal'
47
32
  @opacity = 255
48
- @fill_opacity = 255
49
33
 
50
34
  # Just used for tracking which layer adjustments we're parsing.
51
35
  # Not essential.
@@ -56,9 +40,9 @@ class PSD
56
40
  def parse(index=nil)
57
41
  start_section
58
42
 
59
- @idx = index
43
+ @id = index
60
44
 
61
- parse_info
45
+ parse_position_and_channels
62
46
  parse_blend_modes
63
47
 
64
48
  extra_len = @file.read_int
@@ -67,7 +51,7 @@ class PSD
67
51
  parse_mask_data
68
52
  parse_blending_ranges
69
53
  parse_legacy_layer_name
70
- parse_extra_data
54
+ parse_layer_info
71
55
 
72
56
  PSD.logger.debug "Layer name = #{name}"
73
57
 
@@ -77,278 +61,16 @@ class PSD
77
61
  return self
78
62
  end
79
63
 
80
- # Export the layer to file. May or may not work.
81
- def export(outfile)
82
- export_info(outfile)
83
-
84
- @blend_mode.write(outfile)
85
- @file.seek(@blend_mode.num_bytes, IO::SEEK_CUR)
86
-
87
- export_mask_data(outfile)
88
- export_blending_ranges(outfile)
89
- export_legacy_layer_name(outfile)
90
- export_extra_data(outfile)
91
-
92
- outfile.write @file.read(end_of_section - @file.tell)
93
- end
94
-
95
64
  # We just delegate this to a normal method call.
96
65
  def [](val)
97
66
  self.send(val)
98
67
  end
99
68
 
100
- def parse_channel_image!(header, parse)
101
- @image = ChannelImage.new(@file, header, self)
102
- parse ? @image.parse : @image.skip
103
- end
104
-
105
- # Does this layer represent the start of a folder section?
106
- def folder?
107
- return false unless @adjustments.has_key?(:section_divider)
108
- @adjustments[:section_divider].is_folder
109
- end
110
-
111
- # Does this layer represent the end of a folder section?
112
- def folder_end?
113
- return false unless @adjustments.has_key?(:section_divider)
114
- @adjustments[:section_divider].is_hidden
115
- end
116
-
117
- # Is this layer visible?
118
- def visible?
119
- @visible
120
- end
121
-
122
- # Is this layer hidden?
123
- def hidden?
124
- !@visible
125
- end
126
-
127
- # Attempt to translate this layer and modify the document.
128
- def translate(x=0, y=0)
129
- @left += x
130
- @right += x
131
- @top += y
132
- @bottom += y
133
-
134
- @path_components.each{ |p| p.translate(x,y) } if @path_components
135
- end
136
-
137
- # Attempt to scale the path components within this layer.
138
- def scale_path_components(xr, yr)
139
- return unless @path_components
140
-
141
- @path_components.each{ |p| p.scale(xr, yr) }
142
- end
143
-
144
- # Helper that exports the text data in this layer, if any.
145
- def text
146
- return nil unless @adjustments[:type]
147
- @adjustments[:type].to_hash
148
- end
149
-
150
- # Gets the name of this layer. If the PSD file is from an even remotely
151
- # recent version of Photoshop, this data is stored as extra layer info and
152
- # as a UTF-16 name. Otherwise, it's stored in a legacy block.
153
- def name
154
- if @adjustments.has_key?(:name)
155
- return @adjustments[:name].data
156
- end
157
-
158
- return @legacy_name
159
- end
160
-
161
69
  # We delegate all missing method calls to the extra layer info to make it easier
162
70
  # to access that data.
163
71
  def method_missing(method, *args, &block)
164
72
  return @adjustments[method] if @adjustments.has_key?(method)
165
73
  super
166
74
  end
167
-
168
- private
169
-
170
- def parse_info
171
- start_section(:info)
172
-
173
- @top = @file.read_int
174
- @left = @file.read_int
175
- @bottom = @file.read_int
176
- @right = @file.read_int
177
- @channels = @file.read_short
178
-
179
- @rows = @bottom - @top
180
- @cols = @right - @left
181
-
182
- @channels.times do
183
- channel_id = @file.read_short
184
- channel_length = @file.read_int
185
-
186
- @channels_info << {id: channel_id, length: channel_length}
187
- end
188
-
189
- end_section(:info)
190
- end
191
-
192
- def export_info(outfile)
193
- [@top, @left, @bottom, @right].each { |val| outfile.write_int(val) }
194
- outfile.write_short(@channels)
195
-
196
- @channels_info.each do |channel_info|
197
- outfile.write_short channel_info[:id]
198
- outfile.write_int channel_info[:length]
199
- end
200
-
201
- @file.seek end_of_section(:info)
202
- end
203
-
204
- def export_mask_data(outfile)
205
- outfile.write @file.read(@mask_end - @mask_begin + 4)
206
- end
207
-
208
- def export_blending_ranges(outfile)
209
- length = 4 * 2 # greys
210
- length += @blending_ranges[:num_channels] * 8
211
- outfile.write_int length
212
-
213
- outfile.write_short @blending_ranges[:grey][:source][:black]
214
- outfile.write_short @blending_ranges[:grey][:source][:white]
215
- outfile.write_short @blending_ranges[:grey][:dest][:black]
216
- outfile.write_short @blending_ranges[:grey][:dest][:white]
217
-
218
- @blending_ranges[:num_channels].times do |i|
219
- outfile.write_short @blending_ranges[:channels][i][:source][:black]
220
- outfile.write_short @blending_ranges[:channels][i][:source][:white]
221
- outfile.write_short @blending_ranges[:channels][i][:dest][:black]
222
- outfile.write_short @blending_ranges[:channels][i][:dest][:white]
223
- end
224
-
225
- @file.seek length + 4, IO::SEEK_CUR
226
- end
227
-
228
- def export_legacy_layer_name(outfile)
229
- outfile.write @file.read(@legacy_name_end - @legacy_name_start)
230
- end
231
-
232
- def export_extra_data(outfile)
233
- outfile.write @file.read(@extra_data_end - @extra_data_begin)
234
- if @path_components && !@path_components.empty?
235
- outfile.seek @vector_mask_begin
236
- @file.seek @vector_mask_begin
237
-
238
- write_vector_mask(outfile)
239
- @file.seek outfile.tell
240
- end
241
- end
242
-
243
- def parse_blend_modes
244
- @blend_mode = BlendMode.read(@file)
245
-
246
- @blending_mode = @blend_mode.mode
247
- @opacity = @blend_mode.opacity
248
- @visible = @blend_mode.visible
249
- end
250
-
251
- def parse_mask_data
252
- @mask_begin = @file.tell
253
- @mask = Mask.read(@file)
254
- @mask_end = @file.tell
255
- end
256
-
257
- def parse_blending_ranges
258
- length = @file.read_int
259
-
260
- @blending_ranges[:grey] = {
261
- source: {
262
- black: @file.read_short,
263
- white: @file.read_short
264
- },
265
- dest: {
266
- black: @file.read_short,
267
- white: @file.read_short
268
- }
269
- }
270
-
271
- @blending_ranges[:num_channels] = (length - 8) / 8
272
-
273
- @blending_ranges[:channels] = []
274
- @blending_ranges[:num_channels].times do
275
- @blending_ranges[:channels] << {
276
- source: {
277
- black: @file.read_short,
278
- white: @file.read_short
279
- },
280
- dest: {
281
- black: @file.read_short,
282
- white: @file.read_short
283
- }
284
- }
285
- end
286
- end
287
-
288
- # The old school layer names are encoded in MacRoman format,
289
- # not UTF-8. Luckily Ruby kicks ass at character conversion.
290
- def parse_legacy_layer_name
291
- @legacy_name_start = @file.tell
292
-
293
- len = Util.pad4 @file.read(1).bytes.to_a[0]
294
- @legacy_name = @file.read_string(len)
295
-
296
- @legacy_name_end = @file.tell
297
- end
298
-
299
- # This section is a bit tricky to parse because it represents all of the
300
- # extra data that describes this layer.
301
- def parse_extra_data
302
- @extra_data_begin = @file.tell
303
-
304
- while @file.tell < @layer_end
305
- # Signature, don't need
306
- @file.seek 4, IO::SEEK_CUR
307
-
308
- # Key, very important
309
- key = @file.read_string(4)
310
- @info_keys << key
311
-
312
- length = Util.pad2 @file.read_int
313
- pos = @file.tell
314
-
315
- info_parsed = false
316
- LAYER_INFO.each do |name, info|
317
- next unless info.key == key
318
-
319
- PSD.logger.debug "Layer Info: key = #{key}, start = #{pos}, length = #{length}"
320
-
321
- begin
322
- i = info.new(@file, length)
323
- i.parse
324
-
325
- @adjustments[name] = i
326
- info_parsed = true
327
- rescue Exception => e
328
- PSD.logger.error "Parsing error: key = #{key}, message = #{e.message}"
329
- PSD.logger.error e.backtrace.join("\n")
330
- end
331
-
332
- break
333
- end
334
-
335
- if !info_parsed
336
- PSD.logger.debug "Skipping: key = #{key}, pos = #{@file.tell}, length = #{length}"
337
- @file.seek pos + length
338
- end
339
-
340
- @file.seek pos + length if @file.tell != (pos + length)
341
- end
342
-
343
- @extra_data_end = @file.tell
344
- end
345
-
346
- def write_vector_mask(outfile)
347
- outfile.write @file.read(8)
348
- # outfile.write_int 3
349
- # outfile.write_int @vector_tag
350
-
351
- @path_components.each{ |pc| pc.write(outfile); @file.seek(26, IO::SEEK_CUR) }
352
- end
353
75
  end
354
76
  end
@@ -0,0 +1,17 @@
1
+ class PSD
2
+ class Layer
3
+ module BlendModes
4
+ attr_reader :blend_mode, :blending_mode, :opacity
5
+
6
+ private
7
+
8
+ def parse_blend_modes
9
+ @blend_mode = BlendMode.read(@file)
10
+
11
+ @blending_mode = @blend_mode.mode
12
+ @opacity = @blend_mode.opacity
13
+ @visible = @blend_mode.visible
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ class PSD
2
+ class Layer
3
+ module BlendingRanges
4
+ attr_reader :blending_ranges
5
+
6
+ private
7
+
8
+ def parse_blending_ranges
9
+ length = @file.read_int
10
+
11
+ @blending_ranges[:grey] = {
12
+ source: {
13
+ black: @file.read_short,
14
+ white: @file.read_short
15
+ },
16
+ dest: {
17
+ black: @file.read_short,
18
+ white: @file.read_short
19
+ }
20
+ }
21
+
22
+ @blending_ranges[:num_channels] = (length - 8) / 8
23
+
24
+ @blending_ranges[:channels] = []
25
+ @blending_ranges[:num_channels].times do
26
+ @blending_ranges[:channels] << {
27
+ source: {
28
+ black: @file.read_short,
29
+ white: @file.read_short
30
+ },
31
+ dest: {
32
+ black: @file.read_short,
33
+ white: @file.read_short
34
+ }
35
+ }
36
+ end
37
+ end
38
+
39
+ def export_blending_ranges(outfile)
40
+ length = 4 * 2 # greys
41
+ length += @blending_ranges[:num_channels] * 8
42
+ outfile.write_int length
43
+
44
+ outfile.write_short @blending_ranges[:grey][:source][:black]
45
+ outfile.write_short @blending_ranges[:grey][:source][:white]
46
+ outfile.write_short @blending_ranges[:grey][:dest][:black]
47
+ outfile.write_short @blending_ranges[:grey][:dest][:white]
48
+
49
+ @blending_ranges[:num_channels].times do |i|
50
+ outfile.write_short @blending_ranges[:channels][i][:source][:black]
51
+ outfile.write_short @blending_ranges[:channels][i][:source][:white]
52
+ outfile.write_short @blending_ranges[:channels][i][:dest][:black]
53
+ outfile.write_short @blending_ranges[:channels][i][:dest][:white]
54
+ end
55
+
56
+ @file.seek length + 4, IO::SEEK_CUR
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,12 @@
1
+ class PSD
2
+ class Layer
3
+ module ChannelImage
4
+ attr_reader :image
5
+
6
+ def parse_channel_image(header, parse)
7
+ @image = PSD::ChannelImage.new(@file, header, self)
8
+ parse ? @image.parse : @image.skip
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ class PSD
2
+ class Layer
3
+ module Exporting
4
+ # Export the layer to file. May or may not work.
5
+ def export(outfile)
6
+ export_position_and_channels(outfile)
7
+
8
+ @blend_mode.write(outfile)
9
+ @file.seek(@blend_mode.num_bytes, IO::SEEK_CUR)
10
+
11
+ export_mask_data(outfile)
12
+ export_blending_ranges(outfile)
13
+ export_legacy_layer_name(outfile)
14
+ export_extra_data(outfile)
15
+
16
+ outfile.write @file.read(end_of_section - @file.tell)
17
+ end
18
+
19
+ def write_vector_mask(outfile)
20
+ outfile.write @file.read(8)
21
+ # outfile.write_int 3
22
+ # outfile.write_int @vector_tag
23
+
24
+ @path_components.each{ |pc| pc.write(outfile); @file.seek(26, IO::SEEK_CUR) }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ class PSD
2
+ class Layer
3
+ module Helpers
4
+ # Does this layer represent the start of a folder section?
5
+ def folder?
6
+ if info.has_key?(:section_divider)
7
+ info[:section_divider].is_folder
8
+ elsif info.has_key?(:nested_section_divider)
9
+ info[:nested_section_divider].is_folder
10
+ else
11
+ name == "<Layer group>"
12
+ end
13
+ end
14
+
15
+ # Does this layer represent the end of a folder section?
16
+ def folder_end?
17
+ if info.has_key?(:section_divider)
18
+ info[:section_divider].is_hidden
19
+ elsif info.has_key?(:nested_section_divider)
20
+ info[:nested_section_divider].is_hidden
21
+ else
22
+ name == "</Layer group>"
23
+ end
24
+ end
25
+
26
+ # Is this layer visible?
27
+ def visible?
28
+ @visible
29
+ end
30
+
31
+ # Is this layer hidden?
32
+ def hidden?
33
+ !@visible
34
+ end
35
+
36
+ # Helper that exports the text data in this layer, if any.
37
+ def text
38
+ return nil unless info[:type]
39
+ info[:type].to_hash
40
+ end
41
+
42
+ def layer_type
43
+ return 'normal' unless info.has_key?(:section_divider)
44
+ info[:section_divider].layer_type
45
+ end
46
+
47
+ def fill_opacity
48
+ return nil unless info.has_key?(:fill_opacity)
49
+ info[:fill_opacity].enabled
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,88 @@
1
+ class PSD
2
+ class Layer
3
+ module Info
4
+ # All of the extra layer info sections that we know how to parse.
5
+ LAYER_INFO = {
6
+ type: TypeTool,
7
+ legacy_type: LegacyTypeTool,
8
+ metadata: MetadataSetting,
9
+ layer_name_source: LayerNameSource,
10
+ object_effects: ObjectEffects,
11
+ name: UnicodeName,
12
+ section_divider: LayerSectionDivider,
13
+ nested_section_divider: NestedLayerDivider,
14
+ reference_point: ReferencePoint,
15
+ layer_id: LayerID,
16
+ fill_opacity: FillOpacity,
17
+ placed_layer: PlacedLayer,
18
+ vector_mask: VectorMask
19
+ }
20
+
21
+ attr_reader :adjustments
22
+ alias :info :adjustments
23
+
24
+ private
25
+
26
+ # This section is a bit tricky to parse because it represents all of the
27
+ # extra data that describes this layer.
28
+ def parse_layer_info
29
+ @extra_data_begin = @file.tell
30
+
31
+ while @file.tell < @layer_end
32
+ # Signature, don't need
33
+ @file.seek 4, IO::SEEK_CUR
34
+
35
+ # Key, very important
36
+ key = @file.read_string(4)
37
+ @info_keys << key
38
+
39
+ length = Util.pad2 @file.read_int
40
+ pos = @file.tell
41
+
42
+ info_parsed = false
43
+ LAYER_INFO.each do |name, info|
44
+ next unless info.key == key
45
+
46
+ PSD.logger.debug "Layer Info: key = #{key}, start = #{pos}, length = #{length}"
47
+
48
+ begin
49
+ i = info.new(@file, length)
50
+ i.parse
51
+
52
+ @adjustments[name] = i
53
+ info_parsed = true
54
+ rescue Exception => e
55
+ PSD.logger.error "Parsing error: key = #{key}, message = #{e.message}"
56
+ PSD.logger.error e.backtrace.join("\n")
57
+ end
58
+
59
+ break
60
+ end
61
+
62
+ if !info_parsed
63
+ PSD.logger.debug "Skipping: key = #{key}, pos = #{@file.tell}, length = #{length}"
64
+ @file.seek pos + length
65
+ end
66
+
67
+ if @file.tell != (pos + length)
68
+ PSD.logger.warn "Layer info key #{key} ended at #{@file.tell}, expected #{pos + length}"
69
+ @file.seek pos + length
70
+ end
71
+ end
72
+
73
+ @extra_data_end = @file.tell
74
+ end
75
+
76
+ def export_extra_data(outfile)
77
+ outfile.write @file.read(@extra_data_end - @extra_data_begin)
78
+ if @path_components && !@path_components.empty?
79
+ outfile.seek @vector_mask_begin
80
+ @file.seek @vector_mask_begin
81
+
82
+ write_vector_mask(outfile)
83
+ @file.seek outfile.tell
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,19 @@
1
+ class PSD
2
+ class Layer
3
+ module Mask
4
+ attr_reader :mask
5
+
6
+ private
7
+
8
+ def parse_mask_data
9
+ @mask_begin = @file.tell
10
+ @mask = PSD::Mask.read(@file)
11
+ @mask_end = @file.tell
12
+ end
13
+
14
+ def export_mask_data(outfile)
15
+ outfile.write @file.read(@mask_end - @mask_begin + 4)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ class PSD
2
+ class Layer
3
+ module Name
4
+ # Gets the name of this layer. If the PSD file is from an even remotely
5
+ # recent version of Photoshop, this data is stored as extra layer info and
6
+ # as a UTF-16 name. Otherwise, it's stored in a legacy block.
7
+ def name
8
+ if @adjustments.has_key?(:name)
9
+ return @adjustments[:name].data
10
+ end
11
+
12
+ return @legacy_name
13
+ end
14
+
15
+ private
16
+
17
+ # The old school layer names are encoded in MacRoman format,
18
+ # not UTF-8. Luckily Ruby kicks ass at character conversion.
19
+ def parse_legacy_layer_name
20
+ @legacy_name_start = @file.tell
21
+
22
+ len = Util.pad4 @file.read(1).bytes.to_a[0]
23
+ @legacy_name = @file.read_string(len)
24
+
25
+ @legacy_name_end = @file.tell
26
+ end
27
+
28
+ def export_legacy_layer_name(outfile)
29
+ outfile.write @file.read(@legacy_name_end - @legacy_name_start)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ class PSD
2
+ class Layer
3
+ module PathComponents
4
+ # Attempt to translate this layer and modify the document.
5
+ def translate(x=0, y=0)
6
+ @left += x
7
+ @right += x
8
+ @top += y
9
+ @bottom += y
10
+
11
+ @path_components.each{ |p| p.translate(x,y) } if @path_components
12
+ end
13
+
14
+ # Attempt to scale the path components within this layer.
15
+ def scale_path_components(xr, yr)
16
+ return unless @path_components
17
+
18
+ @path_components.each{ |p| p.scale(xr, yr) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ class PSD
2
+ class Layer
3
+ module PositionAndChannels
4
+ attr_reader :top, :left, :bottom, :right, :cols, :rows
5
+ attr_reader :channels, :channels_info
6
+
7
+ alias :width :cols
8
+ alias :height :rows
9
+
10
+ private
11
+
12
+ def parse_position_and_channels
13
+ start_section(:info)
14
+
15
+ @top = @file.read_int
16
+ @left = @file.read_int
17
+ @bottom = @file.read_int
18
+ @right = @file.read_int
19
+ @channels = @file.read_short
20
+
21
+ @rows = @bottom - @top
22
+ @cols = @right - @left
23
+
24
+ @channels.times do
25
+ channel_id = @file.read_short
26
+ channel_length = @file.read_int
27
+
28
+ @channels_info << {id: channel_id, length: channel_length}
29
+ end
30
+
31
+ end_section(:info)
32
+ end
33
+
34
+ def export_position_and_channels(outfile)
35
+ [@top, @left, @bottom, @right].each { |val| outfile.write_int(val) }
36
+ outfile.write_short(@channels)
37
+
38
+ @channels_info.each do |channel_info|
39
+ outfile.write_short channel_info[:id]
40
+ outfile.write_int channel_info[:length]
41
+ end
42
+
43
+ @file.seek end_of_section(:info)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../layer_info'
2
+
3
+ class PSD
4
+ # Not 100% sure what the purpose of this key is, but it seems to exist
5
+ # whenever the lsct key doesn't. Parsing this like a layer section
6
+ # divider seems to solve a lot of parsing issues with folders.
7
+ #
8
+ # See https://github.com/layervault/psd.rb/issues/38
9
+ class NestedLayerDivider < LayerInfo
10
+ @key = 'lsdk'
11
+
12
+ attr_reader :is_folder, :is_hidden
13
+
14
+ def initialize(file, length)
15
+ super
16
+
17
+ @is_folder = false
18
+ @is_hidden = false
19
+ end
20
+
21
+ def parse
22
+ code = @file.read_int
23
+
24
+ case code
25
+ when 1, 2 then @is_folder = true
26
+ when 3 then @is_hidden = true
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,7 +4,7 @@ class PSD
4
4
  class LayerSectionDivider < LayerInfo
5
5
  @key = 'lsct'
6
6
 
7
- attr_reader :layer_type, :is_folder, :is_hidden
7
+ attr_reader :layer_type, :is_folder, :is_hidden, :blend_mode, :sub_type
8
8
 
9
9
  SECTION_DIVIDER_TYPES = [
10
10
  "other",
@@ -18,6 +18,8 @@ class PSD
18
18
 
19
19
  @is_folder = false
20
20
  @is_hidden = false
21
+ @blend_mode = nil
22
+ @sub_type = nil
21
23
  end
22
24
 
23
25
  def parse
@@ -29,6 +31,17 @@ class PSD
29
31
  when 3 then @is_hidden = true
30
32
  end
31
33
 
34
+ PSD.logger.warn "Section divider is unexpected value: #{code}" if code > 4
35
+
36
+ return self unless @length >= 12
37
+
38
+ @file.seek 4, IO::SEEK_CUR # sig
39
+ @blend_mode = @file.read_string(4)
40
+
41
+ return self unless @length >= 16
42
+
43
+ @sub_type = @file.read_int == 0 ? 'normal' : 'scene group'
44
+
32
45
  return self
33
46
  end
34
47
  end
@@ -56,7 +56,7 @@ class PSD
56
56
 
57
57
  layers.each do |layer|
58
58
  @file.seek 8, IO::SEEK_CUR and next if layer.folder? || layer.folder_end?
59
- layer.parse_channel_image!(@header, @options[:parse_layer_images])
59
+ layer.parse_channel_image(@header, @options[:parse_layer_images])
60
60
  end
61
61
  end
62
62
 
@@ -1,3 +1,3 @@
1
1
  class PSD
2
- VERSION = "1.3.2"
2
+ VERSION = "1.3.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: psd
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan LeFevre
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-16 00:00:00.000000000 Z
12
+ date: 2013-10-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -159,8 +159,19 @@ files:
159
159
  - lib/psd/image_modes/greyscale.rb
160
160
  - lib/psd/image_modes/rgb.rb
161
161
  - lib/psd/layer.rb
162
+ - lib/psd/layer/blend_modes.rb
163
+ - lib/psd/layer/blending_ranges.rb
164
+ - lib/psd/layer/channel_image.rb
165
+ - lib/psd/layer/exporting.rb
166
+ - lib/psd/layer/helpers.rb
167
+ - lib/psd/layer/info.rb
168
+ - lib/psd/layer/mask.rb
169
+ - lib/psd/layer/name.rb
170
+ - lib/psd/layer/path_components.rb
171
+ - lib/psd/layer/position_and_channels.rb
162
172
  - lib/psd/layer_info.rb
163
173
  - lib/psd/layer_info/fill_opacity.rb
174
+ - lib/psd/layer_info/layer_group.rb
164
175
  - lib/psd/layer_info/layer_id.rb
165
176
  - lib/psd/layer_info/layer_name_source.rb
166
177
  - lib/psd/layer_info/layer_section_divider.rb