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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 62a564e222daa1c918ef3bdf9c2a4f99dfd59833
4
- data.tar.gz: 93c28d4cba1c961cb28734da81f54e1617adcbdd
2
+ SHA256:
3
+ metadata.gz: 1199316b606804a9a2c3812a1fcddc5e62d4171693ad201475ce833774bff52f
4
+ data.tar.gz: de991a2e3bb3c6eae0998d31baff59223510b6c07b0baebd41cea6e485656cab
5
5
  SHA512:
6
- metadata.gz: 5eb251154704e8172420d3aa1d97851f739f516dcda1d659349719933f354e7443e3a4cebad498084ccf475505e5f97fef0a16f5cf15b25c4bdc3264401b9c59
7
- data.tar.gz: 7b4c1554a65b7f491f197839b2a99fe383e97c6b1f79ee0528374c919e11abbf3d26293c1b27fc11ca11098b64594651d820d4e9a88e192a4ef4182f0d922106
6
+ metadata.gz: 3299a64c6b5b21dd38d844bea52ae6d42c1ddd864e8b9a9e0bdccc6690d0340beae15852c3c682d74ef1fe658813aff233c350b3daf2e9dad97cca8d228cc4e1
7
+ data.tar.gz: ad77e3095d489ad3799a61d95b35e37a72e48adb6fd76b89b0160cb425e4b87c23132b4a3c998c3179d1ad0c9a5910a8c88d4f7a16d6ffbe140a5d2c8d69dca4
data/.travis.yml CHANGED
@@ -1,6 +1,8 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
4
  - 1.9.3
4
5
  - 2.0.0
5
6
  - 2.1.0
7
+ - 2.2.1
6
8
  - jruby-19mode
data/README.md CHANGED
@@ -19,9 +19,8 @@ A general purpose Photoshop file parser written in Ruby. It allows you to work w
19
19
 
20
20
  PSD.rb is tested against:
21
21
 
22
- * MRI 1.9.3 & 2.0.0
22
+ * MRI 1.9.3, 2.0.0, 2.1.0
23
23
  * JRuby (1.9.3 mode)
24
- * Rubinius (1.9.3 mode)
25
24
 
