psd 2.1.2 → 3.9.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 +5 -5
- data/.travis.yml +2 -0
- data/README.md +60 -20
- data/lib/psd/blend_mode.rb +46 -38
- data/lib/psd/channel_image.rb +11 -5
- data/lib/psd/color.rb +14 -5
- data/lib/psd/descriptor.rb +39 -16
- data/lib/psd/file.rb +9 -6
- data/lib/psd/header.rb +38 -33
- data/lib/psd/helpers.rb +2 -2
- data/lib/psd/image.rb +21 -16
- data/lib/psd/image_formats/layer_rle.rb +2 -2
- data/lib/psd/image_formats/rle.rb +4 -10
- data/lib/psd/image_modes/cmyk.rb +18 -5
- data/lib/psd/image_modes/greyscale.rb +6 -1
- data/lib/psd/image_modes/rgb.rb +17 -5
- data/lib/psd/layer/blend_modes.rb +15 -13
- data/lib/psd/layer/helpers.rb +8 -10
- data/lib/psd/layer/info/black_white.rb +33 -0
- data/lib/psd/{layer_info → layer/info}/blend_clipping_elements.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/blend_interior_elements.rb +4 -2
- data/lib/psd/layer/info/brightness_contrast.rb +21 -0
- data/lib/psd/layer/info/channel_blending_restrictions.rb +27 -0
- data/lib/psd/layer/info/channel_mixer.rb +27 -0
- data/lib/psd/layer/info/color_balance.rb +23 -0
- data/lib/psd/layer/info/color_lookup.rb +14 -0
- data/lib/psd/layer/info/content_generator.rb +42 -0
- data/lib/psd/layer/info/curves.rb +78 -0
- data/lib/psd/layer/info/effects_layer.rb +174 -0
- data/lib/psd/layer/info/exposure.rb +20 -0
- data/lib/psd/{layer_info → layer/info}/fill_opacity.rb +4 -2
- data/lib/psd/layer/info/gradient_fill.rb +14 -0
- data/lib/psd/layer/info/gradient_map.rb +69 -0
- data/lib/psd/layer/info/hue_saturation.rb +41 -0
- data/lib/psd/layer/info/invert.rb +17 -0
- data/lib/psd/layer/info/knockout.rb +22 -0
- data/lib/psd/layer/info/layer_effects.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/layer_group.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/layer_id.rb +4 -2
- data/lib/psd/layer/info/layer_mask_as_global_mask.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/layer_name_source.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/layer_section_divider.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/legacy_typetool.rb +6 -4
- data/lib/psd/layer/info/levels.rb +48 -0
- data/lib/psd/{layer_info → layer/info}/locked.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/metadata_setting.rb +5 -3
- data/lib/psd/{layer_info → layer/info}/object_effects.rb +5 -5
- data/lib/psd/layer/info/pattern.rb +14 -0
- data/lib/psd/layer/info/pattern_fill.rb +15 -0
- data/lib/psd/layer/info/photo_filter.rb +44 -0
- data/lib/psd/{layer_info → layer/info}/placed_layer.rb +4 -2
- data/lib/psd/layer/info/posterize.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/reference_point.rb +4 -2
- data/lib/psd/layer/info/selective_color.rb +32 -0
- data/lib/psd/layer/info/sheet_color.rb +36 -0
- data/lib/psd/layer/info/solid_color.rb +36 -0
- data/lib/psd/layer/info/threshold.rb +16 -0
- data/lib/psd/layer/info/transparency_shapes_layer.rb +16 -0
- data/lib/psd/{layer_info → layer/info}/typetool.rb +27 -13
- data/lib/psd/{layer_info → layer/info}/unicode_name.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/vector_mask.rb +7 -5
- data/lib/psd/layer/info/vector_mask_as_global_mask.rb +16 -0
- data/lib/psd/layer/info/vector_origination.rb +14 -0
- data/lib/psd/{layer_info → layer/info}/vector_stroke.rb +4 -2
- data/lib/psd/{layer_info → layer/info}/vector_stroke_content.rb +4 -2
- data/lib/psd/layer/info/vibrance.rb +22 -0
- data/lib/psd/layer/info.rb +106 -19
- data/lib/psd/layer/position_and_channels.rb +2 -6
- data/lib/psd/layer.rb +13 -16
- data/lib/psd/layer_info.rb +0 -4
- data/lib/psd/layer_mask.rb +61 -50
- data/lib/psd/lazy_execute.rb +5 -1
- data/lib/psd/mask.rb +7 -1
- data/lib/psd/node.rb +142 -49
- data/lib/psd/nodes/ancestry.rb +7 -6
- data/lib/psd/nodes/build_preview.rb +4 -4
- data/lib/psd/nodes/group.rb +36 -0
- data/lib/psd/nodes/layer.rb +45 -0
- data/lib/psd/nodes/layer_comps.rb +93 -0
- data/lib/psd/nodes/locking.rb +36 -0
- data/lib/psd/nodes/root.rb +87 -0
- data/lib/psd/nodes/search.rb +8 -65
- data/lib/psd/path_record.rb +5 -70
- data/lib/psd/renderer/blender.rb +10 -5
- data/lib/psd/renderer/cairo_helpers.rb +46 -0
- data/lib/psd/renderer/canvas.rb +41 -20
- data/lib/psd/renderer/canvas_management.rb +2 -2
- data/lib/psd/renderer/clipping_mask.rb +5 -4
- data/lib/psd/renderer/compose.rb +59 -69
- data/lib/psd/renderer/layer_styles/color_overlay.rb +45 -27
- data/lib/psd/renderer/layer_styles.rb +15 -5
- data/lib/psd/renderer/mask.rb +26 -22
- data/lib/psd/renderer/mask_canvas.rb +12 -0
- data/lib/psd/renderer/vector_shape.rb +239 -0
- data/lib/psd/renderer.rb +18 -7
- data/lib/psd/resource_section.rb +13 -6
- data/lib/psd/resources/base.rb +33 -0
- data/lib/psd/resources/guides.rb +6 -4
- data/lib/psd/resources/layer_comps.rb +6 -4
- data/lib/psd/resources/resolution_info.rb +48 -0
- data/lib/psd/resources/saved_path.rb +19 -0
- data/lib/psd/resources/slices.rb +7 -5
- data/lib/psd/resources/work_path.rb +22 -0
- data/lib/psd/resources/xmp_metadata.rb +46 -0
- data/lib/psd/resources.rb +17 -21
- data/lib/psd/slice.rb +44 -0
- data/lib/psd/slices.rb +13 -0
- data/lib/psd/util.rb +4 -2
- data/lib/psd/version.rb +1 -1
- data/lib/psd.rb +31 -45
- data/psd.gemspec +2 -3
- data/spec/files/alignment_modes.psd +0 -0
- data/spec/files/blendmodes.psd +0 -0
- data/spec/files/example.psb +0 -0
- data/spec/hierarchy_spec.rb +5 -0
- data/spec/locked_spec.rb +8 -8
- data/spec/psb_parsing_spec.rb +57 -0
- data/spec/text_spec.rb +13 -1
- metadata +115 -75
- data/circle.yml +0 -6
- data/lib/psd/layer_info/vector_mask_2.rb +0 -10
- data/lib/psd/node_exporting.rb +0 -20
- data/lib/psd/node_group.rb +0 -86
- data/lib/psd/node_layer.rb +0 -81
- data/lib/psd/node_root.rb +0 -93
- data/lib/psd/nodes/has_children.rb +0 -13
- data/lib/psd/nodes/lock_to_origin.rb +0 -7
- data/lib/psd/nodes/parse_layers.rb +0 -18
- data/lib/psd/renderer/layer_styles/drop_shadow.rb +0 -75
- data/lib/psd/section.rb +0 -26
@@ -1,11 +1,13 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
2
|
+
require 'psd/layer_info'
|
3
3
|
|
4
4
|
class PSD
|
5
5
|
# Parses and provides information about text areas within layers in
|
6
6
|
# the document.
|
7
7
|
class TypeTool < LayerInfo
|
8
|
-
|
8
|
+
def self.should_parse?(key)
|
9
|
+
key == 'TySh'
|
10
|
+
end
|
9
11
|
|
10
12
|
# Parse all of the text data in the layer.
|
11
13
|
def parse
|
@@ -16,9 +18,6 @@ class PSD
|
|
16
18
|
descriptor_version = @file.read_int
|
17
19
|
|
18
20
|
@data[:text] = Descriptor.new(@file).parse
|
19
|
-
@data[:text]['EngineData']
|
20
|
-
.encode!('UTF-8', 'MacRoman', invalid: :replace, undef: :replace)
|
21
|
-
.delete!("\000")
|
22
21
|
|
23
22
|
@data[:engine_data] = nil
|
24
23
|
begin
|
@@ -42,12 +41,7 @@ class PSD
|
|
42
41
|
# Extracts the text within the text area. In the event that psd-enginedata fails
|
43
42
|
# for some reason, we attempt to extract the text using some rough regex.
|
44
43
|
def text_value
|
45
|
-
|
46
|
-
# Something went wrong, lets hack our way through.
|
47
|
-
/\/Text \(˛ˇ(.*)\)$/.match(@data[:text]['EngineData'])[1].gsub /\r/, "\n"
|
48
|
-
else
|
49
|
-
engine_data.EngineDict.Editor.Text
|
50
|
-
end
|
44
|
+
@data[:text]['Txt ']
|
51
45
|
end
|
52
46
|
alias :to_s :text_value
|
53
47
|
|
@@ -56,8 +50,11 @@ class PSD
|
|
56
50
|
def font
|
57
51
|
{
|
58
52
|
name: fonts.first,
|
53
|
+
fonts: fonts,
|
59
54
|
sizes: sizes,
|
60
55
|
colors: colors,
|
56
|
+
alignment: alignment,
|
57
|
+
leadings: leadings,
|
61
58
|
css: to_css
|
62
59
|
}
|
63
60
|
end
|
@@ -69,11 +66,26 @@ class PSD
|
|
69
66
|
engine_data.ResourceDict.FontSet.map(&:Name)
|
70
67
|
end
|
71
68
|
|
69
|
+
# Return all leadings (line spacing) for this layer.
|
70
|
+
def leadings
|
71
|
+
return [] if engine_data.nil? || !styles.has_key?('Leading')
|
72
|
+
styles['Leading'].uniq
|
73
|
+
end
|
74
|
+
|
72
75
|
# Return all font sizes for this layer.
|
73
76
|
def sizes
|
74
77
|
return [] if engine_data.nil? || !styles.has_key?('FontSize')
|
75
78
|
styles['FontSize'].uniq
|
76
79
|
end
|
80
|
+
|
81
|
+
def alignment
|
82
|
+
return {} if engine_data.nil?
|
83
|
+
engine_data.EngineDict.ParagraphRun.RunArray.map do |s|
|
84
|
+
["left", "right", "center", "justify"][[s.ParagraphSheet.Properties.Justification.to_i,3].min]
|
85
|
+
end
|
86
|
+
rescue
|
87
|
+
[]
|
88
|
+
end
|
77
89
|
|
78
90
|
# Return all colors used for text in this layer. The colors are returned in RGBA
|
79
91
|
# format as an array of arrays.
|
@@ -125,11 +137,13 @@ class PSD
|
|
125
137
|
definition = {
|
126
138
|
'font-family' => fonts.join(', '),
|
127
139
|
'font-size' => "#{sizes.first}pt",
|
128
|
-
'color' => "rgba(#{colors.first.join(', ')})"
|
140
|
+
'color' => "rgba(#{colors.first.join(', ')})",
|
141
|
+
'text-align' => alignment.first
|
129
142
|
}
|
130
143
|
|
131
144
|
css = []
|
132
145
|
definition.each do |k, v|
|
146
|
+
next if v.nil?
|
133
147
|
css << "#{k}: #{v};"
|
134
148
|
end
|
135
149
|
|
@@ -162,4 +176,4 @@ class PSD
|
|
162
176
|
end
|
163
177
|
end
|
164
178
|
end
|
165
|
-
end
|
179
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
require 'psd/layer_info'
|
2
2
|
|
3
3
|
class PSD
|
4
4
|
class VectorMask < LayerInfo
|
5
|
-
|
5
|
+
def self.should_parse?(key)
|
6
|
+
['vmsk', 'vsms'].include?(key)
|
7
|
+
end
|
6
8
|
|
7
9
|
attr_reader :invert, :not_link, :disable, :paths
|
8
10
|
|
@@ -14,12 +16,12 @@ class PSD
|
|
14
16
|
@not_link = (tag & (0x01 << 1)) > 0
|
15
17
|
@disable = (tag & (0x01 << 2)) > 0
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
# I haven't figured out yet why this is 10 and not 8.
|
20
|
+
num_records = (@length - 10) / 26
|
19
21
|
@paths = []
|
20
22
|
num_records.times do
|
21
23
|
@paths << PathRecord.new(@file)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
25
|
-
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'psd/layer_info'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
class VectorMaskAsGlobalMask < LayerInfo
|
5
|
+
def self.should_parse?(key)
|
6
|
+
key == 'vmgm'
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :enabled
|
10
|
+
|
11
|
+
def parse
|
12
|
+
@enabled = @file.read_boolean
|
13
|
+
@file.seek 3, IO::SEEK_CUR
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'psd/layer_info'
|
2
|
+
|
3
|
+
class PSD
|
4
|
+
class Vibrance < LayerInfo
|
5
|
+
def self.should_parse?(key)
|
6
|
+
key == 'vibA'
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
@file.seek 4, IO::SEEK_CUR
|
11
|
+
@data = Descriptor.new(@file).parse
|
12
|
+
end
|
13
|
+
|
14
|
+
def vibrance
|
15
|
+
@data['vibrance']
|
16
|
+
end
|
17
|
+
|
18
|
+
def saturation
|
19
|
+
@data['Strt']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/psd/layer/info.rb
CHANGED
@@ -1,34 +1,125 @@
|
|
1
|
+
require 'psd/layer/info/black_white'
|
2
|
+
require 'psd/layer/info/blend_clipping_elements'
|
3
|
+
require 'psd/layer/info/blend_interior_elements'
|
4
|
+
require 'psd/layer/info/brightness_contrast'
|
5
|
+
require 'psd/layer/info/channel_blending_restrictions'
|
6
|
+
require 'psd/layer/info/channel_mixer'
|
7
|
+
require 'psd/layer/info/color_balance'
|
8
|
+
require 'psd/layer/info/color_lookup'
|
9
|
+
require 'psd/layer/info/content_generator'
|
10
|
+
require 'psd/layer/info/curves'
|
11
|
+
require 'psd/layer/info/effects_layer'
|
12
|
+
require 'psd/layer/info/exposure'
|
13
|
+
require 'psd/layer/info/fill_opacity'
|
14
|
+
require 'psd/layer/info/gradient_fill'
|
15
|
+
require 'psd/layer/info/gradient_map'
|
16
|
+
require 'psd/layer/info/hue_saturation'
|
17
|
+
require 'psd/layer/info/invert'
|
18
|
+
require 'psd/layer/info/knockout'
|
19
|
+
require 'psd/layer/info/layer_effects'
|
20
|
+
require 'psd/layer/info/layer_mask_as_global_mask'
|
21
|
+
require 'psd/layer/info/layer_group'
|
22
|
+
require 'psd/layer/info/layer_id'
|
23
|
+
require 'psd/layer/info/layer_name_source'
|
24
|
+
require 'psd/layer/info/layer_section_divider'
|
25
|
+
require 'psd/layer/info/legacy_typetool'
|
26
|
+
require 'psd/layer/info/levels'
|
27
|
+
require 'psd/layer/info/locked'
|
28
|
+
require 'psd/layer/info/metadata_setting'
|
29
|
+
require 'psd/layer/info/object_effects'
|
30
|
+
require 'psd/layer/info/pattern'
|
31
|
+
require 'psd/layer/info/pattern_fill'
|
32
|
+
require 'psd/layer/info/photo_filter'
|
33
|
+
require 'psd/layer/info/placed_layer'
|
34
|
+
require 'psd/layer/info/posterize'
|
35
|
+
require 'psd/layer/info/reference_point'
|
36
|
+
require 'psd/layer/info/selective_color'
|
37
|
+
require 'psd/layer/info/sheet_color'
|
38
|
+
require 'psd/layer/info/solid_color'
|
39
|
+
require 'psd/layer/info/threshold'
|
40
|
+
require 'psd/layer/info/transparency_shapes_layer'
|
41
|
+
require 'psd/layer/info/typetool'
|
42
|
+
require 'psd/layer/info/unicode_name'
|
43
|
+
require 'psd/layer/info/vector_mask'
|
44
|
+
require 'psd/layer/info/vector_mask_as_global_mask'
|
45
|
+
require 'psd/layer/info/vector_origination'
|
46
|
+
require 'psd/layer/info/vector_stroke'
|
47
|
+
require 'psd/layer/info/vector_stroke_content'
|
48
|
+
require 'psd/layer/info/vibrance'
|
49
|
+
|
1
50
|
class PSD
|
2
51
|
class Layer
|
3
52
|
module Info
|
4
53
|
# All of the extra layer info sections that we know how to parse.
|
5
54
|
LAYER_INFO = {
|
55
|
+
black_white: BlackWhite,
|
6
56
|
blend_clipping_elements: BlendClippingElements,
|
7
57
|
blend_interior_elements: BlendInteriorElements,
|
8
|
-
|
58
|
+
brightness_contrast: BrightnessContrast,
|
59
|
+
channel_blending_restrictions: ChannelBlendingRestrictions,
|
60
|
+
channel_mixer: ChannelMixer,
|
61
|
+
color_balance: ColorBalance,
|
62
|
+
color_lookup: ColorLookup,
|
63
|
+
content_generator: ContentGenerator,
|
64
|
+
curves: Curves,
|
65
|
+
effects_layer: EffectsLayer,
|
66
|
+
exposure: Exposure,
|
67
|
+
fill_opacity: FillOpacity,
|
68
|
+
gradient_fill: GradientFill,
|
69
|
+
gradient_map: GradientMap,
|
70
|
+
hue_saturation: HueSaturation,
|
71
|
+
invert: Invert,
|
72
|
+
knockout: Knockout,
|
73
|
+
layer_effects: LayerEffects,
|
74
|
+
layer_mask_as_global_mask: LayerMaskAsGlobalMask,
|
75
|
+
layer_id: LayerID,
|
76
|
+
layer_name_source: LayerNameSource,
|
9
77
|
legacy_type: LegacyTypeTool,
|
78
|
+
levels: Levels,
|
79
|
+
locked: Locked,
|
10
80
|
metadata: MetadataSetting,
|
11
|
-
layer_name_source: LayerNameSource,
|
12
|
-
object_effects: ObjectEffects,
|
13
81
|
name: UnicodeName,
|
14
|
-
section_divider: LayerSectionDivider,
|
15
82
|
nested_section_divider: NestedLayerDivider,
|
16
|
-
|
17
|
-
|
18
|
-
|
83
|
+
object_effects: ObjectEffects,
|
84
|
+
pattern_fill: PatternFill,
|
85
|
+
photo_filter: PhotoFilter,
|
19
86
|
placed_layer: PlacedLayer,
|
20
|
-
|
87
|
+
posterize: Posterize,
|
88
|
+
reference_point: ReferencePoint,
|
89
|
+
selective_color: SelectiveColor,
|
90
|
+
section_divider: LayerSectionDivider,
|
91
|
+
sheet_color: SheetColor,
|
92
|
+
solid_color: SolidColor,
|
93
|
+
threshold: Threshold,
|
94
|
+
transparency_shapes_layer: TransparencyShapesLayer,
|
95
|
+
type: TypeTool,
|
21
96
|
vector_mask: VectorMask,
|
22
|
-
|
97
|
+
vector_mask_as_global_mask: VectorMaskAsGlobalMask,
|
98
|
+
vector_origination: VectorOrigination,
|
23
99
|
vector_stroke: VectorStroke,
|
24
|
-
vector_stroke_content: VectorStrokeContent
|
25
|
-
|
100
|
+
vector_stroke_content: VectorStrokeContent,
|
101
|
+
vibrance: Vibrance
|
102
|
+
}.freeze
|
103
|
+
|
104
|
+
BIG_LAYER_INFO_KEYS = %w{ LMsk Lr16 Lr32 Layr Mt16 Mt32 Mtrn Alph FMsk lnk2 FEid FXid PxSD }
|
26
105
|
|
27
106
|
attr_reader :adjustments
|
28
107
|
alias :info :adjustments
|
29
108
|
|
109
|
+
LAYER_INFO.keys.each do |key|
|
110
|
+
define_method(key) { @adjustments[key] }
|
111
|
+
end
|
112
|
+
|
30
113
|
private
|
31
114
|
|
115
|
+
def parse_additional_layer_info_length(key)
|
116
|
+
if @header.big? && BIG_LAYER_INFO_KEYS.include?(key)
|
117
|
+
Util.pad2 @file.read_longlong
|
118
|
+
else
|
119
|
+
Util.pad2 @file.read_int
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
32
123
|
# This section is a bit tricky to parse because it represents all of the
|
33
124
|
# extra data that describes this layer.
|
34
125
|
def parse_layer_info
|
@@ -42,18 +133,18 @@ class PSD
|
|
42
133
|
key = @file.read_string(4)
|
43
134
|
@info_keys << key
|
44
135
|
|
45
|
-
length =
|
136
|
+
length = parse_additional_layer_info_length(key)
|
46
137
|
pos = @file.tell
|
47
138
|
|
48
139
|
key_parseable = false
|
49
140
|
LAYER_INFO.each do |name, info|
|
50
|
-
next unless info.key
|
141
|
+
next unless info.should_parse?(key)
|
51
142
|
|
52
143
|
PSD.logger.debug "Layer Info: key = #{key}, start = #{pos}, length = #{length}"
|
53
144
|
|
54
145
|
i = info.new(self, length)
|
55
146
|
@adjustments[name] = LazyExecute.new(i, @file).now(:skip).later(:parse)
|
56
|
-
|
147
|
+
|
57
148
|
key_parseable = true and break
|
58
149
|
end
|
59
150
|
|
@@ -65,10 +156,6 @@ class PSD
|
|
65
156
|
|
66
157
|
@extra_data_end = @file.tell
|
67
158
|
end
|
68
|
-
|
69
|
-
def vector_mask
|
70
|
-
info[:vector_mask_2] || info[:vector_mask]
|
71
|
-
end
|
72
159
|
end
|
73
160
|
end
|
74
|
-
end
|
161
|
+
end
|
@@ -10,8 +10,6 @@ class PSD
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def parse_position_and_channels
|
13
|
-
start_section(:info)
|
14
|
-
|
15
13
|
@top = @file.read_int
|
16
14
|
@left = @file.read_int
|
17
15
|
@bottom = @file.read_int
|
@@ -23,12 +21,10 @@ class PSD
|
|
23
21
|
|
24
22
|
@channels.times do
|
25
23
|
channel_id = @file.read_short
|
26
|
-
channel_length = @file.read_int
|
24
|
+
channel_length = @header.big? ? @file.read_longlong : @file.read_int
|
27
25
|
|
28
26
|
@channels_info << {id: channel_id, length: channel_length}
|
29
27
|
end
|
30
|
-
|
31
|
-
end_section(:info)
|
32
28
|
end
|
33
29
|
|
34
30
|
def export_position_and_channels(outfile)
|
@@ -44,4 +40,4 @@ class PSD
|
|
44
40
|
end
|
45
41
|
end
|
46
42
|
end
|
47
|
-
end
|
43
|
+
end
|
data/lib/psd/layer.rb
CHANGED
@@ -1,18 +1,28 @@
|
|
1
|
+
require 'psd/layer/blend_modes'
|
2
|
+
require 'psd/layer/blending_ranges'
|
3
|
+
require 'psd/layer/channel_image'
|
4
|
+
require 'psd/layer/exporting'
|
5
|
+
require 'psd/layer/helpers'
|
6
|
+
require 'psd/layer/info'
|
7
|
+
require 'psd/layer/mask'
|
8
|
+
require 'psd/layer/name'
|
9
|
+
require 'psd/layer/path_components'
|
10
|
+
require 'psd/layer/position_and_channels'
|
11
|
+
|
1
12
|
class PSD
|
2
13
|
# Represents a single layer and all of the data associated with
|
3
14
|
# that layer.
|
4
15
|
class Layer
|
5
|
-
include Section
|
6
16
|
include BlendModes
|
7
17
|
include BlendingRanges
|
8
18
|
include ChannelImage
|
9
19
|
include Exporting
|
10
|
-
include Helpers
|
11
20
|
include Info
|
12
21
|
include Mask
|
13
22
|
include Name
|
14
23
|
include PathComponents
|
15
24
|
include PositionAndChannels
|
25
|
+
include Helpers
|
16
26
|
|
17
27
|
attr_reader :id, :info_keys, :header
|
18
28
|
attr_accessor :group_layer, :node, :file
|
@@ -29,9 +39,6 @@ class PSD
|
|
29
39
|
@blend_mode = {}
|
30
40
|
@group_layer = nil
|
31
41
|
|
32
|
-
@blending_mode = 'normal'
|
33
|
-
@opacity = 255
|
34
|
-
|
35
42
|
# Just used for tracking which layer adjustments we're parsing.
|
36
43
|
# Not essential.
|
37
44
|
@info_keys = []
|
@@ -39,8 +46,6 @@ class PSD
|
|
39
46
|
|
40
47
|
# Parse the layer and all of it's sub-sections.
|
41
48
|
def parse(index=nil)
|
42
|
-
start_section
|
43
|
-
|
44
49
|
@id = index
|
45
50
|
|
46
51
|
parse_position_and_channels
|
@@ -58,7 +63,6 @@ class PSD
|
|
58
63
|
|
59
64
|
@file.seek @layer_end # Skip over any filler zeros
|
60
65
|
|
61
|
-
end_section
|
62
66
|
return self
|
63
67
|
end
|
64
68
|
|
@@ -66,12 +70,5 @@ class PSD
|
|
66
70
|
def [](val)
|
67
71
|
self.send(val)
|
68
72
|
end
|
69
|
-
|
70
|
-
# We delegate all missing method calls to the extra layer info to make it easier
|
71
|
-
# to access that data.
|
72
|
-
def method_missing(method, *args, &block)
|
73
|
-
return @adjustments[method] if @adjustments.has_key?(method)
|
74
|
-
super
|
75
|
-
end
|
76
73
|
end
|
77
|
-
end
|
74
|
+
end
|
data/lib/psd/layer_info.rb
CHANGED
data/lib/psd/layer_mask.rb
CHANGED
@@ -2,8 +2,6 @@ class PSD
|
|
2
2
|
# Covers parsing the global mask and controls parsing of all the
|
3
3
|
# layers/folders in the document.
|
4
4
|
class LayerMask
|
5
|
-
include Section
|
6
|
-
|
7
5
|
attr_reader :layers, :global_mask
|
8
6
|
|
9
7
|
# Store a reference to the file and the header and initialize the defaults.
|
@@ -15,27 +13,57 @@ class PSD
|
|
15
13
|
@layers = []
|
16
14
|
@merged_alpha = false
|
17
15
|
@global_mask = nil
|
18
|
-
@extras = []
|
19
16
|
end
|
20
17
|
|
21
18
|
# Allows us to skip this section because it starts with the length of the section
|
22
19
|
# stored as an integer.
|
23
20
|
def skip
|
24
|
-
@file.seek
|
21
|
+
@file.seek parse_mask_size, IO::SEEK_CUR
|
25
22
|
return self
|
26
23
|
end
|
27
24
|
|
28
|
-
# Parse this section, including all of the layers and folders.
|
29
|
-
# will also trigger parsing of the channel images for each layer.
|
25
|
+
# Parse this section, including all of the layers and folders.
|
30
26
|
def parse
|
31
|
-
|
27
|
+
mask_size = parse_mask_size
|
28
|
+
|
29
|
+
start_position = @file.tell
|
30
|
+
finish = start_position + mask_size
|
32
31
|
|
33
|
-
|
34
|
-
finish = @file.tell + mask_size
|
32
|
+
PSD.logger.debug "Layer mask section: #{start_position} - #{finish}"
|
35
33
|
|
36
34
|
return self if mask_size <= 0
|
37
35
|
|
38
|
-
|
36
|
+
parse_layers
|
37
|
+
parse_global_mask
|
38
|
+
|
39
|
+
consumed_bytes = @file.tell - start_position
|
40
|
+
parse_layer_tagged_blocks(mask_size - consumed_bytes)
|
41
|
+
|
42
|
+
# Layers are parsed in reverse order
|
43
|
+
layers.reverse!
|
44
|
+
|
45
|
+
# Ensure we're at the end of this section
|
46
|
+
@file.seek finish
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def parse_mask_size
|
54
|
+
@header.big? ? @file.read_longlong : @file.read_int
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_layer_info_size
|
58
|
+
Util.pad2(@header.big? ? @file.read_longlong : @file.read_int)
|
59
|
+
end
|
60
|
+
|
61
|
+
def channel_information_length
|
62
|
+
@header.big? ? 10 : 6
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_layers
|
66
|
+
layer_info_size = parse_layer_info_size
|
39
67
|
|
40
68
|
if layer_info_size > 0
|
41
69
|
layer_count = @file.read_short
|
@@ -45,8 +73,8 @@ class PSD
|
|
45
73
|
@merged_alpha = true
|
46
74
|
end
|
47
75
|
|
48
|
-
if layer_count * (18 +
|
49
|
-
|
76
|
+
if layer_count * (18 + channel_information_length * @header.channels) > layer_info_size
|
77
|
+
PSD.logger.error "Unlikely number of layers parsed: #{layer_count}"
|
50
78
|
end
|
51
79
|
|
52
80
|
@layer_section_start = @file.tell
|
@@ -58,60 +86,43 @@ class PSD
|
|
58
86
|
layer.parse_channel_image(@header)
|
59
87
|
end
|
60
88
|
end
|
61
|
-
|
62
|
-
# Layers are parsed in reverse order
|
63
|
-
layers.reverse!
|
64
|
-
group_layers
|
65
|
-
|
66
|
-
parse_global_mask
|
67
|
-
|
68
|
-
# Ensure we're at the end of this section
|
69
|
-
@file.seek finish
|
70
|
-
end_section
|
71
|
-
|
72
|
-
return self
|
73
89
|
end
|
74
90
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
91
|
+
def parse_layer_tagged_blocks(remaining_length)
|
92
|
+
end_pos = @file.tell + remaining_length
|
93
|
+
|
94
|
+
while @file.tell < end_pos
|
95
|
+
res = parse_additional_layer_info_block
|
96
|
+
break unless res
|
80
97
|
end
|
98
|
+
end
|
81
99
|
|
82
|
-
|
83
|
-
|
100
|
+
def parse_additional_layer_info_block
|
101
|
+
sig = @file.read_string(4)
|
84
102
|
|
85
|
-
|
86
|
-
|
103
|
+
unless %w(8BIM 8B64).include?(sig)
|
104
|
+
@file.seek(-4, IO::SEEK_CUR)
|
105
|
+
return false
|
87
106
|
end
|
88
107
|
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
108
|
+
key = @file.read_string(4)
|
93
109
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
if layer.folder?
|
98
|
-
group_layer = layer
|
99
|
-
elsif layer.folder_end?
|
100
|
-
group_layer = nil
|
101
|
-
else
|
102
|
-
layer.group_layer = layer
|
103
|
-
end
|
110
|
+
if %w(Lr16 Lr32 Mt16).include?(key)
|
111
|
+
parse_layers
|
112
|
+
return true
|
104
113
|
end
|
114
|
+
|
115
|
+
false
|
105
116
|
end
|
106
117
|
|
107
118
|
def parse_global_mask
|
108
119
|
length = @file.read_int
|
109
|
-
|
120
|
+
|
110
121
|
PSD.logger.debug "Global Mask: length = #{length}"
|
111
122
|
return if length <= 0
|
112
123
|
|
113
124
|
mask_end = @file.tell + length
|
114
|
-
|
125
|
+
|
115
126
|
@global_mask = {}
|
116
127
|
@global_mask[:overlay_color_space] = @file.read_short
|
117
128
|
@global_mask[:color_components] = 4.times.map { |i| @file.read_short >> 8 }
|
@@ -126,4 +137,4 @@ class PSD
|
|
126
137
|
@file.seek mask_end
|
127
138
|
end
|
128
139
|
end
|
129
|
-
end
|
140
|
+
end
|
data/lib/psd/lazy_execute.rb
CHANGED
@@ -41,7 +41,11 @@ class PSD
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def inspect
|
44
|
-
|
44
|
+
if loaded?
|
45
|
+
@obj.inspect
|
46
|
+
else
|
47
|
+
"<PSD::LazyExecute @obj=#{@obj.class.name}, @pos=#{@start_pos}, @load_method=:#{@load_method}>"
|
48
|
+
end
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|