psd 1.4.3 → 1.4.4
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 +4 -4
- data/README.md +1 -4
- data/lib/psd.rb +8 -8
- data/lib/psd/channel_image.rb +2 -3
- data/lib/psd/layer/channel_image.rb +3 -3
- data/lib/psd/layer/info.rb +7 -32
- data/lib/psd/layer_info.rb +5 -1
- data/lib/psd/layer_mask.rb +4 -3
- data/lib/psd/lazy_execute.rb +54 -0
- data/lib/psd/logger.rb +1 -9
- data/lib/psd/nodes/build_preview.rb +1 -1
- data/lib/psd/util.rb +8 -7
- data/lib/psd/version.rb +1 -1
- data/spec/image_spec.rb +0 -1
- data/spec/lazy_execute_spec.rb +20 -0
- data/spec/parsing_spec.rb +8 -6
- metadata +6 -5
- data/spec/identity_spec.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfdf8466e7c4a858bf93d47ec823c26ef3a667fa
|
4
|
+
data.tar.gz: bab2a4414c765c27a0d8de12b401067a2a894ba5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c80d095fbce20218530a13a3adcf29d9193a6dfe4e4a7b73bc3202bc04ddc3d1a80869d2c9f648ebca606f460647cc34e9d95f81a8bcdba0a044bd2070be8da0
|
7
|
+
data.tar.gz: fabf5fcfceeab8e772228ac3088aa73c5d9382cae2d31f41b405cfd5c2542139f08bbeb1f5e20fe3f99a153b8e65071398315f7fb47b36bd2070e86ec4a0e065
|
data/README.md
CHANGED
@@ -217,11 +217,7 @@ PSD.debug = true
|
|
217
217
|
|
218
218
|
You can build previews of any subset or version of the PSD document. This is useful for generating previews of layer comps or exporting individual layer groups as images.
|
219
219
|
|
220
|
-
**NOTE: You MUST initialize with the parse_layer_images option set to true, as shown below.**
|
221
|
-
|
222
220
|
``` ruby
|
223
|
-
psd = PSD.new(file, parse_layer_images: true)
|
224
|
-
|
225
221
|
# Save a layer comp
|
226
222
|
psd.tree.filter_by_comp("Version A").save_as_png('./Version A.png')
|
227
223
|
|
@@ -235,3 +231,4 @@ There are a few features that are currently missing from PSD.rb.
|
|
235
231
|
|
236
232
|
* More image modes + depths for image exporting
|
237
233
|
* A few layer info blocks
|
234
|
+
* Support for rendering all layer styles
|
data/lib/psd.rb
CHANGED
@@ -24,11 +24,6 @@ class PSD
|
|
24
24
|
include Helpers
|
25
25
|
include NodeExporting
|
26
26
|
|
27
|
-
DEFAULTS = {
|
28
|
-
parse_image: false,
|
29
|
-
parse_layer_images: false
|
30
|
-
}
|
31
|
-
|
32
27
|
attr_reader :file, :opts
|
33
28
|
alias :options :opts
|
34
29
|
|
@@ -55,7 +50,7 @@ class PSD
|
|
55
50
|
@file = PSD::File.new(file, 'rb')
|
56
51
|
@file.seek 0 # If the file was previously used and not closed
|
57
52
|
|
58
|
-
@opts =
|
53
|
+
@opts = opts
|
59
54
|
@header = nil
|
60
55
|
@resources = nil
|
61
56
|
@layer_mask = nil
|
@@ -74,7 +69,7 @@ class PSD
|
|
74
69
|
header
|
75
70
|
resources
|
76
71
|
layer_mask
|
77
|
-
image
|
72
|
+
image
|
78
73
|
|
79
74
|
@parsed = true
|
80
75
|
|
@@ -121,7 +116,12 @@ class PSD
|
|
121
116
|
ensure_resources
|
122
117
|
ensure_layer_mask
|
123
118
|
|
124
|
-
@image ||=
|
119
|
+
@image ||= (
|
120
|
+
# The image is the last section in the file, so we don't have to
|
121
|
+
# bother with skipping over the bytes to read more data.
|
122
|
+
image = Image.new(@file, @header)
|
123
|
+
LazyExecute.new(image, @file).later(:parse)
|
124
|
+
)
|
125
125
|
end
|
126
126
|
|
127
127
|
# Export the current file to a new PSD. This may or may not work.
|
data/lib/psd/channel_image.rb
CHANGED
@@ -9,7 +9,6 @@ class PSD
|
|
9
9
|
|
10
10
|
attr_reader :width, :height, :mask_data
|
11
11
|
|
12
|
-
|
13
12
|
def initialize(file, header, layer)
|
14
13
|
@layer = layer
|
15
14
|
|
@@ -27,7 +26,7 @@ class PSD
|
|
27
26
|
def skip
|
28
27
|
PSD.logger.debug "Skipping channel image data. Layer = #{@layer.name}"
|
29
28
|
@channels_info.each do |ch|
|
30
|
-
@file.seek ch
|
29
|
+
@file.seek ch[:length], IO::SEEK_CUR
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -66,7 +65,7 @@ class PSD
|
|
66
65
|
|
67
66
|
if finish != start + ch_info[:length]
|
68
67
|
PSD.logger.error "Read incorrect number of bytes for channel ##{ch_info[:id]}. Expected = #{ch_info[:length]}, Actual = #{finish - start}"
|
69
|
-
@file.seek start +
|
68
|
+
@file.seek start + ch_info[:length]
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
@@ -3,9 +3,9 @@ class PSD
|
|
3
3
|
module ChannelImage
|
4
4
|
attr_reader :image
|
5
5
|
|
6
|
-
def parse_channel_image(header
|
7
|
-
|
8
|
-
|
6
|
+
def parse_channel_image(header)
|
7
|
+
image = PSD::ChannelImage.new(@file, header, self)
|
8
|
+
@image = LazyExecute.new(image, @file).now(:skip).later(:parse)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/psd/layer/info.rb
CHANGED
@@ -44,34 +44,20 @@ class PSD
|
|
44
44
|
length = Util.pad2 @file.read_int
|
45
45
|
pos = @file.tell
|
46
46
|
|
47
|
-
|
47
|
+
key_parseable = false
|
48
48
|
LAYER_INFO.each do |name, info|
|
49
49
|
next unless info.key == key
|
50
50
|
|
51
51
|
PSD.logger.debug "Layer Info: key = #{key}, start = #{pos}, length = #{length}"
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@adjustments[name] = i
|
58
|
-
info_parsed = true
|
59
|
-
rescue Exception => e
|
60
|
-
PSD.logger.error "Parsing error: key = #{key}, message = #{e.message}"
|
61
|
-
PSD.logger.error e.backtrace.join("\n")
|
62
|
-
end
|
63
|
-
|
64
|
-
break
|
65
|
-
end
|
66
|
-
|
67
|
-
if !info_parsed
|
68
|
-
PSD.logger.debug "Skipping: layer = #{name}, key = #{key}, pos = #{@file.tell}, length = #{length}"
|
69
|
-
@file.seek pos + length
|
53
|
+
i = info.new(self, length)
|
54
|
+
@adjustments[name] = LazyExecute.new(i, @file).now(:skip).later(:parse)
|
55
|
+
key_parseable = true and break
|
70
56
|
end
|
71
57
|
|
72
|
-
|
73
|
-
PSD.logger.
|
74
|
-
@file.seek
|
58
|
+
unless key_parseable
|
59
|
+
PSD.logger.debug "Skipping unknown layer info block: key = #{key}, length = #{length}"
|
60
|
+
@file.seek length, IO::SEEK_CUR
|
75
61
|
end
|
76
62
|
end
|
77
63
|
|
@@ -81,17 +67,6 @@ class PSD
|
|
81
67
|
def vector_mask
|
82
68
|
info[:vector_mask_2] || info[:vector_mask]
|
83
69
|
end
|
84
|
-
|
85
|
-
def export_extra_data(outfile)
|
86
|
-
outfile.write @file.read(@extra_data_end - @extra_data_begin)
|
87
|
-
if @path_components && !@path_components.empty?
|
88
|
-
outfile.seek @vector_mask_begin
|
89
|
-
@file.seek @vector_mask_begin
|
90
|
-
|
91
|
-
write_vector_mask(outfile)
|
92
|
-
@file.seek outfile.tell
|
93
|
-
end
|
94
|
-
end
|
95
70
|
end
|
96
71
|
end
|
97
72
|
end
|
data/lib/psd/layer_info.rb
CHANGED
data/lib/psd/layer_mask.rb
CHANGED
@@ -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
|
59
|
+
layer.parse_channel_image(@header)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -107,11 +107,12 @@ class PSD
|
|
107
107
|
|
108
108
|
def parse_global_mask
|
109
109
|
length = @file.read_int
|
110
|
+
|
111
|
+
PSD.logger.debug "Global Mask: length = #{length}"
|
110
112
|
return if length <= 0
|
111
113
|
|
112
114
|
mask_end = @file.tell + length
|
113
|
-
|
114
|
-
|
115
|
+
|
115
116
|
@global_mask = {}
|
116
117
|
@global_mask[:overlay_color_space] = @file.read_short
|
117
118
|
@global_mask[:color_components] = 4.times.map { |i| @file.read_short >> 8 }
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class PSD
|
2
|
+
# Used for lazily executing methods on demand
|
3
|
+
class LazyExecute
|
4
|
+
def initialize(obj, file)
|
5
|
+
@obj = obj
|
6
|
+
@file = file
|
7
|
+
|
8
|
+
@start_pos = @file.tell
|
9
|
+
@loaded = false
|
10
|
+
@load_method = nil
|
11
|
+
@load_args = []
|
12
|
+
|
13
|
+
PSD.logger.debug "Marked #{@obj.class.name} at position #{@start_pos} for lazy execution"
|
14
|
+
end
|
15
|
+
|
16
|
+
def now(method, *args, &block)
|
17
|
+
PSD.logger.debug "Executing #{@obj.class.name}##{method} now"
|
18
|
+
@obj.send(method, *args, &block)
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
def later(method, *args)
|
23
|
+
@load_method = method
|
24
|
+
@load_args = args
|
25
|
+
return self
|
26
|
+
end
|
27
|
+
|
28
|
+
def loaded?
|
29
|
+
@loaded
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(method, *args, &block)
|
33
|
+
load! unless loaded?
|
34
|
+
@obj.send(method, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"<PSD::LazyExecute @obj=#{@obj.class.name}, @pos=#{@start_pos}, @load_method=:#{@load_method}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def load!
|
44
|
+
PSD.logger.debug "Lazily executing #{@obj.class.name}##{@load_method}"
|
45
|
+
orig_pos = @file.tell
|
46
|
+
@file.seek @start_pos
|
47
|
+
|
48
|
+
@obj.send(@load_method, *@load_args)
|
49
|
+
|
50
|
+
@file.seek orig_pos
|
51
|
+
@loaded = true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/psd/logger.rb
CHANGED
@@ -13,7 +13,7 @@ class PSD
|
|
13
13
|
return @logger if @logger
|
14
14
|
|
15
15
|
if debug || ENV['PSD_DEBUG']
|
16
|
-
@logger = ::Logger.new(
|
16
|
+
@logger = ::Logger.new(STDOUT)
|
17
17
|
@logger.formatter = proc do |severity, datetime, progname, msg|
|
18
18
|
"#{severity}: #{msg}\n"
|
19
19
|
end
|
@@ -23,14 +23,6 @@ class PSD
|
|
23
23
|
|
24
24
|
return @logger
|
25
25
|
end
|
26
|
-
|
27
|
-
def debug_output
|
28
|
-
if ENV['PSD_DEBUG']
|
29
|
-
ENV['PSD_DEBUG'] == 'STDOUT' ? STDOUT : ENV['PSD_DEBUG']
|
30
|
-
end
|
31
|
-
|
32
|
-
STDOUT
|
33
|
-
end
|
34
26
|
end
|
35
27
|
end
|
36
28
|
|
@@ -48,8 +48,8 @@ class PSD
|
|
48
48
|
PSD.logger.warn("Blend mode #{blending_mode} is not implemented") unless Compose.respond_to?(blending_mode)
|
49
49
|
PSD.logger.debug("Blending #{layer.name} with #{blending_mode} blend mode")
|
50
50
|
|
51
|
-
LayerStyles.new(layer, other).apply!
|
52
51
|
other = ClippingMask.new(layer, other).apply
|
52
|
+
LayerStyles.new(layer, other).apply!
|
53
53
|
|
54
54
|
blend_pixels!(blending_mode, layer, base, other, offset_x, offset_y)
|
55
55
|
end
|
data/lib/psd/util.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
class PSD
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
module Util
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def pad2(i)
|
6
|
+
(i + 1) & ~0x01
|
6
7
|
end
|
7
8
|
|
8
9
|
# Ensures value is a multiple of 4
|
9
|
-
def
|
10
|
-
i
|
10
|
+
def pad4(i)
|
11
|
+
((i + 4) & ~0x03) - 1
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def clamp(num, min, max)
|
14
15
|
[min, num, max].sort[1]
|
15
16
|
end
|
16
17
|
end
|
data/lib/psd/version.rb
CHANGED
data/spec/image_spec.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PSD::LazyExecute do
|
4
|
+
before(:each) do
|
5
|
+
@psd = PSD.new('spec/files/pixel.psd')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'initializes correctly' do
|
9
|
+
expect(@psd.image).to be_an_instance_of(PSD::LazyExecute)
|
10
|
+
expect(@psd.image).to_not be_loaded
|
11
|
+
expect(@psd.image.instance_variable_get(:@obj)).to be_an_instance_of(PSD::Image)
|
12
|
+
expect(@psd.image.instance_variable_get(:@start_pos)).to eq @psd.file.tell
|
13
|
+
expect(@psd.image.instance_variable_get(:@load_method)).to eq :parse
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'loads when accessed the first time' do
|
17
|
+
@psd.image.to_png
|
18
|
+
expect(@psd.image).to be_loaded
|
19
|
+
end
|
20
|
+
end
|
data/spec/parsing_spec.rb
CHANGED
@@ -74,12 +74,14 @@ describe 'Parsing' do
|
|
74
74
|
expect(@psd.layer_mask.layers.size).to be > 0
|
75
75
|
end
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
77
|
+
# The test file is actually missing the global mask. Need a new test file
|
78
|
+
# with it present.
|
79
|
+
# it "should contain the global layer mask data" do
|
80
|
+
# expect(@psd.layer_mask.global_mask).to_not be_nil
|
81
|
+
# expect(@psd.layer_mask.global_mask).to include :overlay_color_space
|
82
|
+
# expect(@psd.layer_mask.global_mask).to include :color_components
|
83
|
+
# expect(@psd.layer_mask.global_mask).to include opacity: 1.0
|
84
|
+
# end
|
83
85
|
end
|
84
86
|
|
85
87
|
describe 'Layers' do
|
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.4.
|
4
|
+
version: 1.4.4
|
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-11-
|
12
|
+
date: 2013-11-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -192,6 +192,7 @@ files:
|
|
192
192
|
- lib/psd/layer_info/vector_stroke_content.rb
|
193
193
|
- lib/psd/layer_mask.rb
|
194
194
|
- lib/psd/layer_styles.rb
|
195
|
+
- lib/psd/lazy_execute.rb
|
195
196
|
- lib/psd/logger.rb
|
196
197
|
- lib/psd/mask.rb
|
197
198
|
- lib/psd/node.rb
|
@@ -223,8 +224,8 @@ files:
|
|
223
224
|
- spec/files/slices.psd
|
224
225
|
- spec/files/text.psd
|
225
226
|
- spec/hierarchy_spec.rb
|
226
|
-
- spec/identity_spec.rb
|
227
227
|
- spec/image_spec.rb
|
228
|
+
- spec/lazy_execute_spec.rb
|
228
229
|
- spec/parsing_spec.rb
|
229
230
|
- spec/psd_spec.rb
|
230
231
|
- spec/slices_spec.rb
|
@@ -250,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
250
251
|
version: '0'
|
251
252
|
requirements: []
|
252
253
|
rubyforge_project:
|
253
|
-
rubygems_version: 2.
|
254
|
+
rubygems_version: 2.1.11
|
254
255
|
signing_key:
|
255
256
|
specification_version: 4
|
256
257
|
summary: General purpose library for parsing Photoshop files
|
@@ -263,8 +264,8 @@ test_files:
|
|
263
264
|
- spec/files/slices.psd
|
264
265
|
- spec/files/text.psd
|
265
266
|
- spec/hierarchy_spec.rb
|
266
|
-
- spec/identity_spec.rb
|
267
267
|
- spec/image_spec.rb
|
268
|
+
- spec/lazy_execute_spec.rb
|
268
269
|
- spec/parsing_spec.rb
|
269
270
|
- spec/psd_spec.rb
|
270
271
|
- spec/slices_spec.rb
|
data/spec/identity_spec.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'tempfile'
|
3
|
-
|
4
|
-
describe "Identity exports" do
|
5
|
-
it "should export the simplest PSD" do
|
6
|
-
filepath = 'spec/files/simplest.psd'
|
7
|
-
psd = PSD.new(filepath)
|
8
|
-
psd.parse!
|
9
|
-
tmpfile = Tempfile.new("simplest_export.psd")
|
10
|
-
psd.export tmpfile.path
|
11
|
-
|
12
|
-
expect(Digest::MD5.hexdigest(tmpfile.read)).to eq(Digest::MD5.hexdigest(File.read(filepath)))
|
13
|
-
end
|
14
|
-
|
15
|
-
it "should export a file with a layer" do
|
16
|
-
filepath = 'spec/files/one_layer.psd'
|
17
|
-
psd = PSD.new(filepath)
|
18
|
-
psd.parse!
|
19
|
-
tmpfile = Tempfile.new("one_layer_export.psd")
|
20
|
-
psd.export tmpfile.path
|
21
|
-
|
22
|
-
expect(Digest::MD5.hexdigest(tmpfile.read)).to eq(Digest::MD5.hexdigest(File.read(filepath)))
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should export a PSD with vector paths" do
|
26
|
-
filepath = 'spec/files/path.psd'
|
27
|
-
psd = PSD.new(filepath)
|
28
|
-
psd.parse!
|
29
|
-
tmpfile = Tempfile.new("path_export.psd")
|
30
|
-
psd.export tmpfile.path
|
31
|
-
|
32
|
-
expect(Digest::MD5.hexdigest(tmpfile.read)).to eq(Digest::MD5.hexdigest(File.read(filepath)))
|
33
|
-
end
|
34
|
-
end
|