psd 2.1.2 → 3.1.2

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/psd.rb +8 -6
  4. data/lib/psd/blend_mode.rb +46 -38
  5. data/lib/psd/channel_image.rb +9 -5
  6. data/lib/psd/descriptor.rb +39 -16
  7. data/lib/psd/header.rb +33 -32
  8. data/lib/psd/image_formats/rle.rb +4 -10
  9. data/lib/psd/image_modes/rgb.rb +4 -4
  10. data/lib/psd/layer.rb +1 -15
  11. data/lib/psd/layer/blend_modes.rb +12 -12
  12. data/lib/psd/layer/helpers.rb +8 -10
  13. data/lib/psd/layer/info.rb +9 -7
  14. data/lib/psd/layer/position_and_channels.rb +0 -4
  15. data/lib/psd/layer_info.rb +0 -4
  16. data/lib/psd/layer_info/blend_clipping_elements.rb +4 -2
  17. data/lib/psd/layer_info/blend_interior_elements.rb +4 -2
  18. data/lib/psd/layer_info/fill_opacity.rb +4 -2
  19. data/lib/psd/layer_info/layer_group.rb +4 -2
  20. data/lib/psd/layer_info/layer_id.rb +4 -2
  21. data/lib/psd/layer_info/layer_name_source.rb +4 -2
  22. data/lib/psd/layer_info/layer_section_divider.rb +4 -2
  23. data/lib/psd/layer_info/legacy_typetool.rb +5 -3
  24. data/lib/psd/layer_info/locked.rb +4 -2
  25. data/lib/psd/layer_info/metadata_setting.rb +4 -2
  26. data/lib/psd/layer_info/object_effects.rb +4 -2
  27. data/lib/psd/layer_info/pattern.rb +14 -0
  28. data/lib/psd/layer_info/placed_layer.rb +4 -2
  29. data/lib/psd/layer_info/reference_point.rb +4 -2
  30. data/lib/psd/layer_info/sheet_color.rb +18 -0
  31. data/lib/psd/layer_info/solid_color.rb +36 -0
  32. data/lib/psd/layer_info/typetool.rb +4 -2
  33. data/lib/psd/layer_info/unicode_name.rb +4 -2
  34. data/lib/psd/layer_info/vector_mask.rb +4 -2
  35. data/lib/psd/layer_info/vector_origination.rb +14 -0
  36. data/lib/psd/layer_info/vector_stroke.rb +4 -2
  37. data/lib/psd/layer_info/vector_stroke_content.rb +4 -2
  38. data/lib/psd/layer_mask.rb +2 -8
  39. data/lib/psd/lazy_execute.rb +5 -1
  40. data/lib/psd/node.rb +112 -48
  41. data/lib/psd/nodes/ancestry.rb +80 -75
  42. data/lib/psd/nodes/build_preview.rb +4 -4
  43. data/lib/psd/nodes/group.rb +35 -0
  44. data/lib/psd/nodes/layer.rb +40 -0
  45. data/lib/psd/nodes/root.rb +90 -0
  46. data/lib/psd/nodes/search.rb +19 -19
  47. data/lib/psd/path_record.rb +1 -71
  48. data/lib/psd/renderer.rb +6 -5
  49. data/lib/psd/renderer/blender.rb +10 -5
  50. data/lib/psd/renderer/cairo_helpers.rb +46 -0
  51. data/lib/psd/renderer/canvas.rb +39 -19
  52. data/lib/psd/renderer/canvas_management.rb +2 -2
  53. data/lib/psd/renderer/clipping_mask.rb +5 -4
  54. data/lib/psd/renderer/compose.rb +61 -68
  55. data/lib/psd/renderer/layer_styles.rb +15 -5
  56. data/lib/psd/renderer/layer_styles/color_overlay.rb +46 -27
  57. data/lib/psd/renderer/mask.rb +26 -22
  58. data/lib/psd/renderer/mask_canvas.rb +12 -0
  59. data/lib/psd/renderer/vector_shape.rb +239 -0
  60. data/lib/psd/resource_section.rb +4 -7
  61. data/lib/psd/resources.rb +4 -19
  62. data/lib/psd/resources/base.rb +27 -0
  63. data/lib/psd/resources/guides.rb +6 -4
  64. data/lib/psd/resources/layer_comps.rb +6 -4
  65. data/lib/psd/resources/slices.rb +7 -5
  66. data/lib/psd/version.rb +1 -1
  67. data/psd.gemspec +1 -2
  68. data/spec/files/blendmodes.psd +0 -0
  69. data/spec/hierarchy_spec.rb +5 -0
  70. metadata +27 -26
  71. data/lib/psd/layer_info/vector_mask_2.rb +0 -10
  72. data/lib/psd/node_exporting.rb +0 -20
  73. data/lib/psd/node_group.rb +0 -86
  74. data/lib/psd/node_layer.rb +0 -81
  75. data/lib/psd/node_root.rb +0 -93
  76. data/lib/psd/nodes/has_children.rb +0 -13
  77. data/lib/psd/nodes/lock_to_origin.rb +0 -7
  78. data/lib/psd/nodes/parse_layers.rb +0 -18
  79. data/lib/psd/renderer/layer_styles/drop_shadow.rb +0 -75
  80. data/lib/psd/section.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 62a564e222daa1c918ef3bdf9c2a4f99dfd59833
