psd 2.1.2 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
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