26
25
  If you use MRI Ruby and are interested in significantly speeding up PSD.rb with native code, check out [psd_native](https://github.com/layervault/psd_native).
27
26
 
@@ -76,15 +75,23 @@ end
76
75
 
77
76
  ### Traversing the Document
78
77
 
79
- To access the document as a tree structure, use `psd.tree` to get the root node. From there, you can traverse the tree using any of these methods:
78
+ To access the document as a tree structure, use `psd.tree` to get the root node. From there, work with the tree using any of these methods:
80
79
 
81
80
  * `root`: get the root node from anywhere in the tree
81
+ * `root?`: is this the root node?
82
82
  * `children`: get all immediate children of the node
83
+ * `has_children?`: does this node have any children?
84
+ * `childless?`: opposite of `has_children?`
83
85
  * `ancestors`: get all ancestors in the path of this node (excluding the root)
84
86
  * `siblings`: get all sibling tree nodes including the current one (e.g. all layers in a folder)
87
+ * `next_sibling`: gets the sibling immediately following the current node
88
+ * `prev_sibling`: gets the sibling immediately before the current node
89
+ * `has_siblings?`: does this node have any siblings?
90
+ * `only_child?`: opposite of `has_siblings?`
85
91
  * `descendants`: get all descendant nodes not including the current one
86
92
  * `subtree`: same as descendants but starts with the current node
87
- * `depth`: calculate the depth of the current node
93
+ * `depth`: calculate the depth of the current node (root node is 0)
94
+ * `path`: gets the path to the current node
88
95
 
89
96
  For any of the traversal methods, you can also retrieve folder or layer nodes only by appending `_layers` or `_groups` to the method. For example:
90
97
 
@@ -96,11 +103,12 @@ If you know the path to a group or layer within the tree, you can search by that
96
103
 
97
104
  ``` ruby
98
105
  psd.tree.children_at_path("Version A/Matte")
106
+ psd.tree.children_at_path(["Version A", "Matte"])
99
107
  ```
100
108
 
101
109
  ### Layer Comps
102
110
 
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:
111
+ 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
112
 
105
113
  ``` ruby
106
114
  # Get information about all the available layer comps
@@ -111,7 +119,7 @@ tree = psd.tree.filter_by_comp('Version A')
111
119
  puts tree.children.map(&:name)
112
120
  ```
113
121
 
114
- This returns a new node tree and does not alter the original so you won't lose any data.
122
+ This returns a new node tree and does not alter the original.
115
123
 
116
124
  ### Accessing Layer Data
117
125
 
@@ -197,32 +205,64 @@ png = psd.image.to_png # reference to PNG data
197
205
  psd.image.save_as_png 'path/to/output.png' # writes PNG to disk
198
206
  ```
199
207
 
200
- ### Debugging
208
+ This uses the full rasterized preview provided by Photoshop. It does not use the built-in rendering engine (described below). If the file was not saved with Compatibility Mode enabled, this will return an empty image.
201
209
 
202
- If you run into any problems parsing a PSD, you can enable debug logging via the `PSD_DEBUG` environment variable. For example:
210
+ ### Preview Building
203
211
 
204
- ``` bash
205
- PSD_DEBUG=true bundle exec examples/parse.rb
212
+ You can build previews of any subset or version of the PSD document using the built-in renderer. This is useful for generating previews of layer comps or exporting individual layer groups as images.
213
+
214
+ ``` ruby
215
+ # Save a layer comp
216
+ psd.tree.filter_by_comp("Version A").save_as_png('./Version A.png')
217
+
218
+ # Generate PNG of individual layer group
219
+ psd.tree.children_at_path("Group 1").first.to_png
206
220
  ```
207
221
 
208
- You can also give a path to a file instead. If you need to enable debugging programatically:
222
+ ### Slices
223
+
224
+ Because slices are relative to the full document, you can access them directly on the `psd` object. Use `psd.slices` to get an array of all slices in the document.
209
225
 
210
226
  ``` ruby
211
- PSD.debug = true
227
+ slices = psd.slices
228
+ slices.first.name #=> "Logo"
229
+ slices.first.left #=> 20
230
+ slices.first.width #=> 200
212
231
  ```
213
232
 
214
- ### Preview Building
233
+ You can also search for slices if you know their name or ID. Because slice names do not need to be unique, `slices_by_name` will always return an array of all matches.
215
234
 
216
- **This is currently an experimental feature. It works "well enough" but is not perfect yet.**
235
+ ``` ruby
236
+ psd.slice_by_id(2)
237
+ psd.slices_by_name('Logo')
238
+ ```
217
239
 
218
- You can build previews of any subset or version of the PSD document. This is useful for generating previews of layer comps or exporting individual layer groups as images.
240
+ When you create a slice based off of a layer, Photoshop stores this relation in the file. If you have a slice that was created this way, you can easily get the associated layer.
219
241
 
220
242
  ``` ruby
221
- # Save a layer comp
222
- psd.tree.filter_by_comp("Version A").save_as_png('./Version A.png')
243
+ slice = psd.slices_by_name('Logo').first
244
+ slice.associated_layer #=> <PSD::Node::Layer>
245
+ ```
223
246
 
224
- # Generate PNG of individual layer group
225
- psd.tree.children_at_path("Group 1").first.to_png
247
+ Finally, you can export slices as PNGs.
248
+
249
+ ``` ruby
250
+ psd.slices.first.to_png #=> ChunkyPNG canvas
251
+ psd.slices_by_name('Logo').first.save_as_png('Logo.png') #=> writes Logo.png
252
+ ```
253
+
254
+ ### Debugging
255
+
256
+ If you run into any problems parsing a PSD, you can enable debug logging via the `PSD_DEBUG` environment variable. For example:
257
+
258
+ ``` bash
259
+ PSD_DEBUG=true bundle exec examples/parse.rb
260
+ ```
261
+
262
+ If you need to enable debugging programatically:
263
+
264
+ ``` ruby
265
+ PSD.debug = true
226
266
  ```
227
267
 
228
268
  ## To-do
@@ -230,6 +270,6 @@ psd.tree.children_at_path("Group 1").first.to_png
230
270
  There are a few features that are currently missing from PSD.rb.
231
271
 
232
272
  * More image modes + depths for image exporting
233
- * A few layer info blocks
234
273
  * Support for rendering all layer styles
274
+ * Support for layer comp adjusted layer styles
235
275
  * Render engine fixes for groups with lowered opacity
@@ -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,6 @@
1
- require_relative 'image'
1
+ require 'psd/image'
2
+ require 'psd/image_formats/layer_rle'
3
+ require 'psd/image_formats/layer_raw'
2
4
 
3
5
  class PSD
4
6
  # Represents an image for a single layer, which differs slightly in format from
@@ -18,7 +20,7 @@ class PSD
18
20
  super(file, header)
19
21
 
20
22
  @channels_info = @layer.channels_info
21
- @has_mask = @layer.mask.width * @layer.mask.height > 0
23
+ @has_mask = @channels_info.any? { |chan| chan[:id] == -2 }
22
24
  @opacity = @layer.opacity / 255.0
23
25
  @mask_data = []
24
26
  end
@@ -47,7 +49,7 @@ class PSD
47
49
 
48
50
  # If the ID of this current channel is -2, then we assume the dimensions
49
51
  # of the layer mask.
50
- if ch_info[:id] == -2
52
+ if ch_info[:id] < -1
51
53
  @width = @layer.mask.width
52
54
  @height = @layer.mask.height
53
55
  else
@@ -97,8 +99,12 @@ class PSD
97
99
  def parse_user_mask
98
100
  return unless has_mask?
99
101
 
100
- channel = @channels_info.select { |c| c[:id] == -2 }.first
101
- index = @channels_info.index { |c| c[:id] == -2 }
102
+ mask_id = -2
103
+
104
+ PSD.logger.debug "Using mask in channel #{mask_id}"
105
+
106
+ channel = @channels_info.select { |c| c[:id] == mask_id }.first
107
+ index = @channels_info.index { |c| c[:id] == mask_id }
102
108
  return if channel.nil?
103
109
 
104
110
  start = @channel_length * index
data/lib/psd/color.rb CHANGED
@@ -6,8 +6,17 @@ class PSD
6
6
  module Color
7
7
  extend self
8
8
 
9
- # This is a relic of libpsd that will likely go away in a future version. It
10
- # stored the entire color value in a 32-bit address space for speed.
9
+ COLOR_SPACE = {
10
+ 0 => :rgb,
11
+ 1 => :hsb,
12
+ 2 => :cmyk,
13
+ 7 => :lab,
14
+ 8 => :grayscale
15
+ }
16
+
17
+ # In some places in the PSD file, colors are stored with a short that
18
+ # describes the color space, and the following 4 bytes that store the
19
+ # color data.
11
20
  def color_space_to_argb(color_space, color_component)
12
21
  color = case color_space
13
22
  when 0
@@ -20,7 +29,7 @@ class PSD
20
29
  color_component[1] / 100.0, color_component[2] / 100.0,
21
30
  color_component[3] / 100.0
22
31
  when 7
23
- lab_to_color *color_component
32
+ alab_to_color *color_component
24
33
  else
25
34
  0x00FFFFFF
26
35
  end
@@ -101,7 +110,7 @@ class PSD
101
110
 
102
111
  def alab_to_color(alpha, l, a, b)
103
112
  xyz = lab_to_xyz(l, a, b)
104
- axyz_to_color alpha, xyz[:x], xyz[:y], xyz[:z]
113
+ axyz_to_color alpha, xyz[0], xyz[1], xyz[2]
105
114
  end
106
115
 
107
116
  def lab_to_xyz(l, a, b)
@@ -122,4 +131,4 @@ class PSD
122
131
  }.map { |k, v| [k, Util.clamp(v, 0, 255)] }]