4
- data.tar.gz: 93c28d4cba1c961cb28734da81f54e1617adcbdd
3
+ metadata.gz: e01eed6ed75e5db88c644ea838ff2f29d89c9df2
4
+ data.tar.gz: ca303780610167552c62d169a886595d71b55a6d
5
5
  SHA512:
6
- metadata.gz: 5eb251154704e8172420d3aa1d97851f739f516dcda1d659349719933f354e7443e3a4cebad498084ccf475505e5f97fef0a16f5cf15b25c4bdc3264401b9c59
7
- data.tar.gz: 7b4c1554a65b7f491f197839b2a99fe383e97c6b1f79ee0528374c919e11abbf3d26293c1b27fc11ca11098b64594651d820d4e9a88e192a4ef4182f0d922106
6
+ metadata.gz: f69ddfe1cf1ce6f9600151080928539f5529ae94fdbf0cb94c32bc7e4f0b6e2eef8058dc7af1cc9094202181bce10265a67c6fb850f182772aacbc48ca5ff3b5
7
+ data.tar.gz: cf3f1ae5c8a4c25eeaadc90e93f0d36b479d024fe7da33909a25f2b2b8d00c1ceb1d2d2fe058af67f106781400d29a7e35bdd7da57c7f6fdb4d50a2e35f4eb68
data/README.md CHANGED
@@ -100,7 +100,7 @@ psd.tree.children_at_path("Version A/Matte")
100
100
 
101
101
  ### Layer Comps
102
102
 
103
- You can also filter nodes based on a layer comp. To generate a new tree consisting only of the layers that are enabled in a certain layer comp:
103
+ You can also filter nodes based on a layer comp. To generate a new tree with layer visibility and position set according to the layer comp data:
104
104
 
