psd 0.3.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +161 -0
- data/Rakefile +1 -0
- data/circle.yml +6 -0
- data/examples/export.rb +7 -0
- data/examples/export_image.rb +12 -0
- data/examples/export_text_data.rb +13 -0
- data/examples/images/example-cmyk.psd +0 -0
- data/examples/images/example-greyscale.psd +0 -0
- data/examples/images/example.psd +0 -0
- data/examples/images/example16.psd +0 -0
- data/examples/parse.rb +31 -0
- data/examples/path.rb +7 -0
- data/examples/tree.rb +8 -0
- data/examples/unimplemented_info.rb +9 -0
- data/lib/psd.rb +145 -0
- data/lib/psd/blend_mode.rb +75 -0
- data/lib/psd/channel_image.rb +9 -0
- data/lib/psd/color.rb +125 -0
- data/lib/psd/descriptor.rb +172 -0
- data/lib/psd/file.rb +99 -0
- data/lib/psd/header.rb +61 -0
- data/lib/psd/helpers.rb +35 -0
- data/lib/psd/image.rb +107 -0
- data/lib/psd/image_exports/png.rb +36 -0
- data/lib/psd/image_formats/raw.rb +14 -0
- data/lib/psd/image_formats/rle.rb +67 -0
- data/lib/psd/image_modes/cmyk.rb +27 -0
- data/lib/psd/image_modes/greyscale.rb +36 -0
- data/lib/psd/image_modes/rgb.rb +25 -0
- data/lib/psd/layer.rb +342 -0
- data/lib/psd/layer_info.rb +22 -0
- data/lib/psd/layer_info/fill_opacity.rb +13 -0
- data/lib/psd/layer_info/layer_id.rb +13 -0
- data/lib/psd/layer_info/layer_name_source.rb +12 -0
- data/lib/psd/layer_info/layer_section_divider.rb +35 -0
- data/lib/psd/layer_info/legacy_typetool.rb +88 -0
- data/lib/psd/layer_info/object_effects.rb +16 -0
- data/lib/psd/layer_info/placed_layer.rb +14 -0
- data/lib/psd/layer_info/reference_point.rb +16 -0
- data/lib/psd/layer_info/typetool.rb +127 -0
- data/lib/psd/layer_info/unicode_name.rb +18 -0
- data/lib/psd/layer_info/vector_mask.rb +25 -0
- data/lib/psd/layer_mask.rb +106 -0
- data/lib/psd/mask.rb +45 -0
- data/lib/psd/node.rb +51 -0
- data/lib/psd/node_exporting.rb +20 -0
- data/lib/psd/node_group.rb +67 -0
- data/lib/psd/node_layer.rb +71 -0
- data/lib/psd/node_root.rb +78 -0
- data/lib/psd/nodes/ancestry.rb +82 -0
- data/lib/psd/nodes/has_children.rb +13 -0
- data/lib/psd/nodes/lock_to_origin.rb +7 -0
- data/lib/psd/nodes/parse_layers.rb +18 -0
- data/lib/psd/nodes/search.rb +28 -0
- data/lib/psd/pascal_string.rb +14 -0
- data/lib/psd/path_record.rb +180 -0
- data/lib/psd/resource.rb +27 -0
- data/lib/psd/resources.rb +47 -0
- data/lib/psd/section.rb +26 -0
- data/lib/psd/util.rb +17 -0
- data/lib/psd/version.rb +3 -0
- data/psd.gemspec +30 -0
- data/spec/files/example.psd +0 -0
- data/spec/files/one_layer.psd +0 -0
- data/spec/files/path.psd +0 -0
- data/spec/files/simplest.psd +0 -0
- data/spec/files/text.psd +0 -0
- data/spec/hierarchy_spec.rb +86 -0
- data/spec/identity_spec.rb +34 -0
- data/spec/parsing_spec.rb +134 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/text_spec.rb +12 -0
- metadata +231 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0aedda5ca28cfc042d4b1ade1edb805c8839fe7
|
4
|
+
data.tar.gz: 4b40477a1bd67e4eb2f831eb90c01af7ee366635
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1c266acc7f5017d7e16312f9c75ec38a2b806f3ca32987105fe32e156fbc55484db2dcba41b5786c95fc3fd70df8ff032615aa8b83100ab72a03130fe7de4a3a
|
7
|
+
data.tar.gz: 51a2da94d3778c56dc2feec7a7b14d8524343224515dd9a656e8a65475d5149145d4dad793331a97b0dfa814eef005aa687ae74862402f9c27d12412129d58c1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 LayerVault
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
# PSD.rb
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
A general purpose Photoshop file parser written in Ruby. It allows you to work with a Photoshop document in a manageable tree structure and find out important data such as:
|
6
|
+
|
7
|
+
* Document structure
|
8
|
+
* Document size
|
9
|
+
* Layer/folder size + positioning
|
10
|
+
* Layer/folder names
|
11
|
+
* Layer/folder visibility and opacity
|
12
|
+
* Font data (via [psd-enginedata](https://github.com/layervault/psd-enginedata))
|
13
|
+
* Text area contents
|
14
|
+
* Font names
|
15
|
+
* Font sizes
|
16
|
+
* Color mode and bit-depth
|
17
|
+
* Vector mask data
|
18
|
+
* Flattened image data
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
gem 'psd'
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
$ bundle
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
$ gem install psd
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
**Loading a PSD**
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
require 'psd'
|
40
|
+
|
41
|
+
psd = PSD.new('/path/to/file.psd')
|
42
|
+
psd.parse!
|
43
|
+
```
|
44
|
+
|
45
|
+
**Traversing the Document**
|
46
|
+
|
47
|
+
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:
|
48
|
+
|
49
|
+
* `root`: get the root node from anywhere in the tree
|
50
|
+
* `ancestors`: get all ancestors in the path of this node (excluding the root)
|
51
|
+
* `siblings`: get all sibling tree nodes including the current one (e.g. all layers in a folder)
|
52
|
+
* `descendants`: get all descendant nodes not including the current one
|
53
|
+
* `subtree`: same as descendants but starts with the current node
|
54
|
+
* `depth`: calculate the depth of the current node
|
55
|
+
|
56
|
+
For any of the traversal methods, you can also retrieves folder or layer nodes only by appending `_layers` or `_groups` to the method. For example:
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
psd.tree.descendant_layers
|
60
|
+
```
|
61
|
+
|
62
|
+
**Accessing Layer Data**
|
63
|
+
|
64
|
+
To get data such as the name or dimensions of a layer:
|
65
|
+
|
66
|
+
``` ruby
|
67
|
+
psd.tree.descendant_layers.first.name
|
68
|
+
psd.tree.descendant_layers.first.width
|
69
|
+
```
|
70
|
+
|
71
|
+
PSD files also store various pieces of information in "layer info" blocks. Which blocks a layer has varies from layer-to-layer, but to access them you can do:
|
72
|
+
|
73
|
+
``` ruby
|
74
|
+
psd.tree.descendant_layers.first.type.font
|
75
|
+
|
76
|
+
# Returns
|
77
|
+
{:name=>"HelveticaNeue-Light",
|
78
|
+
:sizes=>[33.0],
|
79
|
+
:colors=>[[255, 19, 120, 98]],
|
80
|
+
:css=>
|
81
|
+
"font-family: \"HelveticaNeue-Light\", \"AdobeInvisFont\", \"MyriadPro-Regular\";\nfont-size: 33.0pt;\ncolor: rgba(19, 120, 98, 255);"}
|
82
|
+
```
|
83
|
+
|
84
|
+
**Exporting Data**
|
85
|
+
|
86
|
+
When working with the tree structure, you can recursively export any node to a Hash.
|
87
|
+
|
88
|
+
``` ruby
|
89
|
+
pp psd.tree.to_hash
|
90
|
+
```
|
91
|
+
|
92
|
+
Which produces something like:
|
93
|
+
|
94
|
+
``` ruby
|
95
|
+
{:children=>
|
96
|
+
[{:type=>:group,
|
97
|
+
:visible=>false,
|
98
|
+
:opacity=>1.0,
|
99
|
+
:blending_mode=>"normal",
|
100
|
+
:name=>"Version D",
|
101
|
+
:left=>0,
|
102
|
+
:right=>900,
|
103
|
+
:top=>0,
|
104
|
+
:bottom=>600,
|
105
|
+
:height=>900,
|
106
|
+
:width=>600,
|
107
|
+
:children=>
|
108
|
+
[{:type=>:layer,
|
109
|
+
:visible=>true,
|
110
|
+
:opacity=>1.0,
|
111
|
+
:blending_mode=>"normal",
|
112
|
+
:name=>"Make a change and save.",
|
113
|
+
:left=>275,
|
114
|
+
:right=>636,
|
115
|
+
:top=>435,
|
116
|
+
:bottom=>466,
|
117
|
+
:height=>31,
|
118
|
+
:width=>361,
|
119
|
+
:text=>
|
120
|
+
{:value=>"Make a change and save.",
|
121
|
+
:font=>
|
122
|
+
{:name=>"HelveticaNeue-Light",
|
123
|
+
:sizes=>[33.0],
|
124
|
+
:colors=>[[255, 19, 120, 98]],
|
125
|
+
:css=>
|
126
|
+
"font-family: \"HelveticaNeue-Light\", \"AdobeInvisFont\", \"MyriadPro-Regular\";\nfont-size: 33.0pt;\ncolor: rgba(19, 120, 98, 255);"},
|
127
|
+
:left=>0,
|
128
|
+
:top=>0,
|
129
|
+
:right=>0,
|
130
|
+
:bottom=>0,
|
131
|
+
:transform=>
|
132
|
+
{:xx=>1.0, :xy=>0.0, :yx=>0.0, :yy=>1.0, :tx=>456.0, :ty=>459.0}},
|
133
|
+
:ref_x=>264.0,
|
134
|
+
:ref_y=>-3.0}]
|
135
|
+
}],
|
136
|
+
:document=>{:width=>900, :height=>600}}
|
137
|
+
```
|
138
|
+
|
139
|
+
You can also export the PSD to a flattened image. Please note that, at this time, not all image modes + depths are supported.
|
140
|
+
|
141
|
+
``` ruby
|
142
|
+
png = psd.image.to_png # reference to PNG data
|
143
|
+
psd.image.save_as_png 'path/to/output.png' # writes PNG to disk
|
144
|
+
```
|
145
|
+
|
146
|
+
## To-do
|
147
|
+
|
148
|
+
There are a few features that are currently missing from PSD.rb.
|
149
|
+
|
150
|
+
* Global layer mask
|
151
|
+
* Individual layer image exporting
|
152
|
+
* More image modes + depths for image exporting
|
153
|
+
* A few layer info blocks
|
154
|
+
|
155
|
+
## Contributing
|
156
|
+
|
157
|
+
1. Fork it
|
158
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
159
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
160
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
161
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/circle.yml
ADDED
data/examples/export.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require './lib/psd'
|
3
|
+
|
4
|
+
psd = PSD.new('examples/images/example.psd')
|
5
|
+
|
6
|
+
results = Benchmark.measure "Image exporting" do
|
7
|
+
psd.image.save_as_png 'output.png'
|
8
|
+
end
|
9
|
+
|
10
|
+
puts "Flattened image exported to ./output.png\n"
|
11
|
+
puts Benchmark::CAPTION
|
12
|
+
puts results.to_s
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require './lib/psd'
|
2
|
+
|
3
|
+
file = ARGV[0] || 'examples/images/example.psd'
|
4
|
+
psd = PSD.new(file)
|
5
|
+
psd.parse!
|
6
|
+
|
7
|
+
outfile = './enginedata'
|
8
|
+
psd.tree.descendant_layers.each do |l|
|
9
|
+
next unless l.layer.adjustments[:type]
|
10
|
+
File.write(outfile, l.layer.adjustments[:type].engine_data)
|
11
|
+
puts "Exported to #{outfile}"
|
12
|
+
exit
|
13
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/examples/parse.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require './lib/psd'
|
3
|
+
|
4
|
+
psd = nil
|
5
|
+
file = ARGV[0] || 'examples/images/example.psd'
|
6
|
+
results = Benchmark.measure "PSD parsing" do
|
7
|
+
psd = PSD.new(file)
|
8
|
+
psd.parse!
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "\nVisible Layers:\n===================="
|
12
|
+
psd.layers.each do |layer|
|
13
|
+
next if layer.folder? || layer.hidden?
|
14
|
+
|
15
|
+
puts "Name: #{layer.name}"
|
16
|
+
puts "Position: top = #{layer.top}, left = #{layer.left}"
|
17
|
+
puts "Size: width = #{layer.width}, height = #{layer.height}"
|
18
|
+
puts "Mask: width = #{layer.mask.width}, height = #{layer.mask.height}"
|
19
|
+
puts "Reference point: #{layer.ref_x}, #{layer.ref_y}"
|
20
|
+
|
21
|
+
puts ""
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "\nPSD Info:\n===================="
|
25
|
+
puts "#{psd.width}x#{psd.height} #{psd.header.mode_name}"
|
26
|
+
puts "#{psd.resources.size} resources parsed"
|
27
|
+
puts "#{psd.layers.size} layers, #{psd.folders.size} folders"
|
28
|
+
|
29
|
+
puts "\nBenchmark Results (seconds):\n===================="
|
30
|
+
puts " "*7 + Benchmark::CAPTION
|
31
|
+
puts "parse: " + results.to_s
|
data/examples/path.rb
ADDED
data/examples/tree.rb
ADDED
data/lib/psd.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require "bindata"
|
2
|
+
require "psd/enginedata"
|
3
|
+
|
4
|
+
require_relative 'psd/section'
|
5
|
+
|
6
|
+
dir_root = File.dirname(File.absolute_path(__FILE__)) + '/psd'
|
7
|
+
[
|
8
|
+
'/image_formats/*',
|
9
|
+
'/image_modes/*',
|
10
|
+
'/image_exports/*',
|
11
|
+
'/nodes/*',
|
12
|
+
'/layer_info/**/*',
|
13
|
+
'/**/*'
|
14
|
+
].each do |path|
|
15
|
+
Dir.glob(dir_root + path) { |file| require file if File.file?(file) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# A general purpose parser for Photoshop files. PSDs are broken up in to 4 logical sections:
|
19
|
+
# the header, resources, the layer mask (including layers), and the preview image. We parse
|
20
|
+
# each of these sections in order.
|
21
|
+
class PSD
|
22
|
+
include Helpers
|
23
|
+
include NodeExporting
|
24
|
+
|
25
|
+
# Just used to track what layer info keys we didn't parse in this file for development purposes.
|
26
|
+
def self.keys; @@keys; end
|
27
|
+
@@keys = []
|
28
|
+
|
29
|
+
DEFAULTS = {
|
30
|
+
parse_image: false,
|
31
|
+
parse_layer_images: false
|
32
|
+
}
|
33
|
+
|
34
|
+
attr_reader :file
|
35
|
+
|
36
|
+
# Create and store a reference to our PSD file
|
37
|
+
def initialize(file, opts={})
|
38
|
+
@file = PSD::File.new(file, 'rb')
|
39
|
+
@file.seek 0 # If the file was previously used and not closed
|
40
|
+
|
41
|
+
@opts = DEFAULTS.merge(opts)
|
42
|
+
@header = nil
|
43
|
+
@resources = nil
|
44
|
+
@layer_mask = nil
|
45
|
+
@parsed = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# There is a specific order that must be followed when parsing
|
49
|
+
# the PSD. Sections can be skipped if needed. This method will
|
50
|
+
# parse all sections of the PSD.
|
51
|
+
def parse!
|
52
|
+
header
|
53
|
+
resources
|
54
|
+
layer_mask
|
55
|
+
image if @opts[:parse_image]
|
56
|
+
|
57
|
+
@parsed = true
|
58
|
+
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Has our PSD been parsed yet?
|
63
|
+
def parsed?
|
64
|
+
@parsed
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the Header, parsing it if needed.
|
68
|
+
def header
|
69
|
+
@header ||= Header.read(@file)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the Resources section, parsing if needed.
|
73
|
+
def resources
|
74
|
+
return @resources.data unless @resources.nil?
|
75
|
+
|
76
|
+
ensure_header
|
77
|
+
|
78
|
+
@resources = Resources.new(@file)
|
79
|
+
@resources.parse
|
80
|
+
|
81
|
+
return @resources.data
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the LayerMask section. Ensures the header and resources
|
85
|
+
# have been parsed first since they are required.
|
86
|
+
def layer_mask
|
87
|
+
ensure_header
|
88
|
+
ensure_resources
|
89
|
+
|
90
|
+
@layer_mask ||= LayerMask.new(@file, @header).parse
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get the full size flattened preview Image.
|
94
|
+
def image
|
95
|
+
ensure_header
|
96
|
+
ensure_resources
|
97
|
+
ensure_layer_mask
|
98
|
+
|
99
|
+
@image ||= Image.new(@file, @header).parse
|
100
|
+
end
|
101
|
+
|
102
|
+
# Export the current file to a new PSD. This may or may not work.
|
103
|
+
def export(file)
|
104
|
+
parse! unless parsed?
|
105
|
+
|
106
|
+
# Create our file for writing
|
107
|
+
outfile = File.open(file, 'w')
|
108
|
+
|
109
|
+
# Reset the file pointer
|
110
|
+
@file.seek 0
|
111
|
+
@header.write outfile
|
112
|
+
@file.seek @header.num_bytes, IO::SEEK_CUR
|
113
|
+
|
114
|
+
# Nothing in the header or resources we want to bother with changing
|
115
|
+
# right now. Write it straight to file.
|
116
|
+
outfile.write @file.read(@resources.end_of_section - @file.tell)
|
117
|
+
|
118
|
+
# Now, the changeable part. Layers and masks.
|
119
|
+
layer_mask.export(outfile)
|
120
|
+
|
121
|
+
# And the rest of the file (merged image data)
|
122
|
+
outfile.write @file.read
|
123
|
+
outfile.flush
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def ensure_header
|
129
|
+
header # Header is always required
|
130
|
+
end
|
131
|
+
|
132
|
+
def ensure_resources
|
133
|
+
return unless @resources.nil?
|
134
|
+
|
135
|
+
@resources = Resources.new(@file)
|
136
|
+
@resources.skip
|
137
|
+
end
|
138
|
+
|
139
|
+
def ensure_layer_mask
|
140
|
+
return unless @layer_mask.nil?
|
141
|
+
|
142
|
+
@layer_mask = LayerMask.new(@file, @header)
|
143
|
+
@layer_mask.skip
|
144
|
+
end
|
145
|
+
end
|