psd 1.0.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e806c5a8d10b43d7bddb5bf2e0e9ca3843a579f
4
- data.tar.gz: 5560fc10135e67e5d396a367c5ae737cf0f84c51
3
+ metadata.gz: 93b149110f6660cd6b6231f8e8e1bcf440ffac4c
4
+ data.tar.gz: 7762bab9c1c6504a5fd39f54602acd27e30746ee
5
5
  SHA512:
6
- metadata.gz: bcac2c4003765bc89eaba409c48b860bcc6d49ec377953332dcfccbb58baecc9b7531d285a95cfeaff14c1f8102b5b8bfab341ae915b4270f67760d271abf186
7
- data.tar.gz: c8656fe0d3ccb52301f09e40856a0f90d1ad7ebeba4ba2793a0cc3edef958fd57daa9e16a69034abf1ae416420126bd1e9e456da5ba69b7edc28d0483755c3cb
6
+ metadata.gz: d198525b3c71df4b3c4d869a3d56752f709bde1ccda35f25c22b4da0d68775bbe7ed09adfdb013fce65600953a4a421eb618430a613017c16c93aa404887ff19
7
+ data.tar.gz: 90ba4242c94d7cb725ed2180188cee87ba2230a2cc45bd77ed33bf6e2e7046561efabd6c9293847343e55fa13d7efba9e4b7491e004c100a3c8c32ab304ee51f
data/lib/psd.rb CHANGED
@@ -28,7 +28,8 @@ class PSD
28
28
  parse_layer_images: false
29
29
  }
30
30
 
31
- attr_reader :file
31
+ attr_reader :file, :opts
32
+ alias :options :opts
32
33
 
33
34
  # Opens the named file, parses it, and makes it available for reading. Then, closes it after you're finished.
34
35
  # @param filename [String] the name of the file to open
@@ -112,7 +113,7 @@ class PSD
112
113
  ensure_header
113
114
  ensure_resources
114
115
 
115
- @layer_mask ||= LayerMask.new(@file, @header).parse
116
+ @layer_mask ||= LayerMask.new(@file, @header, @opts).parse
116
117
  end
117
118
 
118
119
  # Get the full size flattened preview Image.
@@ -164,7 +165,7 @@ class PSD
164
165
  def ensure_layer_mask
165
166
  return unless @layer_mask.nil?
166
167
 
167
- @layer_mask = LayerMask.new(@file, @header)
168
+ @layer_mask = LayerMask.new(@file, @header, @opts)
168
169
  @layer_mask.skip
169
170
  end
170
171
  end
@@ -4,6 +4,85 @@ class PSD
4
4
  # Represents an image for a single layer, which differs slightly in format from
5
5
  # the full size preview image.
6
6
  class ChannelImage < Image
7
+ include ImageFormat::LayerRLE
8
+ include ImageFormat::LayerRAW
7
9
 
10
+ attr_reader :width, :height
11
+
12
+ def initialize(file, header, layer)
13
+ @layer = layer
14
+
15
+ @width = @layer.width
16
+ @height = @layer.height
17
+
18
+ super(file, header)
19
+
20
+ @channels_info = @layer.channels_info
21
+ end
22
+
23
+ def skip
24
+ PSD.logger.debug "Skipping channel image data. Layer = #{@layer.name}"
25
+ @channels_info.each do |ch|
26
+ @file.seek ch.length, IO::SEEK_CUR
27
+ end
28
+ end
29
+
30
+ def channels
31
+ @layer.channels
32
+ end
33
+
34
+ def parse
35
+ PSD.logger.debug "Layer = #{@layer.name}, Size = #{width}x#{height}"
36
+
37
+ @chan_pos = 0
38
+
39
+ @channels_info.each do |ch_info|
40
+ @ch_info = ch_info
41
+ if ch_info[:length] <= 0
42
+ parse_compression! and next
43
+ end
44
+
45
+ # If the ID of this current channel is -2, then we assume the dimensions
46
+ # of the layer mask.
47
+ if ch_info[:id] == -2
48
+ @width = @layer.mask.width
49
+ @height = @layer.mask.height
50
+ else
51
+ @width = @layer.width
52
+ @height = @layer.height
53
+ end
54
+
55
+ start = @file.tell
56
+
57
+ PSD.logger.debug "Channel ##{ch_info[:id]}, length = #{ch_info[:length]}"
58
+ parse_image_data!
59
+
60
+ finish = @file.tell
61
+
62
+ if finish != start + ch_info[:length]
63
+ PSD.logger.error "Read incorrect number of bytes for channel ##{ch_info[:id]}. Expected = #{ch_info[:length]}, Actual = #{finish - start}"
64
+ @file.seek start + @ch_info[:length]
65
+ end
66
+ end
67
+
68
+ if @channel_data.length != @length
69
+ PSD.logger.error "#{channel_data.length} read; expected #{@length}"
70
+ end
71
+
72
+ process_image_data
73
+ end
74
+
75
+ def parse_image_data!
76
+ @compression = parse_compression!
77
+
78
+ case @compression
79
+ when 0 then parse_raw!
80
+ when 1 then parse_rle!
81
+ when 2, 3 then parse_zip!
82
+ else
83
+ PSD.logger.error "Unknown image compression. Attempting to skip."
84
+ @file.seek(@end_pos)
85
+ end
86
+ end
8
87
  end
