psd 1.4.5 → 1.5.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 +4 -4
- data/lib/psd/channel_image.rb +0 -1
- data/lib/psd/compose.rb +1 -1
- data/lib/psd/file.rb +1 -1
- data/lib/psd/image_exports/png.rb +20 -17
- data/lib/psd/image_formats/layer_rle.rb +1 -1
- data/lib/psd/image_formats/rle.rb +1 -1
- data/lib/psd/layer/helpers.rb +24 -0
- data/lib/psd/layer/info.rb +2 -0
- data/lib/psd/layer_info/locked.rb +19 -0
- data/lib/psd/layer_info/typetool.rb +1 -1
- data/lib/psd/layer_mask.rb +0 -1
- data/lib/psd/node_group.rb +9 -4
- data/lib/psd/node_layer.rb +4 -0
- data/lib/psd/nodes/build_preview.rb +9 -9
- data/lib/psd/resources/slices.rb +118 -13
- data/lib/psd/version.rb +1 -1
- data/spec/files/empty-layer.psd +0 -0
- data/spec/files/locked.psd +0 -0
- data/spec/hierarchy_spec.rb +21 -1
- data/spec/image_spec.rb +44 -28
- data/spec/locked_spec.rb +78 -0
- data/spec/parsing_spec.rb +1 -1
- data/spec/slices_spec.rb +15 -10
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87dd8d12bd49feb7ad2502047fa2ec73d4aff7b1
|
|
4
|
+
data.tar.gz: 5647eca77e19f0a4ebb3ac59448d8b26d0f56da8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d82f2fde33a13a60863548bc75788ffd279651e1c66a553749d794080733463e4f78cf13cfbcbf58a4d6296579f678cc3e32a0e6419c83ef7273e1b41d1406c5
|
|
7
|
+
data.tar.gz: 23a3d1ba099ed12c363958af463393e56702e2ac2a02ba67a9af247016fbe2f384f893f5ea5dad4bdb651cb76196f9a77d42efe69cf7ced445ea6177e4ffbffe
|
data/lib/psd/channel_image.rb
CHANGED
data/lib/psd/compose.rb
CHANGED
|
@@ -17,7 +17,7 @@ class PSD
|
|
|
17
17
|
|
|
18
18
|
# Normal composition, delegate to ChunkyPNG
|
|
19
19
|
def normal(fg, bg, opts={})
|
|
20
|
-
return fg if
|
|
20
|
+
return fg if fully_transparent?(bg)
|
|
21
21
|
return bg if fully_transparent?(fg)
|
|
22
22
|
|
|
23
23
|
mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts))
|
data/lib/psd/file.rb
CHANGED
|
@@ -72,7 +72,7 @@ class PSD
|
|
|
72
72
|
# Reads a string of the given length and converts it to UTF-8 from the internally used MacRoman encoding.
|
|
73
73
|
def read_string(length=nil)
|
|
74
74
|
length = @file.read(1).bytes.to_a[0] if length.nil?
|
|
75
|
-
read(length).
|
|
75
|
+
read(length).delete("\000")
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def read_byte
|
|
@@ -7,31 +7,34 @@ 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
|
+
return @png if @png
|
|
11
|
+
|
|
10
12
|
PSD.logger.debug "Beginning PNG export"
|
|
11
|
-
png = ChunkyPNG::Canvas.new(width.to_i, height.to_i, ChunkyPNG::Color::TRANSPARENT)
|
|
13
|
+
@png = ChunkyPNG::Canvas.new(width.to_i, height.to_i, ChunkyPNG::Color::TRANSPARENT)
|
|
12
14
|
|
|
13
15
|
i = 0
|
|
14
16
|
height.times do |y|
|
|
15
17
|
width.times do |x|
|
|
16
|
-
png[x,y] = @pixel_data[i]
|
|
18
|
+
@png[x,y] = @pixel_data[i]
|
|
17
19
|
i += 1
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
png
|
|
23
|
+
@png
|
|
22
24
|
end
|
|
23
25
|
alias :export :to_png
|
|
24
26
|
|
|
25
27
|
def to_png_with_mask
|
|
26
28
|
return to_png unless has_mask?
|
|
29
|
+
return @png_with_mask if @png_with_mask
|
|
27
30
|
|
|
28
31
|
PSD.logger.debug "Beginning PNG export with mask"
|
|
29
|
-
|
|
32
|
+
|
|
30
33
|
# We generate the preview at the document size instead to make applying the mask
|
|
31
34
|
# significantly easier.
|
|
32
35
|
width = @layer.header.width.to_i
|
|
33
36
|
height = @layer.header.height.to_i
|
|
34
|
-
|
|
37
|
+
@png_with_mask = ChunkyPNG::Canvas.new(width, height, ChunkyPNG::Color::TRANSPARENT)
|
|
35
38
|
|
|
36
39
|
i = 0
|
|
37
40
|
@layer.height.times do |y|
|
|
@@ -39,13 +42,13 @@ class PSD::Image
|
|
|
39
42
|
offset_x = x + @layer.left
|
|
40
43
|
offset_y = y + @layer.top
|
|
41
44
|
|
|
42
|
-
i +=1 and next if offset_x < 0 || offset_y < 0 || offset_x >=
|
|
45
|
+
i +=1 and next if offset_x < 0 || offset_y < 0 || offset_x >= @png_with_mask.width || offset_y >= @png_with_mask.height
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
@png_with_mask[offset_x, offset_y] = @pixel_data[i]
|
|
45
48
|
i += 1
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
# Now we apply the mask
|
|
50
53
|
i = 0
|
|
51
54
|
@layer.mask.height.times do |y|
|
|
@@ -53,22 +56,22 @@ class PSD::Image
|
|
|
53
56
|
offset_x = @layer.mask.left + x
|
|
54
57
|
offset_y = @layer.mask.top + y
|
|
55
58
|
|
|
56
|
-
i += 1 and next if offset_x < 0 || offset_y < 0 || offset_x >=
|
|
59
|
+
i += 1 and next if offset_x < 0 || offset_y < 0 || offset_x >= @png_with_mask.width || offset_y >= @png_with_mask.height
|
|
57
60
|
|
|
58
|
-
color = ChunkyPNG::Color.to_truecolor_alpha_bytes(
|
|
61
|
+
color = ChunkyPNG::Color.to_truecolor_alpha_bytes(@png_with_mask[offset_x, offset_y])
|
|
59
62
|
color[3] = color[3] * @mask_data[i] / 255
|
|
60
63
|
|
|
61
|
-
|
|
64
|
+
@png_with_mask[offset_x, offset_y] = ChunkyPNG::Color.rgba(*color)
|
|
62
65
|
i += 1
|
|
63
66
|
end
|
|
64
67
|
end
|
|
65
68
|
|
|
66
|
-
crop_left = PSD::Util.clamp(@layer.left, 0,
|
|
67
|
-
crop_top = PSD::Util.clamp(@layer.top, 0,
|
|
68
|
-
crop_width = PSD::Util.clamp(@layer.width.to_i, 0,
|
|
69
|
-
crop_height = PSD::Util.clamp(@layer.height.to_i, 0,
|
|
69
|
+
crop_left = PSD::Util.clamp(@layer.left, 0, @png_with_mask.width)
|
|
70
|
+
crop_top = PSD::Util.clamp(@layer.top, 0, @png_with_mask.height)
|
|
71
|
+
crop_width = PSD::Util.clamp(@layer.width.to_i, 0, @png_with_mask.width - crop_left)
|
|
72
|
+
crop_height = PSD::Util.clamp(@layer.height.to_i, 0, @png_with_mask.height - crop_top)
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
@png_with_mask.crop!(crop_left, crop_top, crop_width, crop_height)
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
def mask_to_png
|
|
@@ -93,4 +96,4 @@ class PSD::Image
|
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
end
|
|
96
|
-
end
|
|
99
|
+
end
|
|
@@ -16,7 +16,7 @@ class PSD
|
|
|
16
16
|
def parse_channel_data!
|
|
17
17
|
@line_index = 0
|
|
18
18
|
|
|
19
|
-
PSD.logger.debug "Parsing
|
|
19
|
+
PSD.logger.debug "Parsing RLE channel ##{@ch_info[:id]}: file position = #{@file.tell}, image position = #{@chan_pos}, line = #{@line_index}"
|
|
20
20
|
decode_rle_channel
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -23,7 +23,7 @@ class PSD
|
|
|
23
23
|
@line_index = 0
|
|
24
24
|
|
|
25
25
|
channels.times do |i|
|
|
26
|
-
PSD.logger.debug "Parsing RLE channel ##{i}: position = #{@chan_pos}, line = #{@line_index}"
|
|
26
|
+
PSD.logger.debug "Parsing RLE channel ##{i}: file position = #{@file.tell}, image position = #{@chan_pos}, line = #{@line_index}"
|
|
27
27
|
decode_rle_channel
|
|
28
28
|
@line_index += height
|
|
29
29
|
end
|
data/lib/psd/layer/helpers.rb
CHANGED
|
@@ -23,6 +23,30 @@ class PSD
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Is the layer completely locked?
|
|
27
|
+
def all_locked?
|
|
28
|
+
return false unless info.has_key?(:locked)
|
|
29
|
+
info[:locked].all_locked
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Is the layer position locked?
|
|
33
|
+
def position_locked?
|
|
34
|
+
return false unless info.has_key?(:locked)
|
|
35
|
+
info[:locked].position_locked
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Is the layer composite locked?
|
|
39
|
+
def composite_locked?
|
|
40
|
+
return false unless info.has_key?(:locked)
|
|
41
|
+
info[:locked].composite_locked
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Is the layer transparency locked?
|
|
45
|
+
def transparency_locked?
|
|
46
|
+
return false unless info.has_key?(:locked)
|
|
47
|
+
info[:locked].transparency_locked
|
|
48
|
+
end
|
|
49
|
+
|
|
26
50
|
# Is this layer visible?
|
|
27
51
|
def visible?
|
|
28
52
|
@visible
|
data/lib/psd/layer/info.rb
CHANGED
|
@@ -17,6 +17,7 @@ class PSD
|
|
|
17
17
|
layer_id: LayerID,
|
|
18
18
|
fill_opacity: FillOpacity,
|
|
19
19
|
placed_layer: PlacedLayer,
|
|
20
|
+
locked: Locked,
|
|
20
21
|
vector_mask: VectorMask,
|
|
21
22
|
vector_mask_2: VectorMask2,
|
|
22
23
|
vector_stroke: VectorStroke,
|
|
@@ -52,6 +53,7 @@ class PSD
|
|
|
52
53
|
|
|
53
54
|
i = info.new(self, length)
|
|
54
55
|
@adjustments[name] = LazyExecute.new(i, @file).now(:skip).later(:parse)
|
|
56
|
+
|
|
55
57
|
key_parseable = true and break
|
|
56
58
|
end
|
|
57
59
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative '../layer_info'
|
|
2
|
+
|
|
3
|
+
class PSD
|
|
4
|
+
class Locked < LayerInfo
|
|
5
|
+
@key = 'lspf'
|
|
6
|
+
|
|
7
|
+
attr_reader :all_locked, :transparency_locked, :composite_locked, :position_locked
|
|
8
|
+
|
|
9
|
+
def parse
|
|
10
|
+
locked = @file.read_int
|
|
11
|
+
|
|
12
|
+
@transparency_locked = (locked & (0x01 << 0)) > 0 || locked == -2147483648
|
|
13
|
+
@composite_locked = (locked & (0x01 << 1)) > 0 || locked == -2147483648
|
|
14
|
+
@position_locked = (locked & (0x01 << 2)) > 0 || locked == -2147483648
|
|
15
|
+
|
|
16
|
+
@all_locked = @transparency_locked && @composite_locked && @position_locked
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/psd/layer_mask.rb
CHANGED
data/lib/psd/node_group.rb
CHANGED
|
@@ -46,6 +46,10 @@ class PSD::Node
|
|
|
46
46
|
@children.each{ |c| c.show! }
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
def empty?
|
|
50
|
+
@children.empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
49
53
|
# Export this layer and it's children to a hash recursively.
|
|
50
54
|
def to_hash
|
|
51
55
|
super.merge({
|
|
@@ -64,10 +68,11 @@ class PSD::Node
|
|
|
64
68
|
private
|
|
65
69
|
|
|
66
70
|
def get_dimensions
|
|
67
|
-
|
|
68
|
-
@
|
|
69
|
-
@
|
|
70
|
-
@
|
|
71
|
+
children = @children.reject(&:empty?)
|
|
72
|
+
@left = children.map(&:left).min || 0
|
|
73
|
+
@top = children.map(&:top).min || 0
|
|
74
|
+
@bottom = children.map(&:bottom).max || 0
|
|
75
|
+
@right = children.map(&:right).max || 0
|
|
71
76
|
end
|
|
72
77
|
end
|
|
73
78
|
end
|
data/lib/psd/node_layer.rb
CHANGED
|
@@ -6,7 +6,7 @@ class PSD
|
|
|
6
6
|
alias :orig_to_png :to_png
|
|
7
7
|
def to_png
|
|
8
8
|
return build_png if group?
|
|
9
|
-
layer.image.
|
|
9
|
+
layer.image.to_png_with_mask
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def build_png(png=nil)
|
|
@@ -14,7 +14,7 @@ class PSD
|
|
|
14
14
|
|
|
15
15
|
children.reverse.each do |c|
|
|
16
16
|
next unless c.visible?
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
if c.group?
|
|
19
19
|
if c.blending_mode == 'passthru'
|
|
20
20
|
c.build_png(png)
|
|
@@ -23,10 +23,10 @@ class PSD
|
|
|
23
23
|
end
|
|
24
24
|
else
|
|
25
25
|
compose!(
|
|
26
|
-
c,
|
|
27
|
-
png,
|
|
28
|
-
c.image.to_png_with_mask,
|
|
29
|
-
PSD::Util.clamp(c.left.to_i, 0, png.width),
|
|
26
|
+
c,
|
|
27
|
+
png,
|
|
28
|
+
c.image.to_png_with_mask,
|
|
29
|
+
PSD::Util.clamp(c.left.to_i, 0, png.width),
|
|
30
30
|
PSD::Util.clamp(c.top.to_i, 0, png.height)
|
|
31
31
|
)
|
|
32
32
|
end
|
|
@@ -59,11 +59,11 @@ class PSD
|
|
|
59
59
|
other.width.times do |x|
|
|
60
60
|
base_x = x + offset_x
|
|
61
61
|
base_y = y + offset_y
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
next if base_x < 0 || base_y < 0 || base_x >= base.width || base_y >= base.height
|
|
64
64
|
|
|
65
65
|
color = Compose.send(
|
|
66
|
-
blending_mode,
|
|
66
|
+
blending_mode,
|
|
67
67
|
other[x, y],
|
|
68
68
|
base[base_x, base_y],
|
|
69
69
|
opacity: layer.opacity,
|
|
@@ -76,4 +76,4 @@ class PSD
|
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
|
-
end
|
|
79
|
+
end
|
data/lib/psd/resources/slices.rb
CHANGED
|
@@ -1,27 +1,132 @@
|
|
|
1
1
|
class PSD
|
|
2
2
|
class Resource
|
|
3
3
|
class Section
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
class Slices < Section
|
|
5
|
+
attr_reader :data, :version
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
def self.id; 1050; end
|
|
8
|
+
def self.name; :slices; end
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@descriptor_version = @file.read_int
|
|
13
|
-
@resource.data = self
|
|
10
|
+
def parse
|
|
11
|
+
@version = @file.read_int
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
case @version
|
|
14
|
+
when 6 then parse_legacy
|
|
15
|
+
when 7, 8 then
|
|
16
|
+
descriptor_version = @file.read_int
|
|
17
|
+
@data = Descriptor.new(@file).parse
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
normalize_data!
|
|
21
|
+
|
|
22
|
+
@resource.data = self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_a
|
|
26
|
+
return [] if @data.nil?
|
|
27
|
+
@data[:slices]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def parse_legacy
|
|
33
|
+
@data = {}
|
|
34
|
+
|
|
35
|
+
@data[:bounds] = {}.tap do |bounds|
|
|
36
|
+
bounds[:top] = @file.read_int
|
|
37
|
+
bounds[:left] = @file.read_int
|
|
38
|
+
bounds[:bottom] = @file.read_int
|
|
39
|
+
bounds[:right] = @file.read_int
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@data[:name] = @file.read_unicode_string
|
|
18
43
|
|
|
44
|
+
@data[:slices] = []
|
|
45
|
+
slices_count = @file.read_int
|
|
46
|
+
slices_count.times do
|
|
47
|
+
@data[:slices] << {}.tap do |slice|
|
|
48
|
+
slice[:id] = @file.read_int
|
|
49
|
+
slice[:group_id] = @file.read_int
|
|
50
|
+
slice[:origin] = @file.read_int
|
|
51
|
+
|
|
52
|
+
slice[:associated_layer_id] = (slice[:origin] == 1 ? @file.read_int : nil)
|
|
53
|
+
|
|
54
|
+
slice[:name] = @file.read_unicode_string
|
|
55
|
+
slice[:type] = @file.read_int
|
|
56
|
+
|
|
57
|
+
slice[:bounds] = {}.tap do |bounds|
|
|
58
|
+
bounds[:left] = @file.read_int
|
|
59
|
+
bounds[:top] = @file.read_int
|
|
60
|
+
bounds[:right] = @file.read_int
|
|
61
|
+
bounds[:bottom] = @file.read_int
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
slice[:url] = @file.read_unicode_string
|
|
65
|
+
slice[:target] = @file.read_unicode_string
|
|
66
|
+
slice[:message] = @file.read_unicode_string
|
|
67
|
+
slice[:alt] = @file.read_unicode_string
|
|
68
|
+
|
|
69
|
+
slice[:cell_text_is_html] = @file.read_boolean
|
|
70
|
+
slice[:cell_text] = @file.read_unicode_string
|
|
71
|
+
|
|
72
|
+
slice[:horizontal_alignment] = @file.read_int
|
|
73
|
+
slice[:vertical_alignment] = @file.read_int
|
|
74
|
+
|
|
75
|
+
a, r, g, b = 4.times.map { @file.read_byte }
|
|
76
|
+
slice[:color] = ChunkyPNG::Color.rgba(r, g, b, a)
|
|
77
|
+
end
|
|
19
78
|
end
|
|
79
|
+
end
|
|
20
80
|
|
|
21
|
-
|
|
22
|
-
|
|
81
|
+
# Normalizes the data between version 6 and versions 7/8 to make it easier
|
|
82
|
+
# to deal with the discrepancies. We use version 6 as the base.
|
|
83
|
+
def normalize_data!
|
|
84
|
+
return if @version == 6
|
|
85
|
+
data = {}
|
|
86
|
+
data[:bounds] = {
|
|
87
|
+
top: @data['bounds']['Top '],
|
|
88
|
+
left: @data['bounds']['Left'],
|
|
89
|
+
bottom: @data['bounds']['Btom'],
|
|
90
|
+
right: @data['bounds']['Rght']
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
data[:name] = @data['baseName']
|
|
94
|
+
|
|
95
|
+
data[:slices] = @data['slices'].map do |slice|
|
|
96
|
+
{
|
|
97
|
+
id: slice['sliceID'],
|
|
98
|
+
group_id: slice['groupID'],
|
|
99
|
+
origin: slice['origin'],
|
|
100
|
+
associated_layer_id: nil,
|
|
101
|
+
name: '',
|
|
102
|
+
type: slice['Type'],
|
|
103
|
+
bounds: {
|
|
104
|
+
left: slice['bounds']['Left'],
|
|
105
|
+
top: slice['bounds']['Top '],
|
|
106
|
+
right: slice['bounds']['Rght'],
|
|
107
|
+
bottom: slice['bounds']['Btom']
|
|
108
|
+
},
|
|
109
|
+
url: slice['url'],
|
|
110
|
+
target: '',
|
|
111
|
+
message: slice['Msge'],
|
|
112
|
+
alt: slice['altTag'],
|
|
113
|
+
cell_text_is_html: slice['cellTextIsHTML'],
|
|
114
|
+
cell_text: slice['cellText'],
|
|
115
|
+
horizontal_alignment: slice['horzAlign'],
|
|
116
|
+
vertical_alignment: slice['vertAlign'],
|
|
117
|
+
color: nil,
|
|
118
|
+
outset: {
|
|
119
|
+
top: slice['topOutset'],
|
|
120
|
+
left: slice['leftOutset'],
|
|
121
|
+
bottom: slice['bottomOutset'],
|
|
122
|
+
right: slice['rightOutset']
|
|
123
|
+
}
|
|
124
|
+
}
|
|
23
125
|
end
|
|
126
|
+
|
|
127
|
+
@data = data
|
|
24
128
|
end
|
|
129
|
+
end
|
|
25
130
|
end
|
|
26
131
|
end
|
|
27
132
|
end
|
data/lib/psd/version.rb
CHANGED
|
Binary file
|
|
Binary file
|
data/spec/hierarchy_spec.rb
CHANGED
|
@@ -27,7 +27,7 @@ describe "Hierarchy" do
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
it "should properly identify the root node" do
|
|
30
|
-
expect(@tree.root?).to
|
|
30
|
+
expect(@tree.root?).to be true
|
|
31
31
|
expect(@tree.root).to be @tree
|
|
32
32
|
expect(@tree.children.last.root).to be @tree
|
|
33
33
|
end
|
|
@@ -105,4 +105,24 @@ describe "Hierarchy" do
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
|
+
|
|
109
|
+
# https://github.com/layervault/psd.rb/pull/54
|
|
110
|
+
describe "Size Calculation" do
|
|
111
|
+
before(:each) do
|
|
112
|
+
@psd = PSD.new('spec/files/empty-layer.psd')
|
|
113
|
+
@psd.parse!
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'should correctly identify empty nodes' do
|
|
117
|
+
expect(@psd.tree.children_at_path('group/empty layer').first).to be_empty
|
|
118
|
+
expect(@psd.tree.children[0]).to_not be_empty
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "should correctly calculate the size of a group" do
|
|
122
|
+
expect(@psd.tree.children[0].width).to eq 100
|
|
123
|
+
expect(@psd.tree.children[0].height).to eq 100
|
|
124
|
+
expect(@psd.tree.children[0].left).to eq 450
|
|
125
|
+
expect(@psd.tree.children[0].top).to eq 450
|
|
126
|
+
end
|
|
127
|
+
end
|
|
108
128
|
end
|
data/spec/image_spec.rb
CHANGED
|
@@ -1,39 +1,33 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe 'Image Exporting' do
|
|
4
|
-
|
|
5
|
-
class PSD::Image
|
|
6
|
-
attr_accessor :pixel_data
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
@psd = PSD.new('spec/files/pixel.psd')
|
|
10
|
-
end
|
|
4
|
+
let(:psd) { PSD.new('spec/files/pixel.psd') }
|
|
11
5
|
|
|
12
6
|
describe "the full preview image" do
|
|
13
7
|
it "should successfully parse the image data" do
|
|
14
|
-
|
|
15
|
-
expect(
|
|
16
|
-
expect(
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
8
|
+
psd.parse!
|
|
9
|
+
expect(psd).to be_parsed
|
|
10
|
+
expect(psd.image).to_not be_nil
|
|
11
|
+
expect(psd.image.width).to eq(1)
|
|
12
|
+
expect(psd.image.height).to eq(1)
|
|
13
|
+
expect(psd.image.pixel_data).to eq([ChunkyPNG::Color.rgba(0, 100, 200, 255)])
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
it "should be able to skip to the image" do
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
25
|
-
expect(
|
|
26
|
-
expect(
|
|
17
|
+
expect(psd).to_not be_parsed
|
|
18
|
+
expect(psd.image.width).to eq(1)
|
|
19
|
+
expect(psd.image.height).to eq(1)
|
|
20
|
+
expect(psd.image.pixel_data).to eq([ChunkyPNG::Color.rgba(0, 100, 200, 255)])
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
describe "as PNG" do
|
|
30
24
|
it "should produce a valid PNG object" do
|
|
31
|
-
expect(
|
|
25
|
+
expect(psd.image.to_png).to be_an_instance_of(ChunkyPNG::Canvas)
|
|
32
26
|
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
27
|
+
expect(psd.image.to_png.width).to eq(1)
|
|
28
|
+
expect(psd.image.to_png.height).to eq(1)
|
|
35
29
|
expect(
|
|
36
|
-
ChunkyPNG::Color.to_truecolor_alpha_bytes(
|
|
30
|
+
ChunkyPNG::Color.to_truecolor_alpha_bytes(psd.image.to_png[0,0])
|
|
37
31
|
).to eq([0, 100, 200, 255])
|
|
38
32
|
end
|
|
39
33
|
end
|
|
@@ -41,10 +35,10 @@ describe 'Image Exporting' do
|
|
|
41
35
|
|
|
42
36
|
describe "layer images" do
|
|
43
37
|
it "should successfully parse the image data" do
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
psd.options[:parse_layer_images] = true
|
|
39
|
+
psd.parse!
|
|
46
40
|
|
|
47
|
-
image =
|
|
41
|
+
image = psd.tree.children.first.image
|
|
48
42
|
expect(image.width).to eq(1)
|
|
49
43
|
expect(image.height).to eq(1)
|
|
50
44
|
|
|
@@ -53,10 +47,10 @@ describe 'Image Exporting' do
|
|
|
53
47
|
|
|
54
48
|
describe "as PNG" do
|
|
55
49
|
it "should produce a valid PNG object" do
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
psd.options[:parse_layer_images] = true
|
|
51
|
+
psd.parse!
|
|
58
52
|
|
|
59
|
-
png =
|
|
53
|
+
png = psd.tree.children.first.image.to_png
|
|
60
54
|
expect(png).to be_an_instance_of(ChunkyPNG::Canvas)
|
|
61
55
|
expect(png.width).to eq(1)
|
|
62
56
|
expect(png.height).to eq(1)
|
|
@@ -64,6 +58,28 @@ describe 'Image Exporting' do
|
|
|
64
58
|
ChunkyPNG::Color.to_truecolor_alpha_bytes(png[0,0])
|
|
65
59
|
).to eq([0, 100, 200, 255])
|
|
66
60
|
end
|
|
61
|
+
|
|
62
|
+
it "memorizes the png instance" do
|
|
63
|
+
psd.options[:parse_layer_images] = true
|
|
64
|
+
psd.parse!
|
|
65
|
+
|
|
66
|
+
node = psd.tree.children.first
|
|
67
|
+
png = node.image.to_png
|
|
68
|
+
|
|
69
|
+
expect(png).to be_an_instance_of(ChunkyPNG::Canvas)
|
|
70
|
+
expect(node.image.to_png.__id__).to eq(png.__id__)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "memorizes the png_with_mask instance" do
|
|
74
|
+
psd = PSD.new('spec/files/path.psd', parse_layer_images: true)
|
|
75
|
+
psd.parse!
|
|
76
|
+
|
|
77
|
+
node = psd.tree.children.first
|
|
78
|
+
png = node.image.to_png_with_mask
|
|
79
|
+
|
|
80
|
+
expect(png).to be_an_instance_of(ChunkyPNG::Canvas)
|
|
81
|
+
expect(node.image.to_png_with_mask.__id__).to eq(png.__id__)
|
|
82
|
+
end
|
|
67
83
|
end
|
|
68
84
|
end
|
|
69
|
-
end
|
|
85
|
+
end
|
data/spec/locked_spec.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Locked' do
|
|
4
|
+
it "should parse locked layer info" do
|
|
5
|
+
psd = PSD.new('spec/files/locked.psd')
|
|
6
|
+
psd.parse!
|
|
7
|
+
|
|
8
|
+
expect(psd.tree.children[0].position_locked?).to eq(true)
|
|
9
|
+
expect(psd.tree.children[0].all_locked?).to eq(true)
|
|
10
|
+
expect(psd.tree.children[0].composite_locked?).to eq(true)
|
|
11
|
+
expect(psd.tree.children[0].transparency_locked?).to eq(true)
|
|
12
|
+
|
|
13
|
+
expect(psd.tree.children[1].position_locked?).to eq(true)
|
|
14
|
+
expect(psd.tree.children[1].all_locked?).to eq(false)
|
|
15
|
+
expect(psd.tree.children[1].composite_locked?).to eq(false)
|
|
16
|
+
expect(psd.tree.children[1].transparency_locked?).to eq(false)
|
|
17
|
+
|
|
18
|
+
expect(psd.tree.children[2].position_locked?).to eq(false)
|
|
19
|
+
expect(psd.tree.children[2].all_locked?).to eq(false)
|
|
20
|
+
expect(psd.tree.children[2].composite_locked?).to eq(true)
|
|
21
|
+
expect(psd.tree.children[2].transparency_locked?).to eq(false)
|
|
22
|
+
|
|
23
|
+
expect(psd.tree.children[3].position_locked?).to eq(false)
|
|
24
|
+
expect(psd.tree.children[3].all_locked?).to eq(false)
|
|
25
|
+
expect(psd.tree.children[3].composite_locked?).to eq(false)
|
|
26
|
+
expect(psd.tree.children[3].transparency_locked?).to eq(true)
|
|
27
|
+
|
|
28
|
+
expect(psd.tree.children[4].position_locked?).to eq(true)
|
|
29
|
+
expect(psd.tree.children[4].all_locked?).to eq(false)
|
|
30
|
+
expect(psd.tree.children[4].composite_locked?).to eq(true)
|
|
31
|
+
expect(psd.tree.children[4].transparency_locked?).to eq(false)
|
|
32
|
+
|
|
33
|
+
expect(psd.tree.children[5].position_locked?).to eq(true)
|
|
34
|
+
expect(psd.tree.children[5].all_locked?).to eq(false)
|
|
35
|
+
expect(psd.tree.children[5].composite_locked?).to eq(false)
|
|
36
|
+
expect(psd.tree.children[5].transparency_locked?).to eq(true)
|
|
37
|
+
|
|
38
|
+
expect(psd.tree.children[6].position_locked?).to eq(false)
|
|
39
|
+
expect(psd.tree.children[6].all_locked?).to eq(false)
|
|
40
|
+
expect(psd.tree.children[6].composite_locked?).to eq(false)
|
|
41
|
+
expect(psd.tree.children[6].transparency_locked?).to eq(false)
|
|
42
|
+
|
|
43
|
+
expect(psd.tree.children[7].position_locked?).to eq(true)
|
|
44
|
+
expect(psd.tree.children[7].all_locked?).to eq(true)
|
|
45
|
+
expect(psd.tree.children[7].composite_locked?).to eq(true)
|
|
46
|
+
expect(psd.tree.children[7].transparency_locked?).to eq(true)
|
|
47
|
+
|
|
48
|
+
expect(psd.tree.children[7].children[0].position_locked?).to eq(false)
|
|
49
|
+
expect(psd.tree.children[7].children[0].all_locked?).to eq(false)
|
|
50
|
+
expect(psd.tree.children[7].children[0].composite_locked?).to eq(false)
|
|
51
|
+
expect(psd.tree.children[7].children[0].transparency_locked?).to eq(false)
|
|
52
|
+
|
|
53
|
+
expect(psd.tree.children[7].children[1].position_locked?).to eq(true)
|
|
54
|
+
expect(psd.tree.children[7].children[1].all_locked?).to eq(true)
|
|
55
|
+
expect(psd.tree.children[7].children[1].composite_locked?).to eq(true)
|
|
56
|
+
expect(psd.tree.children[7].children[1].transparency_locked?).to eq(true)
|
|
57
|
+
|
|
58
|
+
expect(psd.tree.children[7].children[1].children[0].position_locked?).to eq(true)
|
|
59
|
+
expect(psd.tree.children[7].children[1].children[0].all_locked?).to eq(false)
|
|
60
|
+
expect(psd.tree.children[7].children[1].children[0].composite_locked?).to eq(false)
|
|
61
|
+
expect(psd.tree.children[7].children[1].children[0].transparency_locked?).to eq(false)
|
|
62
|
+
|
|
63
|
+
expect(psd.tree.children[8].position_locked?).to eq(true)
|
|
64
|
+
expect(psd.tree.children[8].all_locked?).to eq(false)
|
|
65
|
+
expect(psd.tree.children[8].composite_locked?).to eq(false)
|
|
66
|
+
expect(psd.tree.children[8].transparency_locked?).to eq(true)
|
|
67
|
+
|
|
68
|
+
expect(psd.tree.children[9].position_locked?).to eq(true)
|
|
69
|
+
expect(psd.tree.children[9].all_locked?).to eq(true)
|
|
70
|
+
expect(psd.tree.children[9].composite_locked?).to eq(true)
|
|
71
|
+
expect(psd.tree.children[9].transparency_locked?).to eq(true)
|
|
72
|
+
|
|
73
|
+
expect(psd.tree.children[12].position_locked?).to eq(false)
|
|
74
|
+
expect(psd.tree.children[12].all_locked?).to eq(false)
|
|
75
|
+
expect(psd.tree.children[12].composite_locked?).to eq(false)
|
|
76
|
+
expect(psd.tree.children[12].transparency_locked?).to eq(false)
|
|
77
|
+
end
|
|
78
|
+
end
|
data/spec/parsing_spec.rb
CHANGED
|
@@ -131,7 +131,7 @@ describe 'Parsing' do
|
|
|
131
131
|
expect(blend_mode.mode).to eq('normal')
|
|
132
132
|
expect(blend_mode.opacity).to eq(255)
|
|
133
133
|
expect(blend_mode.opacity_percentage).to eq(100)
|
|
134
|
-
expect(blend_mode.visible).to
|
|
134
|
+
expect(blend_mode.visible).to be true
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
it "should parse all layer comps" do
|
data/spec/slices_spec.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe 'Slices' do
|
|
4
|
-
|
|
5
4
|
describe "Handle file with slice properly" do
|
|
6
|
-
|
|
7
|
-
|
|
8
5
|
it "should successfully parse a the PSD file of version 7 or above and has slices" do
|
|
9
6
|
psd = PSD.new('spec/files/slices.psd')
|
|
10
7
|
psd.parse!
|
|
@@ -21,11 +18,10 @@ describe 'Slices' do
|
|
|
21
18
|
# Bounds of each slice should not be nil
|
|
22
19
|
psd.resources[:slices].data.to_a.each do |slice|
|
|
23
20
|
expect(slice).to_not be_nil
|
|
24
|
-
expect(slice[
|
|
21
|
+
expect(slice[:bounds]).to_not be_nil
|
|
25
22
|
end
|
|
26
23
|
end
|
|
27
24
|
|
|
28
|
-
|
|
29
25
|
it "should successfully parse a the PSD file of version 6 and has slices" do
|
|
30
26
|
psd = PSD.new('spec/files/pixel.psd')
|
|
31
27
|
psd.parse!
|
|
@@ -39,14 +35,23 @@ describe 'Slices' do
|
|
|
39
35
|
# But slices version should be equal to 6
|
|
40
36
|
expect(psd.resources[:slices].data.version).to eq 6
|
|
41
37
|
|
|
42
|
-
#
|
|
43
|
-
|
|
38
|
+
# Bounds should not be nil
|
|
39
|
+
psd.resources[:slices].data.to_a.each do |slice|
|
|
40
|
+
expect(slice).to_not be_nil
|
|
41
|
+
expect(slice[:bounds]).to_not be_nil
|
|
42
|
+
end
|
|
44
43
|
end
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
describe "Handle file without slices properly" do
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
it "should successfully parse a PSD file which does not have slices" do
|
|
48
|
+
psd = PSD.new('spec/files/simplest.psd')
|
|
49
|
+
psd.parse!
|
|
50
|
+
|
|
51
|
+
expect(psd).to be_parsed
|
|
52
|
+
|
|
53
|
+
expect(psd.resources[:slices].data.to_a.size).to be 1
|
|
54
|
+
expect(psd.resources[:slices].data.to_a[0][:id]).to be 0
|
|
55
|
+
end
|
|
51
56
|
end
|
|
52
57
|
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.
|
|
4
|
+
version: 1.5.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-
|
|
12
|
+
date: 2013-12-27 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rake
|
|
@@ -180,6 +180,7 @@ files:
|
|
|
180
180
|
- lib/psd/layer_info/layer_name_source.rb
|
|
181
181
|
- lib/psd/layer_info/layer_section_divider.rb
|
|
182
182
|
- lib/psd/layer_info/legacy_typetool.rb
|
|
183
|
+
- lib/psd/layer_info/locked.rb
|
|
183
184
|
- lib/psd/layer_info/metadata_setting.rb
|
|
184
185
|
- lib/psd/layer_info/object_effects.rb
|
|
185
186
|
- lib/psd/layer_info/placed_layer.rb
|
|
@@ -216,7 +217,9 @@ files:
|
|
|
216
217
|
- lib/psd/util.rb
|
|
217
218
|
- lib/psd/version.rb
|
|
218
219
|
- psd.gemspec
|
|
220
|
+
- spec/files/empty-layer.psd
|
|
219
221
|
- spec/files/example.psd
|
|
222
|
+
- spec/files/locked.psd
|
|
220
223
|
- spec/files/one_layer.psd
|
|
221
224
|
- spec/files/path.psd
|
|
222
225
|
- spec/files/pixel.psd
|
|
@@ -226,6 +229,7 @@ files:
|
|
|
226
229
|
- spec/hierarchy_spec.rb
|
|
227
230
|
- spec/image_spec.rb
|
|
228
231
|
- spec/lazy_execute_spec.rb
|
|
232
|
+
- spec/locked_spec.rb
|
|
229
233
|
- spec/parsing_spec.rb
|
|
230
234
|
- spec/psd_spec.rb
|
|
231
235
|
- spec/slices_spec.rb
|
|
@@ -256,7 +260,9 @@ signing_key:
|
|
|
256
260
|
specification_version: 4
|
|
257
261
|
summary: General purpose library for parsing Photoshop files
|
|
258
262
|
test_files:
|
|
263
|
+
- spec/files/empty-layer.psd
|
|
259
264
|
- spec/files/example.psd
|
|
265
|
+
- spec/files/locked.psd
|
|
260
266
|
- spec/files/one_layer.psd
|
|
261
267
|
- spec/files/path.psd
|
|
262
268
|
- spec/files/pixel.psd
|
|
@@ -266,6 +272,7 @@ test_files:
|
|
|
266
272
|
- spec/hierarchy_spec.rb
|
|
267
273
|
- spec/image_spec.rb
|
|
268
274
|
- spec/lazy_execute_spec.rb
|
|
275
|
+
- spec/locked_spec.rb
|
|
269
276
|
- spec/parsing_spec.rb
|
|
270
277
|
- spec/psd_spec.rb
|
|
271
278
|
- spec/slices_spec.rb
|