psd 0.3.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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +161 -0
  8. data/Rakefile +1 -0
  9. data/circle.yml +6 -0
  10. data/examples/export.rb +7 -0
  11. data/examples/export_image.rb +12 -0
  12. data/examples/export_text_data.rb +13 -0
  13. data/examples/images/example-cmyk.psd +0 -0
  14. data/examples/images/example-greyscale.psd +0 -0
  15. data/examples/images/example.psd +0 -0
  16. data/examples/images/example16.psd +0 -0
  17. data/examples/parse.rb +31 -0
  18. data/examples/path.rb +7 -0
  19. data/examples/tree.rb +8 -0
  20. data/examples/unimplemented_info.rb +9 -0
  21. data/lib/psd.rb +145 -0
  22. data/lib/psd/blend_mode.rb +75 -0
  23. data/lib/psd/channel_image.rb +9 -0
  24. data/lib/psd/color.rb +125 -0
  25. data/lib/psd/descriptor.rb +172 -0
  26. data/lib/psd/file.rb +99 -0
  27. data/lib/psd/header.rb +61 -0
  28. data/lib/psd/helpers.rb +35 -0
  29. data/lib/psd/image.rb +107 -0
  30. data/lib/psd/image_exports/png.rb +36 -0
  31. data/lib/psd/image_formats/raw.rb +14 -0
  32. data/lib/psd/image_formats/rle.rb +67 -0
  33. data/lib/psd/image_modes/cmyk.rb +27 -0
  34. data/lib/psd/image_modes/greyscale.rb +36 -0
  35. data/lib/psd/image_modes/rgb.rb +25 -0
  36. data/lib/psd/layer.rb +342 -0
  37. data/lib/psd/layer_info.rb +22 -0
  38. data/lib/psd/layer_info/fill_opacity.rb +13 -0
  39. data/lib/psd/layer_info/layer_id.rb +13 -0
  40. data/lib/psd/layer_info/layer_name_source.rb +12 -0
  41. data/lib/psd/layer_info/layer_section_divider.rb +35 -0
  42. data/lib/psd/layer_info/legacy_typetool.rb +88 -0
  43. data/lib/psd/layer_info/object_effects.rb +16 -0
  44. data/lib/psd/layer_info/placed_layer.rb +14 -0
  45. data/lib/psd/layer_info/reference_point.rb +16 -0
  46. data/lib/psd/layer_info/typetool.rb +127 -0
  47. data/lib/psd/layer_info/unicode_name.rb +18 -0
  48. data/lib/psd/layer_info/vector_mask.rb +25 -0
  49. data/lib/psd/layer_mask.rb +106 -0
  50. data/lib/psd/mask.rb +45 -0
  51. data/lib/psd/node.rb +51 -0
  52. data/lib/psd/node_exporting.rb +20 -0
  53. data/lib/psd/node_group.rb +67 -0
  54. data/lib/psd/node_layer.rb +71 -0
  55. data/lib/psd/node_root.rb +78 -0
  56. data/lib/psd/nodes/ancestry.rb +82 -0
  57. data/lib/psd/nodes/has_children.rb +13 -0
  58. data/lib/psd/nodes/lock_to_origin.rb +7 -0
  59. data/lib/psd/nodes/parse_layers.rb +18 -0
  60. data/lib/psd/nodes/search.rb +28 -0
  61. data/lib/psd/pascal_string.rb +14 -0
  62. data/lib/psd/path_record.rb +180 -0
  63. data/lib/psd/resource.rb +27 -0
  64. data/lib/psd/resources.rb +47 -0
  65. data/lib/psd/section.rb +26 -0
  66. data/lib/psd/util.rb +17 -0
  67. data/lib/psd/version.rb +3 -0
  68. data/psd.gemspec +30 -0
  69. data/spec/files/example.psd +0 -0
  70. data/spec/files/one_layer.psd +0 -0
  71. data/spec/files/path.psd +0 -0
  72. data/spec/files/simplest.psd +0 -0
  73. data/spec/files/text.psd +0 -0
  74. data/spec/hierarchy_spec.rb +86 -0
  75. data/spec/identity_spec.rb +34 -0
  76. data/spec/parsing_spec.rb +134 -0
  77. data/spec/spec_helper.rb +13 -0
  78. data/spec/text_spec.rb +12 -0
  79. metadata +231 -0
