psd 1.0.0 → 1.1.0

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: 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