9
88
  end
@@ -1,11 +1,11 @@
1
1
  class PSD
2
2
  # Parses the full preview image at the end of the PSD document.
3
3
  class Image
4
- include Format::RAW
5
- include Format::RLE
6
- include Mode::CMYK
7
- include Mode::Greyscale
8
- include Mode::RGB
4
+ include ImageFormat::RAW
5
+ include ImageFormat::RLE
6
+ include ImageMode::CMYK
7
+ include ImageMode::Greyscale
8
+ include ImageMode::RGB
9
9
  include Export::PNG
10
10
 
11
11
  # All of the possible compression formats Photoshop uses.
@@ -16,14 +16,6 @@ class PSD
16
16
  'ZIPPrediction'
17
17
  ]
18
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
19
  # Store a reference to the file and the header. We also do a few simple calculations
28
20
  # to figure out the number of pixels in the image and the length of each channel.
29
21
  def initialize(file, header)
@@ -42,6 +34,14 @@ class PSD
42
34
  @pixel_data = []
43
35
 
44
36
  PSD.logger.debug "Image: #{width}x#{height}, length = #{@length}, mode = #{@header.mode_name}, position = #{@start_pos}"
37
+
38
+ # Each color channel is represented by a unique ID
39
+ @channels_info = [
40
+ {id: 0},
41
+ {id: 1},
42
+ {id: 2},
43
+ {id: -1}
44
+ ]
45
45
  end
46
46
 
47
47
  # Begins parsing the image by first figuring out the compression format used, and then
@@ -7,24 +7,26 @@ class PSD::Image
7
7
  # Load the image pixels into a PNG file and return a reference to the
8
8
  # data.
9
9
  def to_png
10
- PSD.logger.debug "Beginning PNG export"
11
- png = ChunkyPNG::Image.new(width.to_i, height.to_i, ChunkyPNG::Color::TRANSPARENT)
10
+ @png ||= (
11
+ PSD.logger.debug "Beginning PNG export"
12
+ png = ChunkyPNG::Image.new(width.to_i, height.to_i, ChunkyPNG::Color::TRANSPARENT)
12
13
 
13
- i = 0
14
- height.times do |y|
15
- width.times do |x|
16
- png[x,y] = ChunkyPNG::Color.rgba(
17
- @pixel_data[i],
18
- @pixel_data[i+1],
19
- @pixel_data[i+2],
20
- @pixel_data[i+3]
21
- )
14
+ i = 0
15
+ height.times do |y|
16
+ width.times do |x|
17
+ png[x,y] = ChunkyPNG::Color.rgba(
18
+ @pixel_data[i],
19
+ @pixel_data[i+1],
20
+ @pixel_data[i+2],
21
+ @pixel_data[i+3]
22
+ )
22
23
 
23
- i += 4
24
+ i += 4
25
+ end
24
26
  end
25
- end
26
27
 
27
- png
28
+ png
29
+ )
28
30
  end
29
31
  alias :export :to_png
30
32
 