123
132
  end
124
133
  end
125
- end
134
+ end
@@ -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/file.rb CHANGED
@@ -82,7 +82,10 @@ class PSD
82
82
  # Reads a unicode string, which is double the length of a normal string and encoded as UTF-16.
83
83
  def read_unicode_string(length=nil)
84
84
  length ||= read_int if length.nil?
85
- !length.nil? && length > 0 ? read(length * 2).encode('UTF-8', 'UTF-16BE').delete("\000") : ''
85
+ return '' if length.nil? || length <= 0
86
+ read(length * 2)
87
+ .encode('UTF-8', 'UTF-16BE', universal_newline: true)
88
+ .delete("\000")
86
89
  end
87
90
 
88
91
  # Reads a boolean value.
@@ -93,12 +96,12 @@ class PSD
93
96
  # Reads a 32-bit color space value.
94
97
  def read_space_color
95
98
  color_space = read_short
96
- color_component = []
97
- 4.times do |i|
98
- color_component.push(read_short >> 8)
99
+ color_components = []
100
+ 4.times.map do |i|
101
+ color_components.push(read_short >> 8)
99
102
  end
100
103
 
101
- Color.color_space_to_argb(color_space, color_component)
104
+ { color_mode: color_space, color_components: color_components }
102
105
  end
