psd 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![](https://circleci.com/gh/layervault/psd.rb.png?circle-token=ad8a75fdd86f595e0926a963179a3a621d564c6e)
|
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
|