psd 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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