103
106
  end
104
- end
107
+ end
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,46 @@ 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
56
+ end
57
+
58
+ def mode_name
59
+ MODES[@mode]
54
60
  end
55
61
 
56
- # Height of the entire document in pixels.
57
- def height
58
- rows
62
+ def big?
63
+ version == 2
59
64
  end
60
65
 
61
66
  def rgb?
@@ -66,4 +71,4 @@ class PSD
66
71
  mode == 4
67
72
  end
68
73
  end
69
- end
74
+ end
data/lib/psd/helpers.rb CHANGED
@@ -45,7 +45,7 @@ class PSD
45
45
  end
46
46
 
47
47
  def slices
48
- resource(:slices).to_a
48
+ @slices ||= resource(:slices).to_a.map { |s| Slice.new(self, s) }
49
49
  end
50
50
  end
51
- end
51
+ end
data/lib/psd/image.rb CHANGED
@@ -1,6 +1,14 @@
1
+ require 'psd/image_formats/raw'
2
+ require 'psd/image_formats/rle'
3
+ require 'psd/image_modes/cmyk'
4
+ require 'psd/image_modes/greyscale'
5
+ require 'psd/image_modes/rgb'
6
+ require 'psd/image_exports/png'
7
+
1
8
  class PSD
2
9
  # Parses the full preview image at the end of the PSD document.
3
10
  class Image
11
+ extend Forwardable
4
12
  include ImageFormat::RAW
5
13
  include ImageFormat::RLE
6
14
  include ImageMode::CMYK
@@ -11,6 +19,9 @@ class PSD
11
19
  attr_reader :pixel_data, :opacity, :has_mask
12
20
  alias :has_mask? :has_mask
13
21
 
22
+ # We delegate a few useful methods to the header.
23
+ def_delegators :@header, :height, :width, :channels, :depth, :mode
24
+
14
25
  # All of the possible compression formats Photoshop uses.
15
26
  COMPRESSIONS = [
16
27
  'Raw',
@@ -29,6 +40,8 @@ class PSD
29
40
  @num_pixels *= 2 if depth == 16
30
41
 
31
42
  calculate_length
43
+ set_channels_info
44
+
32
45
  @channel_data = []
33
46
  @pixel_data = []
34
47
  @opacity = 1.0
@@ -38,15 +51,6 @@ class PSD
38
51
  @end_pos = @start_pos + @length
39
52
 
40
53
  PSD.logger.debug "Image: #{width}x#{height}, length = #{@length}, mode = #{@header.mode_name}, position = #{@start_pos}"
41
-
42
- # Each color channel is represented by a unique ID
43
- @channels_info = [
44
- {id: 0},
45
- {id: 1},
46
- {id: 2}
47
- ]
48
-
49
- @channels_info << {id: -1} if channels == 4
50
54
  end
51
55
 
52
56
  # Begins parsing the image by first figuring out the compression format used, and then
@@ -67,15 +71,16 @@ class PSD
67
71
  return self
68
72
  end
69
73
 
70
- # We delegate a few useful methods to the header.
71
- [:height, :width, :channels, :depth, :mode].each do |attribute|
72
- define_method attribute do
73
- @header.send(attribute)
74
+ private
75
+
76
+ def set_channels_info
77
+ case mode
78
+ when 1 then set_greyscale_channels
79
+ when 3 then set_rgb_channels
80
+ when 4 then set_cmyk_channels
74
81
  end
75
82
  end
76
83
 
77
- private
78
-
79
84
  def calculate_length
80
85
  @length = case depth
81
86
  when 1 then (width + 7) / 8 * height
@@ -120,4 +125,4 @@ class PSD
120
125
  @pixel_data[i]
121
126
  end
122
127
  end
123
- end
128
+ end
@@ -7,7 +7,7 @@ class PSD
7
7
  def parse_byte_counts!
8
8
  byte_counts = []
9
9
  height.times do
10
- byte_counts << @file.read_short
10
+ byte_counts << (@header.big? ? @file.read_int : @file.read_short)
11
11
  end
12
12
 
13
13
  return byte_counts
@@ -21,4 +21,4 @@ class PSD
21
21
  end
22
22
  end
23
23
  end
24
- end
24
+ end