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.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -0
  3. data/README.md +60 -20
  4. data/lib/psd/blend_mode.rb +46 -38
  5. data/lib/psd/channel_image.rb +11 -5
  6. data/lib/psd/color.rb +14 -5
  7. data/lib/psd/descriptor.rb +39 -16
  8. data/lib/psd/file.rb +9 -6
  9. data/lib/psd/header.rb +38 -33
  10. data/lib/psd/helpers.rb +2 -2
  11. data/lib/psd/image.rb +21 -16
  12. data/lib/psd/image_formats/layer_rle.rb +2 -2
  13. data/lib/psd/image_formats/rle.rb +4 -10
  14. data/lib/psd/image_modes/cmyk.rb +18 -5
  15. data/lib/psd/image_modes/greyscale.rb +6 -1
  16. data/lib/psd/image_modes/rgb.rb +17 -5
  17. data/lib/psd/layer/blend_modes.rb +15 -13
  18. data/lib/psd/layer/helpers.rb +8 -10
  19. data/lib/psd/layer/info/black_white.rb +33 -0
  20. data/lib/psd/{layer_info → layer/info}/blend_clipping_elements.rb +4 -2
  21. data/lib/psd/{layer_info → layer/info}/blend_interior_elements.rb +4 -2
  22. data/lib/psd/layer/info/brightness_contrast.rb +21 -0
  23. data/lib/psd/layer/info/channel_blending_restrictions.rb +27 -0
  24. data/lib/psd/layer/info/channel_mixer.rb +27 -0
  25. data/lib/psd/layer/info/color_balance.rb +23 -0
  26. data/lib/psd/layer/info/color_lookup.rb +14 -0
  27. data/lib/psd/layer/info/content_generator.rb +42 -0
  28. data/lib/psd/layer/info/curves.rb +78 -0
  29. data/lib/psd/layer/info/effects_layer.rb +174 -0
  30. data/lib/psd/layer/info/exposure.rb +20 -0
  31. data/lib/psd/{layer_info → layer/info}/fill_opacity.rb +4 -2
  32. data/lib/psd/layer/info/gradient_fill.rb +14 -0
  33. data/lib/psd/layer/info/gradient_map.rb +69 -0
  34. data/lib/psd/layer/info/hue_saturation.rb +41 -0
  35. data/lib/psd/layer/info/invert.rb +17 -0
  36. data/lib/psd/layer/info/knockout.rb +22 -0
  37. data/lib/psd/layer/info/layer_effects.rb +16 -0
  38. data/lib/psd/{layer_info → layer/info}/layer_group.rb +4 -2
  39. data/lib/psd/{layer_info → layer/info}/layer_id.rb +4 -2
  40. data/lib/psd/layer/info/layer_mask_as_global_mask.rb +16 -0
  41. data/lib/psd/{layer_info → layer/info}/layer_name_source.rb +4 -2
  42. data/lib/psd/{layer_info → layer/info}/layer_section_divider.rb +4 -2
  43. data/lib/psd/{layer_info → layer/info}/legacy_typetool.rb +6 -4
  44. data/lib/psd/layer/info/levels.rb +48 -0
  45. data/lib/psd/{layer_info → layer/info}/locked.rb +4 -2
  46. data/lib/psd/{layer_info → layer/info}/metadata_setting.rb +5 -3
  47. data/lib/psd/{layer_info → layer/info}/object_effects.rb +5 -5
  48. data/lib/psd/layer/info/pattern.rb +14 -0
  49. data/lib/psd/layer/info/pattern_fill.rb +15 -0
  50. data/lib/psd/layer/info/photo_filter.rb +44 -0
  51. data/lib/psd/{layer_info → layer/info}/placed_layer.rb +4 -2
  52. data/lib/psd/layer/info/posterize.rb +16 -0
  53. data/lib/psd/{layer_info → layer/info}/reference_point.rb +4 -2
  54. data/lib/psd/layer/info/selective_color.rb +32 -0
  55. data/lib/psd/layer/info/sheet_color.rb +36 -0
  56. data/lib/psd/layer/info/solid_color.rb +36 -0
  57. data/lib/psd/layer/info/threshold.rb +16 -0
  58. data/lib/psd/layer/info/transparency_shapes_layer.rb +16 -0
  59. data/lib/psd/{layer_info → layer/info}/typetool.rb +27 -13
  60. data/lib/psd/{layer_info → layer/info}/unicode_name.rb +4 -2
  61. data/lib/psd/{layer_info → layer/info}/vector_mask.rb +7 -5
  62. data/lib/psd/layer/info/vector_mask_as_global_mask.rb +16 -0
  63. data/lib/psd/layer/info/vector_origination.rb +14 -0
  64. data/lib/psd/{layer_info → layer/info}/vector_stroke.rb +4 -2
  65. data/lib/psd/{layer_info → layer/info}/vector_stroke_content.rb +4 -2
  66. data/lib/psd/layer/info/vibrance.rb +22 -0
  67. data/lib/psd/layer/info.rb +106 -19
  68. data/lib/psd/layer/position_and_channels.rb +2 -6
  69. data/lib/psd/layer.rb +13 -16
  70. data/lib/psd/layer_info.rb +0 -4
  71. data/lib/psd/layer_mask.rb +61 -50
  72. data/lib/psd/lazy_execute.rb +5 -1
  73. data/lib/psd/mask.rb +7 -1
  74. data/lib/psd/node.rb +142 -49
  75. data/lib/psd/nodes/ancestry.rb +7 -6
  76. data/lib/psd/nodes/build_preview.rb +4 -4
  77. data/lib/psd/nodes/group.rb +36 -0
  78. data/lib/psd/nodes/layer.rb +45 -0
  79. data/lib/psd/nodes/layer_comps.rb +93 -0
  80. data/lib/psd/nodes/locking.rb +36 -0
  81. data/lib/psd/nodes/root.rb +87 -0
  82. data/lib/psd/nodes/search.rb +8 -65
  83. data/lib/psd/path_record.rb +5 -70
  84. data/lib/psd/renderer/blender.rb +10 -5
  85. data/lib/psd/renderer/cairo_helpers.rb +46 -0
  86. data/lib/psd/renderer/canvas.rb +41 -20
  87. data/lib/psd/renderer/canvas_management.rb +2 -2
  88. data/lib/psd/renderer/clipping_mask.rb +5 -4
  89. data/lib/psd/renderer/compose.rb +59 -69
  90. data/lib/psd/renderer/layer_styles/color_overlay.rb +45 -27
  91. data/lib/psd/renderer/layer_styles.rb +15 -5
  92. data/lib/psd/renderer/mask.rb +26 -22
  93. data/lib/psd/renderer/mask_canvas.rb +12 -0
  94. data/lib/psd/renderer/vector_shape.rb +239 -0
  95. data/lib/psd/renderer.rb +18 -7
  96. data/lib/psd/resource_section.rb +13 -6
  97. data/lib/psd/resources/base.rb +33 -0
  98. data/lib/psd/resources/guides.rb +6 -4
  99. data/lib/psd/resources/layer_comps.rb +6 -4
  100. data/lib/psd/resources/resolution_info.rb +48 -0
  101. data/lib/psd/resources/saved_path.rb +19 -0
  102. data/lib/psd/resources/slices.rb +7 -5
  103. data/lib/psd/resources/work_path.rb +22 -0
  104. data/lib/psd/resources/xmp_metadata.rb +46 -0
  105. data/lib/psd/resources.rb +17 -21
  106. data/lib/psd/slice.rb +44 -0
  107. data/lib/psd/slices.rb +13 -0
  108. data/lib/psd/util.rb +4 -2
  109. data/lib/psd/version.rb +1 -1
  110. data/lib/psd.rb +31 -45
  111. data/psd.gemspec +2 -3
  112. data/spec/files/alignment_modes.psd +0 -0
  113. data/spec/files/blendmodes.psd +0 -0
  114. data/spec/files/example.psb +0 -0
  115. data/spec/hierarchy_spec.rb +5 -0
  116. data/spec/locked_spec.rb +8 -8
  117. data/spec/psb_parsing_spec.rb +57 -0
  118. data/spec/text_spec.rb +13 -1
  119. metadata +115 -75
  120. data/circle.yml +0 -6
  121. data/lib/psd/layer_info/vector_mask_2.rb +0 -10
  122. data/lib/psd/node_exporting.rb +0 -20
  123. data/lib/psd/node_group.rb +0 -86
  124. data/lib/psd/node_layer.rb +0 -81
  125. data/lib/psd/node_root.rb +0 -93
  126. data/lib/psd/nodes/has_children.rb +0 -13
  127. data/lib/psd/nodes/lock_to_origin.rb +0 -7
  128. data/lib/psd/nodes/parse_layers.rb +0 -18
  129. data/lib/psd/renderer/layer_styles/drop_shadow.rb +0 -75
  130. data/lib/psd/section.rb +0 -26