@@ -0,0 +1,21 @@
1
+ class PSD
2
+ module ImageFormat
3
+ module LayerRAW
4
+ private
5
+
6
+ # Since we're parsing on a per-channel basis, we need to modify the behavior
7
+ # of the RAW encoding parser a bit. This version is aware of the current
8
+ # channel data position, since layers that have RAW encoding often use RLE
9
+ # encoded alpha channels.
10
+ def parse_raw!(length = @length)
11
+ PSD.logger.debug "Attempting to parse RAW encoded channel..."
12
+
13
+ (@chan_pos...(@chan_pos + @ch_info[:length] - 2)).each do |i|
14
+ @channel_data[i] = @file.read(1).bytes.to_a[0]
15
+ end
16
+
17
+ @chan_pos += @ch_info[:length] - 2
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ class PSD
2
+ module ImageFormat
3
+ # Some method overrides for layer images.
4
+ module LayerRLE
5
+ private
6
+
7
+ def parse_byte_counts!
8
+ byte_counts = []
9
+ height.times do
10
+ byte_counts << @file.read_short
11
+ end
12
+
13
+ return byte_counts
14
+ end
15
+
16
+ def parse_channel_data!
17
+ PSD.logger.debug "Parsing layer RLE channel ##{@ch_info[:id]}: position = #{@chan_pos}"
18
+ @chan_pos = decode_rle_channel(@chan_pos, 0)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,12 +1,12 @@
1
- class PSD::Image
2
- module Format
1
+ class PSD
2
+ module ImageFormat
3
3
  # Parses a RAW uncompressed image
4
4
  module RAW
5
5
  private
6
6
 
7
7
  def parse_raw!(length = @length)
8
8
  @length.times do |i|
9
- @channel_data[i] = @file.read(1).unpack('C')[0]
9
+ @channel_data[i] = @file.read(1).bytes.to_a[0]
10
10
  end
11
11
  end
12
12
  end
@@ -1,5 +1,5 @@
1
- class PSD::Image
2
- module Format
1
+ class PSD
2
+ module ImageFormat
3
3
  # Parses an RLE compressed image
4
4
  module RLE
5
5
  private
@@ -1,26 +1,28 @@
1
- class PSD::Image::Mode
2
- module CMYK
3
- private
1
+ class PSD
2
+ module ImageMode
3
+ module CMYK
4
+ private
4
5
 
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
6
+ def combine_cmyk_channel
7
+ (0...@num_pixels).step(pixel_step) do |i|
8
+ if channels == 5
9
+ a = @channel_data[i]
10
+ c = @channel_data[i + @channel_length]
11
+ m = @channel_data[i + @channel_length * 2]
12
+ y = @channel_data[i + @channel_length * 3]
13
+ k = @channel_data[i + @channel_length * 4]
14
+ else
15
+ a = 255
16
+ c = @channel_data[i]
17
+ m = @channel_data[i + @channel_length]
18
+ y = @channel_data[i + @channel_length * 2]
19
+ k = @channel_data[i + @channel_length * 3]
20
+ end
20
21
 
21
- rgb = PSD::Color.cmyk_to_rgb(255 - c, 255 - m, 255 - y, 255 - k)
22
+ rgb = PSD::Color.cmyk_to_rgb(255 - c, 255 - m, 255 - y, 255 - k)
22
23
 
23
- @pixel_data.push *rgb.values, a
24
+ @pixel_data.push *rgb.values, a
25
+ end
24
26
  end
25
27
  end
26
28
  end
@@ -1,18 +1,20 @@
1
- class PSD::Image::Mode
2
- module Greyscale
3
- private
1
+ class PSD
2
+ module ImageMode
3
+ module Greyscale
4
+ private
4
5
 
5
- def combine_greyscale_channel
6
- if channels == 2
7
- (0...@num_pixels).step(pixel_step) do |i|
8
- alpha = @channel_data[i]
9
- grey = @channel_data[@channel_length + i]
6
+ def combine_greyscale_channel
7
+ if channels == 2
8
+ (0...@num_pixels).step(pixel_step) do |i|
9
+ alpha = @channel_data[i]
10
+ grey = @channel_data[@channel_length + i]
10
11
 
