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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1199316b606804a9a2c3812a1fcddc5e62d4171693ad201475ce833774bff52f
|
4
|
+
data.tar.gz: de991a2e3bb3c6eae0998d31baff59223510b6c07b0baebd41cea6e485656cab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3299a64c6b5b21dd38d844bea52ae6d42c1ddd864e8b9a9e0bdccc6690d0340beae15852c3c682d74ef1fe658813aff233c350b3daf2e9dad97cca8d228cc4e1
|
7
|
+
data.tar.gz: ad77e3095d489ad3799a61d95b35e37a72e48adb6fd76b89b0160cb425e4b87c23132b4a3c998c3179d1ad0c9a5910a8c88d4f7a16d6ffbe140a5d2c8d69dca4
|
data/.travis.yml
CHANGED
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
|
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,
|
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
|
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
|
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
|
-
|
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
|
-
|
210
|
+
### Preview Building
|
203
211
|
|
204
|
-
|
205
|
-
|
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
|
-
|
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
|
-
|
227
|
+
slices = psd.slices
|
228
|
+
slices.first.name #=> "Logo"
|
229
|
+
slices.first.left #=> 20
|
230
|
+
slices.first.width #=> 200
|
212
231
|
```
|
213
232
|
|
214
|
-
|
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
|
-
|
235
|
+
``` ruby
|
236
|
+
psd.slice_by_id(2)
|
237
|
+
psd.slices_by_name('Logo')
|
238
|
+
```
|
217
239
|
|
218
|
-
|
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
|
-
|
222
|
-
|
243
|
+
slice = psd.slices_by_name('Logo').first
|
244
|
+
slice.associated_layer #=> <PSD::Node::Layer>
|
245
|
+
```
|
223
246
|
|
224
|
-
|
225
|
-
|
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
|
data/lib/psd/blend_mode.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
class PSD
|
2
|
-
|
3
|
-
|
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: '
|
28
|
-
sLit: '
|
19
|
+
hLit: 'hard_light',
|
20
|
+
sLit: 'soft_light',
|
29
21
|
diff: 'difference',
|
30
22
|
smud: 'exclusion',
|
31
|
-
div: '
|
32
|
-
idiv: '
|
33
|
-
lbrn: '
|
34
|
-
lddg: '
|
35
|
-
vLit: '
|
36
|
-
lLit: '
|
37
|
-
pLit: '
|
38
|
-
hMix: '
|
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: '
|
41
|
-
lgCl: '
|
32
|
+
dkCl: 'darker_color',
|
33
|
+
lgCl: 'lighter_color',
|
42
34
|
fsub: 'subtract',
|
43
35
|
fdiv: 'divide'
|
44
|
-
}
|
36
|
+
}.freeze
|
45
37
|
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
57
|
-
|
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
|
data/lib/psd/channel_image.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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 = @
|
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]
|
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
|
-
|
101
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
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[
|
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
|
data/lib/psd/descriptor.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
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
|
-
|
97
|
-
4.times do |i|
|
98
|
-
|
99
|
+
color_components = []
|
100
|
+
4.times.map do |i|
|
101
|
+
color_components.push(read_short >> 8)
|
99
102
|
end
|
100
103
|
|
101
|
-
|
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
|
-
|
3
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
57
|
-
|
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
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|