@@ -1,11 +1,13 @@
1
1
  # encoding: UTF-8
2
- require_relative '../layer_info'
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
- @key = 'TySh'
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
- if engine_data.nil?
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
- require_relative '../layer_info'
1
+ require 'psd/layer_info'
2
2
 
3
3
  class PSD
4
4
  class UnicodeName < LayerInfo
5
- @key = 'luni'
5
+ def self.should_parse?(key)
6
+ key == 'luni'
7
+ end
6
8
 
7
9
  def parse
8
10
  pos = @file.tell
@@ -1,8 +1,10 @@
1
- require_relative '../layer_info'
1
+ require 'psd/layer_info'
2
2
 
3
3
  class PSD
4
4
  class VectorMask < LayerInfo
5
- @key = 'vmsk'
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
- num_records = (@length - 8) / 26
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,14 @@
1
+ require 'psd/layer_info'
2
+
3
+ class PSD
4
+ class VectorOrigination < LayerInfo
5
+ def self.should_parse?(key)
6
+ key == 'vogk'
7
+ end
8
+
9
+ def parse
10
+ @file.seek 8, IO::SEEK_CUR
11
+ @data = Descriptor.new(@file).parse
12
+ end
13
+ end
14
+ end
@@ -1,8 +1,10 @@
1
- require_relative '../layer_info'
1
+ require 'psd/layer_info'
2
2
 