11
- @pixel_data.push grey, grey, grey, alpha
12
- end
13
- else
14
- (0...@num_pixels).step(pixel_step) do |i|
15
- @pixel_data.push *([@channel_data[i]] * 3), 255
12
+ @pixel_data.push grey, grey, grey, alpha
13
+ end
14
+ else
15
+ (0...@num_pixels).step(pixel_step) do |i|
16
+ @pixel_data.push *([@channel_data[i]] * 3), 255
17
+ end
16
18
  end
17
19
  end
18
20
  end
@@ -1,28 +1,30 @@
1
- class PSD::Image::Mode
2
- # Combines the channel data from the image into RGB pixel values
3
- module RGB
4
- private
1
+ class PSD
2
+ module ImageMode
3
+ # Combines the channel data from the image into RGB pixel values
4
+ module RGB
5
+ private
5
6
 
6
- def combine_rgb_channel
7
- PSD.logger.debug "Beginning RGB processing"
7
+ def combine_rgb_channel
8
+ PSD.logger.debug "Beginning RGB processing"
8
9
 
9
- (0...@num_pixels).step(pixel_step) do |i|
10
- r = g = b = 0
11
- a = 255
10
+ (0...@num_pixels).step(pixel_step) do |i|
11
+ r = g = b = 0
12
+ a = 255
12
13
 
13
- PSD::Image::CHANNEL_INFO.each_with_index do |chan, index|
14
- next if channels == 3 && chan[:id] == -1
15
- val = @channel_data[i + (@channel_length * index)]
14
+ @channels_info.each_with_index do |chan, index|
15
+ next if channels == 3 && chan[:id] == -1
16
+ val = @channel_data[i + (@channel_length * index)]
16
17
 
17
- case chan[:id]
18
- when -1 then a = val
19
- when 0 then r = val
20
- when 1 then g = val
21
- when 2 then b = val
18
+ case chan[:id]
19
+ when -1 then a = val
20
+ when 0 then r = val
21
+ when 1 then g = val
22
+ when 2 then b = val
23
+ end
22
24
  end
23
- end
24
25
 
25
- @pixel_data.push r, g, b, a
26
+ @pixel_data.push r, g, b, a
27
+ end
26
28
  end
27
29
  end
28
30
  end
@@ -96,8 +96,9 @@ class PSD
96
96
  self.send(val)
97
97
  end
98
98
 
99
- def parse_channel_image!(header) #:nodoc:
100
- # @image = ChannelImage.new(@file, header, self)
99
+ def parse_channel_image!(header, parse)
100
+ @image = ChannelImage.new(@file, header, self)
101
+ parse ? @image.parse : @image.skip
101
102
  end
102
103
 
103
104
  # Does this layer represent the start of a folder section?
@@ -7,9 +7,10 @@ class PSD
7
7
  attr_reader :layers, :global_mask
8
8
 
9
9
  # Store a reference to the file and the header and initialize the defaults.
10
- def initialize(file, header)
10
+ def initialize(file, header, options)
11
11
  @file = file
12
12
  @header = header
13
+ @options = options
13
14
 
14
15
  @layers = []
15
16
  @merged_alpha = false
@@ -55,8 +56,7 @@ class PSD
55
56
 
56
57
  layers.each do |layer|
57
58
  @file.seek 8, IO::SEEK_CUR and next if layer.folder? || layer.folder_end?
58
-
59
- layer.parse_channel_image!(@header)
59
+ layer.parse_channel_image!(@header, @options[:parse_layer_images])
60
60
  end
61
61
  end
62
62
 
@@ -29,6 +29,14 @@ class PSD
29
29
  @layer.visible?
30
30
  end
31
31
 
32
+ def layer?
33
+ is_a?(PSD::Node::Layer)
34
+ end
35
+
36
+ def group?
37
+ is_a?(PSD::Node::Group)
38
+ end
39
+
32
40
  def to_hash