105
105
  ``` ruby
106
106
  # Get information about all the available layer comps
data/lib/psd.rb CHANGED
@@ -1,8 +1,9 @@
1
- require "bindata"
2
- require "psd/enginedata"
3
- require "chunky_png"
1
+ require 'psd/enginedata'
2
+ require 'chunky_png'
3
+ # require "cairo"
4
4
 
5
- require_relative 'psd/section'
5
+ require 'active_support/core_ext/module/delegation'
6
+ require 'active_support/concern'
6
7
 
7
8
  dir_root = File.dirname(File.absolute_path(__FILE__)) + '/psd'
8
9
  [
@@ -23,7 +24,6 @@ end
23
24
  class PSD
24
25
  include Logger
25
26
  include Helpers
26
- include NodeExporting
27
27
 
28
28
  attr_reader :file, :opts
29
29
  alias :options :opts
@@ -86,7 +86,9 @@ class PSD
86
86
  def header
87
87
  return @header if @header
88
88
 
89
- @header = Header.read(@file)
89
+ @header = Header.new(@file)
90
+ @header.parse!
91
+
90
92
  PSD.logger.debug @header.inspect
91
93
  end
92
94
 
@@ -1,14 +1,6 @@
1
1
  class PSD
2
- # Describes the blend mode for a single layer or folder.
3
- class BlendMode < BinData::Record
4
- endian :big
5
-
6
- string :sig, read_length: 4
7
- string :blend_key, read_length: 4, trim_value: true
8
- uint8 :opacity
9
- uint8 :clipping
10
- bit8 :flags
11
- skip length: 1
2
+ class BlendMode
3
+ attr_reader :blend_key, :opacity, :clipping
12
4
 
13
5
  # All of the blend modes are stored in the PSD file with a specific key.
14
6
  # This is the mapping of that key to its readable name.
@@ -24,57 +16,73 @@ class PSD
24
16
  scrn: 'screen',
25
17
  diss: 'dissolve',
26
18
  over: 'overlay',
27
- hLit: 'hard light',
28
- sLit: 'soft light',
19
+ hLit: 'hard_light',
20
+ sLit: 'soft_light',
29
21
  diff: 'difference',
30
22
  smud: 'exclusion',
31
- div: 'color dodge',
32
- idiv: 'color burn',
33
- lbrn: 'linear burn',
34
- lddg: 'linear dodge',
35
- vLit: 'vivid light',
36
- lLit: 'linear light',
37
- pLit: 'pin light',
38
- hMix: 'hard mix',
23
+ div: 'color_dodge',
24
+ idiv: 'color_burn',
25
+ lbrn: 'linear_burn',
26
+ lddg: 'linear_dodge',
27
+ vLit: 'vivid_light',
28
+ lLit: 'linear_light',
29
+ pLit: 'pin_light',
30
+ hMix: 'hard_mix',
39
31
  pass: 'passthru',
40
- dkCl: 'darker color',
41
- lgCl: 'lighter color',
32
+ dkCl: 'darker_color',
33
+ lgCl: 'lighter_color',
42
34
  fsub: 'subtract',
43
35
  fdiv: 'divide'
44
- }
36
+ }.freeze
45
37
 
46
- # Get the readable name for this blend mode.
47
- def mode
48
- BLEND_MODES[blend_key.strip.to_sym]
38
+ def initialize(file)
39
+ @file = file
40
+
41
+ @blend_key = nil
42
+ @opacity = nil
43
+ @clipping = nil
44
+ @flags = nil
49
45
  end
50
46
 
51
- # Set the blend mode with the readable name.
52
- def mode=(val)
53
- blend_key = BLEND_MODES.invert[val.strip.downcase]
47
+ def parse!
48
+ @file.seek 4, IO::SEEK_CUR
49
+
50
+ @blend_key = @file.read_string(4).strip
51
+ @opacity = @file.read_byte
52
+ @clipping = @file.read_byte
53
+ @flags = @file.read_byte
54
+
55
+ @file.seek 1, IO::SEEK_CUR
54
56
  end
55
57
 
56
- # Opacity is stored as an integer between 0-255. This converts the opacity
57
- # to a percentage value to match the Photoshop interface.
58
+ def mode
59
+ BLEND_MODES[@blend_key.to_sym]
60
+ end
61
+ alias_method :blending_mode, :mode
62
+
58
63
  def opacity_percentage
59
- opacity * 100 / 255
64
+ @opacity_percentage ||= @opacity * 100 / 255
65
+ end
66
+
67
+ def clipped?
68
+ @clipping == 1
60
69
  end
61
70
 
62
71
  def transparency_protected
63
- flags & 0x01
72
+ @flags & 0x01
64
73
  end
65
74
 
66
- # Is this layer/folder visible?
67
75
  def visible
68
- !((flags & (0x01 << 1)) > 0)
76
+ !((@flags & (0x01 << 1)) > 0)
69
77
  end
70
78
 
71
79
  def obsolete
72
- (flags & (0x01 << 2)) > 0
80
+ (@flags & (0x01 << 2)) > 0
73
81
  end
74
82
 
75
83
  def pixel_data_irrelevant
76
- return nil unless (flags & (0x01 << 3)) > 0
77
- (flags & (0x01 << 4)) > 0
84
+ return nil unless (@flags & (0x01 << 3)) > 0
85
+ (@flags & (0x01 << 4)) > 0
78
86
  end
79
87
  end
80
88
  end
@@ -1,4 +1,4 @@
1
- require_relative 'image'
1
+ require 'psd/image'
2
2
 
3
3
  class PSD
4
4
  # Represents an image for a single layer, which differs slightly in format from
@@ -18,7 +18,7 @@ class PSD
18
18
  super(file, header)
19
19
 
20
20
  @channels_info = @layer.channels_info
21
- @has_mask = @layer.mask.width * @layer.mask.height > 0
21
+ @has_mask = @channels_info.any? { |chan| chan[:id] == -2 }
22
22
  @opacity = @layer.opacity / 255.0
23
23
  @mask_data = []
24
24
  end
@@ -47,7 +47,7 @@ class PSD
47
47
 
48
48
  # If the ID of this current channel is -2, then we assume the dimensions
49
49
  # of the layer mask.
50
- if ch_info[:id] == -2
50
+ if ch_info[:id] < -1
51
51
  @width = @layer.mask.width
52
52
  @height = @layer.mask.height
53
53
  else
@@ -97,8 +97,12 @@ class PSD
97
97
  def parse_user_mask
98
98
  return unless has_mask?
99
99
 
100
- channel = @channels_info.select { |c| c[:id] == -2 }.first
101
- index = @channels_info.index { |c| c[:id] == -2 }
100
+ mask_id = -2
101
+
102
+ PSD.logger.debug "Using mask in channel #{mask_id}"
103
+
104
+ channel = @channels_info.select { |c| c[:id] == mask_id }.first
105
+ index = @channels_info.index { |c| c[:id] == mask_id }
102
106
  return if channel.nil?
103
107
 
104
108
  start = @channel_length * index
@@ -84,13 +84,27 @@ class PSD
84
84
  def parse_identifier; @file.read_int; end
85
85
  def parse_index; @file.read_int; end
86
86
  def parse_offset; @file.read_int; end
87
- def parse_property; parse_id; end
87
+
88
+ def parse_property
89
+ {
90
+ class: parse_class,
91
+ id: parse_id
92
+ }
93
+ end
88
94
 
89
- # Discard the first ID becasue it's the same as the key
90
- # parsed from the Key/Item. Also, YOLO.
91
95
  def parse_enum
92
- parse_id
93
- parse_id
96
+ {
97
+ type: parse_id,
98
+ value: parse_id
99
+ }
100
+ end
101
+
102
+ def parse_enum_reference
103
+ {
104
+ class: parse_class,
105
+ type: parse_id,
106
+ value: parse_id
107
+ }
94
108
  end
95
109
 
96
110
  def parse_alias
@@ -149,18 +163,27 @@ class PSD
149
163
  end
150
164
 
151
165
  def parse_reference
152
- form = @file.read_string(4)
153
- klass = parse_class
154
-
155
- case form
156
- when 'Clss' then nil
157
- when 'Enmr' then parse_enum
158
- when 'Idnt' then parse_identifier
159
- when 'indx' then parse_index
160
- when 'name' then @file.read_unicode_string
161
- when 'rele' then parse_offset
162
- when 'prop' then parse_property
166
+ num_items = @file.read_int
167
+ items = []
168
+
169
+ num_items.times do
170
+ type = @file.read_string(4)
171
+ PSD.logger.debug "Reference type = #{type}"
172
+
173
+ value = case type
174
+ when 'prop' then parse_property
175
+ when 'Clss' then parse_class
176
+ when 'Enmr' then parse_enum_reference
177
+ when 'Idnt' then parse_identifier
178
+ when 'indx' then parse_index
179
+ when 'name' then @file.read_unicode_string
180
+ when 'rele' then parse_offset
181
+ end
182
+
183
+ items << {type: type, value: value}
163
184
  end
185
+
186
+ items
164
187
  end
165
188
 
166
189
  def parse_unit_double
data/lib/psd/header.rb CHANGED
@@ -1,22 +1,6 @@
1
1
  class PSD
2
- # Describes the Header for the PSD file, which is the first section of the file.
3
- class Header < BinData::Record
4
- endian :big
5
-
6
- string :sig, read_length: 4
7
- uint16 :version
8
-
9
- # Reserved bytes
10
- skip length: 6
11
-
12
- uint16 :channels
13
- uint32 :rows
14
- uint32 :cols
15
- uint16 :depth
16
- uint16 :mode
17
-
18
- uint32 :color_data_len
19
- skip length: :color_data_len
2
+ class Header
3
+ attr_reader :sig, :version, :channels, :rows, :cols, :depth, :mode
20
4
 
21
5
  # All of the color modes are stored internally as a short from 0-15.
22
6
  # This is a mapping of that value to a human-readable name.
@@ -37,25 +21,42 @@ class PSD
37
21
  'CMYK64',
38
22
  'DeepMultichannel',
39
23
  'Duotone16'
40
- ]
24
+ ].freeze
41
25
 
42
- # Get the human-readable color mode name.
43
- def mode_name
44
- if mode >= 0 && mode <= 15
45
- MODES[mode]
46
- else
47
- "(#{mode})"
48
- end
26
+ alias_method :width, :cols
27
+ alias_method :height, :rows
28
+
29
+ def initialize(file)
30
+ @file = file
31
+
32
+ @sig = nil
33
+ @version = nil
34
+ @channels = nil
35
+ @rows = nil
36
+ @cols = nil
37
+ @depth = nil
38
+ @mode = nil
49
39
  end
50
40
 
51
- # Width of the entire document in pixels.
52
- def width
53
- cols
41
+ def parse!
42
+ @sig = @file.read_string(4)
43
+ @version = @file.read_ushort
44
+
45
+ # Reserved bytes, must be 0
46
+ @file.seek 6, IO::SEEK_CUR
47
+
48
+ @channels = @file.read_ushort
49
+ @rows = @file.read_uint
50
+ @cols = @file.read_uint
51
+ @depth = @file.read_ushort
52
+ @mode = @file.read_ushort
53
+
54
+ color_data_len = @file.read_uint
55
+ @file.seek color_data_len, IO::SEEK_CUR
54
56
  end
55
57
 
56
- # Height of the entire document in pixels.
57
- def height
58
- rows
58
+ def mode_name
59
+ MODES[@mode]
59
60
  end
60
61
 
61
62
  def rgb?
@@ -35,24 +35,18 @@ class PSD
35
35
  finish = @file.tell + byte_count
36
36
 
37
37
  while @file.tell < finish
38
- len = @file.read(1).bytes.to_a[0]
38
+ len = @file.read_byte
39
39
 
40
40
  if len < 128
41
41
  len += 1
42
- (@chan_pos...@chan_pos+len).each do |k|
43
- @channel_data[k] = @file.read(1).bytes.to_a[0]
44
- end
45
-
42
+ @channel_data.insert @chan_pos, *@file.read(len).bytes.to_a
46
43
  @chan_pos += len
47
44
  elsif len > 128
48
45
  len ^= 0xff
49
46
  len += 2
50
47
 
51
- val = @file.read(1).bytes.to_a[0]
52
- (@chan_pos...@chan_pos+len).each do |k|
53
- @channel_data[k] = val
54
- end
55
-
48
+ val = @file.read(1).bytes.to_a
49
+ @channel_data.insert @chan_pos, *(val * len)
56
50
  @chan_pos += len
57
51
  end
58
52
  end
@@ -7,16 +7,16 @@ class PSD
7
7
  def combine_rgb_channel
8
8
  PSD.logger.debug "Beginning RGB processing"
9
9
 
10
+ rgb_channels = @channels_info.map { |ch| ch[:id] }.reject { |ch| ch < -1 }
11
+
10
12
  (0...@num_pixels).step(pixel_step) do |i|
11
13
  r = g = b = 0
12
14
  a = 255
13
15
 
14
- @channels_info.each_with_index do |chan, index|
15
- next if chan[:id] == -2
16
-
16
+ rgb_channels.each_with_index do |chan, index|
17
17
  val = @channel_data[i + (@channel_length * index)]
18
18
 
19
- case chan[:id]
19
+ case chan
20
20
  when -1 then a = val
21
21
  when 0 then r = val
22
22
  when 1 then g = val
data/lib/psd/layer.rb CHANGED
@@ -2,17 +2,16 @@ class PSD
2
2
  # Represents a single layer and all of the data associated with
3
3
  # that layer.
4
4
  class Layer
5
- include Section
6
5
  include BlendModes
7
6
  include BlendingRanges
8
7
  include ChannelImage
9
8
  include Exporting
10
- include Helpers
11
9
  include Info
12
10
  include Mask
13
11
  include Name
14
12
  include PathComponents
15
13
  include PositionAndChannels
14
+ include Helpers
16
15
 
17
16
  attr_reader :id, :info_keys, :header
18
17
  attr_accessor :group_layer, :node, :file
@@ -29,9 +28,6 @@ class PSD
29
28
  @blend_mode = {}
30
29
  @group_layer = nil
31
30
 
32
- @blending_mode = 'normal'
33
- @opacity = 255
34
-
35
31
  # Just used for tracking which layer adjustments we're parsing.
36
32
  # Not essential.
37
33
  @info_keys = []
@@ -39,8 +35,6 @@ class PSD
39
35
 
40
36
  # Parse the layer and all of it's sub-sections.
41
37
  def parse(index=nil)
42
- start_section
43
-
44
38
  @id = index
45
39
 
46
40
  parse_position_and_channels
@@ -58,7 +52,6 @@ class PSD
58
52
 
59
53
  @file.seek @layer_end # Skip over any filler zeros
60
54
 
61
- end_section
62
55
  return self
63
56
  end
64
57
 
@@ -66,12 +59,5 @@ class PSD
66
59
  def [](val)
67
60
  self.send(val)
68
61
  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
62
  end
77
63
  end