3
3
  class PSD
4
4
  class VectorStroke < LayerInfo
5
- @key = 'vstk'
5
+ def self.should_parse?(key)
6
+ key == 'vstk'
7
+ end
6
8
 
7
9
  def parse
8
10
  version = @file.read_int
@@ -1,8 +1,10 @@
1
- require_relative '../layer_info'
1
+ require 'psd/layer_info'
2
2
 
3
3
  class PSD
4
4
  class VectorStrokeContent < LayerInfo
5
- @key = 'vscg'
5
+ def self.should_parse?(key)
6
+ key == 'vscg'
7
+ end
6
8
 
7
9
  attr_reader :key
8
10
 
@@ -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
@@ -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
- type: TypeTool,
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
- reference_point: ReferencePoint,
17
- layer_id: LayerID,
18
- fill_opacity: FillOpacity,
83
+ object_effects: ObjectEffects,
84
+ pattern_fill: PatternFill,
85
+ photo_filter: PhotoFilter,
19
86
  placed_layer: PlacedLayer,
20
- locked: Locked,
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
- vector_mask_2: VectorMask2,
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 = Util.pad2 @file.read_int
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 == 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
@@ -3,10 +3,6 @@ class PSD
3
3
  class LayerInfo
4
4
  attr_reader :data
5
5
 
6
- # The value of the key as used in the PSD format.
7
- class << self; attr_accessor :key; end
8
- @key = ""
9
-
10
6
  def initialize(layer, length)
11
7
  @layer = layer
12
8
  @file = layer.file
@@ -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 @file.read_int, IO::SEEK_CUR
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. Once implemented, this
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
- start_section
27
+ mask_size = parse_mask_size
28
+
29
+ start_position = @file.tell
30
+ finish = start_position + mask_size
32
31
 
33
- mask_size = @file.read_int
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
- layer_info_size = Util.pad2(@file.read_int)
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 + 6 * @header.channels) > layer_info_size
49
- raise "Unlikely number of layers parsed: #{layer_count}"
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
- # Export the mask and all the children layers to a file.
76
- def export(outfile)
77
- if @layers.size == 0
78
- # No data, just read whatever's here.
79
- return outfile.write @file.read(@section_end[:all] - start_of_section)
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
- # Read the initial mask data since it won't change
83
- outfile.write @file.read(@layer_section_start - @file.tell)
100
+ def parse_additional_layer_info_block
101
+ sig = @file.read_string(4)
84
102
 
85
- @layers.reverse.each do |layer|
86
- layer.export(outfile)
103
+ unless %w(8BIM 8B64).include?(sig)
104
+ @file.seek(-4, IO::SEEK_CUR)
105
+ return false
87
106
  end
88
107
 
89
- outfile.write @file.read(end_of_section - @file.tell)
90
- end
91
-
92
- private
108
+ key = @file.read_string(4)
93
109
 
94
- def group_layers
95
- group_layer = nil
96
- layers.each do |layer|
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
@@ -41,7 +41,11 @@ class PSD
41
41
  end
42
42
 
43
43
  def inspect
44
- "<PSD::LazyExecute @obj=#{@obj.class.name}, @pos=#{@start_pos}, @load_method=:#{@load_method}>"
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