33
41
  hash = {
34
42
  type: nil,
@@ -1,3 +1,3 @@
1
1
  class PSD
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
Binary file
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Image Exporting' do
4
+ before(:each) do
5
+ class PSD::Image
6
+ attr_accessor :pixel_data
7
+ end
8
+
9
+ @psd = PSD.new('spec/files/pixel.psd')
10
+ end
11
+
12
+ describe "the full preview image" do
13
+ it "should successfully parse the image data" do
14
+ @psd.parse!
15
+ expect(@psd).to be_parsed
16
+ expect(@psd.image).to_not be_nil
17
+ expect(@psd.image.width).to eq(1)
18
+ expect(@psd.image.height).to eq(1)
19
+ expect(@psd.image.pixel_data).to eq([0, 100, 200, 255])
20
+ end
21
+
22
+ it "should be able to skip to the image" do
23
+ expect(@psd).to_not be_parsed
24
+ expect(@psd.image.width).to eq(1)
25
+ expect(@psd.image.height).to eq(1)
26
+ expect(@psd.image.pixel_data).to eq([0, 100, 200, 255])
27
+ end
28
+
29
+ describe "as PNG" do
30
+ it "should produce a valid PNG object" do
31
+ expect(@psd.image.to_png).to be_an_instance_of(ChunkyPNG::Image)
32
+
33
+ # Ensure it's cached
34
+ expect(@psd.image.to_png).to be @psd.image.to_png
35
+ expect(@psd.image.to_png.width).to eq(1)
36
+ expect(@psd.image.to_png.height).to eq(1)
37
+ expect(
38
+ ChunkyPNG::Color.to_truecolor_alpha_bytes(@psd.image.to_png[0,0])
39
+ ).to eq([0, 100, 200, 255])
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "layer images" do
45
+ it "should successfully parse the image data" do
46
+ @psd.options[:parse_layer_images] = true
47
+ @psd.parse!
48
+
49
+ image = @psd.tree.children.first.image
50
+ expect(image).to be_an_instance_of(PSD::ChannelImage)
51
+ expect(image.width).to eq(1)
52
+ expect(image.height).to eq(1)
53
+
54
+ expect(image.pixel_data).to eq([0, 100, 200, 255])
55
+ end
56
+
57
+ describe "as PNG" do
58
+ it "should produce a valid PNG object" do
59
+ @psd.options[:parse_layer_images] = true
60
+ @psd.parse!
61
+
62
+ png = @psd.tree.children.first.image.to_png
63
+ expect(png).to be_an_instance_of(ChunkyPNG::Image)
64
+ expect(png.width).to eq(1)
65
+ expect(png.height).to eq(1)
66
+ expect(
67
+ ChunkyPNG::Color.to_truecolor_alpha_bytes(png[0,0])
68
+ ).to eq([0, 100, 200, 255])
69
+ end
70
+ end
71
+ end
72
+ 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.0.0
4
+ version: 1.1.0
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-08-25 00:00:00.000000000 Z
12
+ date: 2013-09-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bindata
@@ -136,6 +136,8 @@ files:
136
136
  - lib/psd/helpers.rb
137
137
  - lib/psd/image.rb
138
138
  - lib/psd/image_exports/png.rb
139
+ - lib/psd/image_formats/layer_raw.rb
140
+ - lib/psd/image_formats/layer_rle.rb
139
141
  - lib/psd/image_formats/raw.rb
140
142
  - lib/psd/image_formats/rle.rb
141
143
  - lib/psd/image_modes/cmyk.rb
@@ -179,10 +181,12 @@ files:
179
181
  - spec/files/example.psd
180
182
  - spec/files/one_layer.psd
181
183
  - spec/files/path.psd
184
+ - spec/files/pixel.psd
182
185
  - spec/files/simplest.psd
183
186
  - spec/files/text.psd
184
187
  - spec/hierarchy_spec.rb
185
188
  - spec/identity_spec.rb
189
+ - spec/image_spec.rb
186
190
  - spec/parsing_spec.rb
187
191
  - spec/psd_spec.rb
188
192
  - spec/spec_helper.rb
@@ -215,10 +219,12 @@ test_files:
215
219
  - spec/files/example.psd
216
220
  - spec/files/one_layer.psd
217
221
  - spec/files/path.psd
222
+ - spec/files/pixel.psd
218
223
  - spec/files/simplest.psd
219
224
  - spec/files/text.psd
220
225
  - spec/hierarchy_spec.rb
221
226
  - spec/identity_spec.rb
227
+ - spec/image_spec.rb
222
228
  - spec/parsing_spec.rb
223
229
  - spec/psd_spec.rb
224
230
  - spec/spec_helper.rb