@@ -0,0 +1,61 @@
1
+ class PSD
2
+ # Describes the Header for the PSD file, which is the first section of the file.
3
+ class Header < BinData::Record
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
20
+
21
+ # All of the color modes are stored internally as a short from 0-15.
22
+ # This is a mapping of that value to a human-readable name.
23
+ MODES = [
24
+ 'Bitmap',
25
+ 'GrayScale',
26
+ 'IndexedColor',
27
+ 'RGBColor',
28
+ 'CMYKColor',
29
+ 'HSLColor',
30
+ 'HSBColor',
31
+ 'Multichannel',
32
+ 'Duotone',
33
+ 'LabColor',
34
+ 'Gray16',
35
+ 'RGB48',
36
+ 'Lab48',
37
+ 'CMYK64',
38
+ 'DeepMultichannel',
39
+ 'Duotone16'
40
+ ]
41
+
42
+ # Get the human-readable color mode name.
43
+ def mode_name
44
+ if mode >= 0 && mode <= 15
45
+ MODES[mode]
46
+ else
47
+ "(#{mode})"
48
+ end
49
+ end
50
+
51
+ # Width of the entire document in pixels.
52
+ def width
53
+ cols
54
+ end
55
+
56
+ # Height of the entire document in pixels.
57
+ def height
58
+ rows
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ class PSD
2
+ # Various helper methods that make accessing PSD data easier since it's
3
+ # split up among various sections.
4
+ module Helpers
5
+ # Width of the entire PSD document, in pixels.
6
+ def width
7
+ header.cols
8
+ end
9
+
10
+ # Height of the entire PSD document, in pixels.
11
+ def height
12
+ header.rows
13
+ end
14
+
15
+ # All of the layers in this document, including section divider layers.
16
+ def layers
17
+ layer_mask.layers
18
+ end
19
+
20
+ # All of the layers, but filters out the section dividers.
21
+ def actual_layers
22
+ layers.delete_if { |l| l.folder? || l.folder_end? }
23
+ end
24
+
25
+ # All of the folders in the document.
26
+ def folders
27
+ layers.select { |l| l.folder? }
28
+ end
29
+
30
+ # Constructs a tree of the current document for easy traversal and data access.
31
+ def tree
32
+ @root ||= PSD::Node::Root.new(self)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,107 @@
1
+ class PSD
2
+ # Parses the full preview image at the end of the PSD document.
3
+ class Image
4
+ include Format::RAW
5
+ include Format::RLE
6
+ include Mode::CMYK
7
+ include Mode::Greyscale
8
+ include Mode::RGB
9
+ include Export::PNG
10
+
11
+ # All of the possible compression formats Photoshop uses.
12
+ COMPRESSIONS = [
13
+ 'Raw',
14
+ 'RLE',
15
+ 'ZIP',
16
+ 'ZIPPrediction'
17
+ ]
18
+
19
+ # Each color channel is represented by a unique ID
20
+ CHANNEL_INFO = [
21
+ {id: 0},
22
+ {id: 1},
23
+ {id: 2},
24
+ {id: -1}
25
+ ]
26
+
27
+ # Store a reference to the file and the header. We also do a few simple calculations
28
+ # to figure out the number of pixels in the image and the length of each channel.
29
+ def initialize(file, header)
30
+ @file = file
31
+ @header = header
32
+
33
+ @num_pixels = width * height
34
+ @num_pixels *= 2 if depth == 16
35
+
36
+ calculate_length
37
+ @channel_data = {} # Using a Hash over an NArray, because NArray has problems w/ Ruby 2.0. Hashes are faster than Arrays
38
+
39
+ @start_pos = @file.tell
40
+ @end_pos = @start_pos + @length
41
+
42
+ @pixel_data = []
43
+ end
44
+
45
+ # Begins parsing the image by first figuring out the compression format used, and then
46
+ # by reading the image data.
47
+ def parse
48
+ @compression = parse_compression!
49
+
50
+ # ZIP not implemented
51
+ if [2, 3].include?(@compression)
52
+ @file.seek @end_pos and return
53
+ end
54
+
55
+ parse_image_data!
56
+
57
+ return self
58
+ end
59
+
60
+ # We delegate a few useful methods to the header.
61
+ [:height, :width, :channels, :depth, :mode].each do |attribute|
62
+ define_method attribute do
63
+ @header.send(attribute)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def calculate_length
70
+ @length = case depth
71
+ when 1 then (width + 7) / 8 * height
72
+ when 16 then width * height * 2
73
+ else width * height
74
+ end
75
+
76
+ @channel_length = @length
77
+ @length *= channels
78
+ end
79
+
80
+ def parse_compression!
81
+ @file.read_short
82
+ end
83
+
84
+ def parse_image_data!
85
+ case @compression
86
+ when 0 then parse_raw!
87
+ when 1 then parse_rle!
88
+ when 2, 3 then parse_zip!
89
+ else @file.seek(@end_pos)
90
+ end
91
+
92
+ process_image_data
93
+ end
94
+
95
+ def process_image_data
96
+ case mode
97
+ when 1 then combine_greyscale_channel
98
+ when 3 then combine_rgb_channel
99
+ when 4 then combine_cmyk_channel
100
+ end
101
+ end
102
+
103
+ def pixel_step
104
+ depth == 8 ? 1 : 2
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,36 @@
1
+ require 'chunky_png'
2
+
3
+ class PSD::Image
4
+ module Export
5
+ # PNG image export. This is the default export format.
6
+ module PNG
7
+ # Load the image pixels into a PNG file and return a reference to the
8
+ # data.
9
+ def to_png
10
+ png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
11
+
12
+ i = 0
13
+ height.times do |y|
14
+ width.times do |x|
15
+ png[x,y] = ChunkyPNG::Color.rgba(
16
+ @pixel_data[i],
17
+ @pixel_data[i+1],
18
+ @pixel_data[i+2],
19
+ @pixel_data[i+3]
20
+ )
21
+
22
+ i += 4
23
+ end
24
+ end
25
+
26
+ png
27
+ end
28
+ alias :export :to_png
29
+
30
+ # Saves the PNG data to disk.
31
+ def save_as_png(file)
32
+ to_png.save(file)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ class PSD::Image
2
+ module Format
3
+ # Parses a RAW uncompressed image
4
+ module RAW
5
+ private
6
+
7
+ def parse_raw!(length = @length)
8
+ @length.times do |i|
9
+ @channel_data[i] = @file.read(1).unpack('C')[0]
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ class PSD::Image
2
+ module Format
3
+ # Parses an RLE compressed image
4
+ module RLE
5
+ private
6
+
7
+ def parse_rle!
8
+ @byte_counts = parse_byte_counts!
9
+ parse_channel_data!
10
+ end
11
+
12
+ def parse_byte_counts!
13
+ byte_counts = []
14
+ channels.times do |i|
15
+ height.times do |j|
16
+ byte_counts << @file.read_short
17
+ end
18
+ end
19
+
20
+ return byte_counts
21
+ end
22
+
23
+ def parse_channel_data!
24
+ chan_pos = 0
25
+ line_index = 0
26
+
27
+ channels.times do |i|
28
+ chan_pos = decode_rle_channel(chan_pos, line_index)
29
+ line_index += height
30
+ end
31
+ end
32
+
33
+ def decode_rle_channel(chan_pos, line_index)
34
+ height.times do |j|
35
+ byte_count = @byte_counts[line_index]
36
+ line_index += 1
37
+ start = @file.tell
38
+
39
+ while @file.tell < start + byte_count
40
+ len = @file.read(1).bytes.to_a[0]
41
+
42
+ if len < 128
43
+ len += 1
44
+ (chan_pos...chan_pos+len).each do |k|
45
+ @channel_data[k] = @file.read(1).bytes.to_a[0]
46
+ end
47
+
48
+ chan_pos += len
49
+ elsif len > 128
50
+ len ^= 0xff
51
+ len += 2
52
+
53
+ val = @file.read(1).bytes.to_a[0]
54
+ (chan_pos...chan_pos+len).each do |k|
55
+ @channel_data[k] = val
56
+ end
57
+
58
+ chan_pos += len
59
+ end
60
+ end
61
+ end
62
+
63
+ return chan_pos
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,27 @@
1
+ class PSD::Image::Mode
2
+ module CMYK
3
+ private
4
+
5
+ def combine_cmyk_channel
6
+ (0...@num_pixels).step(pixel_step) do |i|
7
+ if channels == 5
8
+ a = @channel_data[i]
9
+ c = @channel_data[i + @channel_length]
10
+ m = @channel_data[i + @channel_length * 2]
11
+ y = @channel_data[i + @channel_length * 3]
12
+ k = @channel_data[i + @channel_length * 4]
13
+ else
14
+ a = 255
15
+ c = @channel_data[i]
16
+ m = @channel_data[i + @channel_length]
17
+ y = @channel_data[i + @channel_length * 2]
18
+ k = @channel_data[i + @channel_length * 3]
19
+ end
20
+
21
+ rgb = PSD::Color.cmyk_to_rgb(255 - c, 255 - m, 255 - y, 255 - k)
22
+
23
+ @pixel_data.push *rgb.values, a
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ class PSD::Image::Mode
2
+ module Greyscale
3
+ private
4
+
5
+ def combine_greyscale8_channel
6
+ if channels == 2
7
+ # We have an alpha channel
8
+ @num_pixels.times do |i|
9
+ alpha = @channel_data[i]
10
+ grey = @channel_data[@channel_length + i]
11
+
12
+ @pixel_data.push grey, grey, grey, alpha
13
+ end
14
+ else
15
+ @num_pixels.times do |i|
16
+ @pixel_data.push *([@channel_data[i]] * 3), 255
17
+ end
18
+ end
19
+ end
20
+
21
+ def combine_greyscale_channel
22
+ if channels == 2
23
+ (0...@num_pixels).step(pixel_step) do |i|
24
+ alpha = @channel_data[i]
25
+ grey = @channel_data[@channel_length + i]
26
+
27
+ @pixel_data.push grey, grey, grey, alpha
28
+ end
29
+ else
30
+ (0...@num_pixels).step(pixel_step) do |i|
31
+ @pixel_data.push *([@channel_data[i]] * 3), 255
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ class PSD::Image::Mode
2
+ # Combines the channel data from the image into RGB pixel values
3
+ module RGB
4
+ private
5
+
6
+ def combine_rgb_channel
7
+ (0...@num_pixels).step(pixel_step) do |i|
8
+ pixel = {r: 0, g: 0, b: 0, a: 255}
9
+
10
+ PSD::Image::CHANNEL_INFO[0...channels].each_with_index do |chan, index|
11
+ val = @channel_data[i + (@channel_length * index)]
12
+
13
+ case chan[:id]
14
+ when -1 then pixel[:a] = val
15
+ when 0 then pixel[:r] = val
16
+ when 1 then pixel[:g] = val
17
+ when 2 then pixel[:b] = val
18
+ end
19
+ end
20
+
21
+ @pixel_data.push *pixel.values
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,342 @@
1
+ class PSD
2
+ # Represents a single layer and all of the data associated with
3
+ # that layer.
4
+ class Layer
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
+ layer_name_source: LayerNameSource,
23
+ object_effects: ObjectEffects,
24
+ name: UnicodeName,
25
+ section_divider: LayerSectionDivider,
26
+ reference_point: ReferencePoint,
27
+ layer_id: LayerID,
28
+ fill_opacity: FillOpacity,
29
+ placed_layer: PlacedLayer,
30
+ vector_mask: VectorMask
31
+ }
32
+
33
+ # Initializes all of the defaults for the layer.
34
+ def initialize(file)
35
+ @file = file
36
+ @image = nil
37
+ @mask = {}
38
+ @blending_ranges = {}
39
+ @adjustments = {}
40
+ @channels_info = []
41
+ @blend_mode = {}
42
+ @group_layer = nil
43
+
44
+ @layer_type = 'normal'
45
+ @blending_mode = 'normal'
46
+ @opacity = 255
47
+ @fill_opacity = 255
48
+
49
+ # Just used for tracking which layer adjustments we're parsing.
50
+ # Not essential.
51
+ @info_keys = []
52
+ end
53
+
54
+ # Parse the layer and all of it's sub-sections.
55
+ def parse(index=nil)
56
+ start_section
57
+
58
+ @idx = index
59
+
60
+ parse_info
61
+ parse_blend_modes
62
+
63
+ extra_len = @file.read_int
64
+ @layer_end = @file.tell + extra_len
65
+
66
+ parse_mask_data
67
+ parse_blending_ranges
68
+ parse_legacy_layer_name
69
+ parse_extra_data
70
+
71
+ @file.seek @layer_end # Skip over any filler zeros
72
+
73
+ end_section
74
+ return self
75
+ end
76
+
77
+ # Export the layer to file. May or may not work.
78
+ def export(outfile)
79
+ export_info(outfile)
80
+
81
+ @blend_mode.write(outfile)
82
+ @file.seek(@blend_mode.num_bytes, IO::SEEK_CUR)
83
+
84
+ export_mask_data(outfile)
85
+ export_blending_ranges(outfile)
86
+ export_legacy_layer_name(outfile)
87
+ export_extra_data(outfile)
88
+
89
+ outfile.write @file.read(end_of_section - @file.tell)
90
+ end
91
+
92
+ # We just delegate this to a normal method call.
93
+ def [](val)
94
+ self.send(val)
95
+ end
96
+
97
+ def parse_channel_image!(header) #:nodoc:
98
+ # @image = ChannelImage.new(@file, header, self)
99
+ end
100
+
101
+ # Does this layer represent the start of a folder section?
102
+ def folder?
103
+ return false unless @adjustments.has_key?(:section_divider)
104
+ @adjustments[:section_divider].is_folder
105
+ end
106
+
107
+ # Does this layer represent the end of a folder section?
108
+ def folder_end?
109
+ return false unless @adjustments.has_key?(:section_divider)
110
+ @adjustments[:section_divider].is_hidden
111
+ end
112
+
113
+ # Is this layer visible?
114
+ def visible?
115
+ @visible
116
+ end
117
+
118
+ # Is this layer hidden?
119
+ def hidden?
120
+ !@visible
121
+ end
122
+
123
+ # Attempt to translate this layer and modify the document.
124
+ def translate(x=0, y=0)
125
+ @left += x
126
+ @right += x
127
+ @top += y
128
+ @bottom += y
129
+
130
+ @path_components.each{ |p| p.translate(x,y) } if @path_components
131
+ end
132
+
133
+ # Attempt to scale the path components within this layer.
134
+ def scale_path_components(xr, yr)
135
+ return unless @path_components
136
+
137
+ @path_components.each{ |p| p.scale(xr, yr) }
138
+ end
139
+
140
+ # Helper that exports the text data in this layer, if any.
141
+ def text
142
+ return nil unless @adjustments[:type]
143
+ @adjustments[:type].to_hash
144
+ end
145
+
146
+ # Gets the name of this layer. If the PSD file is from an even remotely
147
+ # recent version of Photoshop, this data is stored as extra layer info and
148
+ # as a UTF-16 name. Otherwise, it's stored in a legacy block.
149
+ def name
150
+ if @adjustments.has_key?(:name)
151
+ return @adjustments[:name].data
152
+ end
153
+
154
+ return @legacy_name
155
+ end
156
+
157
+ # We delegate all missing method calls to the extra layer info to make it easier
158
+ # to access that data.
159
+ def method_missing(method, *args, &block)
160
+ return @adjustments[method] if @adjustments.has_key?(method)
161
+ super
162
+ end
163
+
164
+ private
165
+
166
+ def parse_info
167
+ start_section(:info)
168
+
169
+ @top = @file.read_int
170
+ @left = @file.read_int
171
+ @bottom = @file.read_int
172
+ @right = @file.read_int
173
+ @channels = @file.read_short
174
+
175
+ @rows = @bottom - @top
176
+ @cols = @right - @left
177
+
178
+ @channels.times do
179
+ channel_id = @file.read_short
180
+ channel_length = @file.read_int
181
+
182
+ @channels_info << {id: channel_id, length: channel_length}
183
+ end
184
+
185
+ end_section(:info)
186
+ end
187
+
188
+ def export_info(outfile)
189
+ [@top, @left, @bottom, @right].each { |val| outfile.write_int(val) }
190
+ outfile.write_short(@channels)
191
+
192
+ @channels_info.each do |channel_info|
193
+ outfile.write_short channel_info[:id]
194
+ outfile.write_int channel_info[:length]
195
+ end
196
+
197
+ @file.seek end_of_section(:info)
198
+ end
199
+
200
+ def export_mask_data(outfile)
201
+ outfile.write @file.read(@mask_end - @mask_begin + 4)
202
+ end
203
+
204
+ def export_blending_ranges(outfile)
205
+ length = 4 * 2 # greys
206
+ length += @blending_ranges[:num_channels] * 8
207
+ outfile.write_int length
208
+
209
+ outfile.write_short @blending_ranges[:grey][:source][:black]
210
+ outfile.write_short @blending_ranges[:grey][:source][:white]
211
+ outfile.write_short @blending_ranges[:grey][:dest][:black]
212
+ outfile.write_short @blending_ranges[:grey][:dest][:white]
213
+
214
+ @blending_ranges[:num_channels].times do |i|
215
+ outfile.write_short @blending_ranges[:channels][i][:source][:black]
216
+ outfile.write_short @blending_ranges[:channels][i][:source][:white]
217
+ outfile.write_short @blending_ranges[:channels][i][:dest][:black]
218
+ outfile.write_short @blending_ranges[:channels][i][:dest][:white]
219
+ end
220
+
221
+ @file.seek length + 4, IO::SEEK_CUR
222
+ end
223
+
224
+ def export_legacy_layer_name(outfile)
225
+ outfile.write @file.read(@legacy_name_end - @legacy_name_start)
226
+ end
227
+
228
+ def export_extra_data(outfile)
229
+ outfile.write @file.read(@extra_data_end - @extra_data_begin)
230
+ if @path_components && !@path_components.empty?
231
+ outfile.seek @vector_mask_begin
232
+ @file.seek @vector_mask_begin
233
+
234
+ write_vector_mask(outfile)
235
+ @file.seek outfile.tell
236
+ end
237
+ end
238
+
239
+ def parse_blend_modes
240
+ @blend_mode = BlendMode.read(@file)
241
+
242
+ @blending_mode = @blend_mode.mode
243
+ @opacity = @blend_mode.opacity
244
+ @visible = @blend_mode.visible
245
+ end
246
+
247
+ def parse_mask_data
248
+ @mask_begin = @file.tell
249
+ @mask = Mask.read(@file)
250
+ @mask_end = @file.tell
251
+ end
252
+
253
+ def parse_blending_ranges
254
+ length = @file.read_int
255
+
256
+ @blending_ranges[:grey] = {
257
+ source: {
258
+ black: @file.read_short,
259
+ white: @file.read_short
260
+ },
261
+ dest: {
262
+ black: @file.read_short,
263
+ white: @file.read_short
264
+ }
265
+ }
266
+
267
+ @blending_ranges[:num_channels] = (length - 8) / 8
268
+
269
+ @blending_ranges[:channels] = []
270
+ @blending_ranges[:num_channels].times do
271
+ @blending_ranges[:channels] << {
272
+ source: {
273
+ black: @file.read_short,
274
+ white: @file.read_short
275
+ },
276
+ dest: {
277
+ black: @file.read_short,
278
+ white: @file.read_short
279
+ }
280
+ }
281
+ end
282
+ end
283
+
284
+ # The old school layer names are encoded in MacRoman format,
285
+ # not UTF-8. Luckily Ruby kicks ass at character conversion.
286
+ def parse_legacy_layer_name
287
+ @legacy_name_start = @file.tell
288
+ len = Util.pad4 @file.read(1).unpack('C')[0]
289
+ @legacy_name = @file.read(len).encode('UTF-8', 'MacRoman').delete("\000")
290
+ @legacy_name_end = @file.tell
291
+ end
292
+
293
+ # This section is a bit tricky to parse because it represents all of the
294
+ # extra data that describes this layer.
295
+ def parse_extra_data
296
+ @extra_data_begin = @file.tell
297
+
298
+ while @file.tell < @layer_end
299
+ # Signature, don't need
300
+ @file.seek 4, IO::SEEK_CUR
301
+
302
+ # Key, very important
303
+ key = @file.read(4).unpack('A4')[0]
304
+ @info_keys << key
305
+
306
+ length = Util.pad2 @file.read_int
307
+ pos = @file.tell
308
+
309
+ info_parsed = false
310
+ LAYER_INFO.each do |name, info|
311
+ next unless info.key == key
312
+
313
+ i = info.new(@file, length)
314
+ i.parse
315
+
316
+ @adjustments[name] = i
317
+ info_parsed = true
318
+ break
319
+ end
320
+
321
+ if !info_parsed
322
+ PSD.keys << key
323
+ # puts "SKIPPING #{key}, length = #{length}"
324
+ @file.seek length, IO::SEEK_CUR
325
+ end
326
+
327
+ @file.seek pos + length if @file.tell != (pos + length)
328
+ end
329
+
330
+ # puts "Layer = #{name}, Parsed = #{@info_keys - PSD.keys.uniq}, Unparsed = #{PSD.keys.uniq - @info_keys}"
331
+ @extra_data_end = @file.tell
332
+ end
333
+
334
+ def write_vector_mask(outfile)
335
+ outfile.write @file.read(8)
336
+ # outfile.write_int 3
337
+ # outfile.write_int @vector_tag
338
+
339
+ @path_components.each{ |pc| pc.write(outfile); @file.seek(26, IO::SEEK_CUR) }
340
+ end
341
+ end
342
+ end