psd 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +161 -0
  8. data/Rakefile +1 -0
  9. data/circle.yml +6 -0
  10. data/examples/export.rb +7 -0
  11. data/examples/export_image.rb +12 -0
  12. data/examples/export_text_data.rb +13 -0
  13. data/examples/images/example-cmyk.psd +0 -0
  14. data/examples/images/example-greyscale.psd +0 -0
  15. data/examples/images/example.psd +0 -0
  16. data/examples/images/example16.psd +0 -0
  17. data/examples/parse.rb +31 -0
  18. data/examples/path.rb +7 -0
  19. data/examples/tree.rb +8 -0
  20. data/examples/unimplemented_info.rb +9 -0
  21. data/lib/psd.rb +145 -0
  22. data/lib/psd/blend_mode.rb +75 -0
  23. data/lib/psd/channel_image.rb +9 -0
  24. data/lib/psd/color.rb +125 -0
  25. data/lib/psd/descriptor.rb +172 -0
  26. data/lib/psd/file.rb +99 -0
  27. data/lib/psd/header.rb +61 -0
  28. data/lib/psd/helpers.rb +35 -0
  29. data/lib/psd/image.rb +107 -0
  30. data/lib/psd/image_exports/png.rb +36 -0
  31. data/lib/psd/image_formats/raw.rb +14 -0
  32. data/lib/psd/image_formats/rle.rb +67 -0
  33. data/lib/psd/image_modes/cmyk.rb +27 -0
  34. data/lib/psd/image_modes/greyscale.rb +36 -0
  35. data/lib/psd/image_modes/rgb.rb +25 -0
  36. data/lib/psd/layer.rb +342 -0
  37. data/lib/psd/layer_info.rb +22 -0
  38. data/lib/psd/layer_info/fill_opacity.rb +13 -0
  39. data/lib/psd/layer_info/layer_id.rb +13 -0
  40. data/lib/psd/layer_info/layer_name_source.rb +12 -0
  41. data/lib/psd/layer_info/layer_section_divider.rb +35 -0
  42. data/lib/psd/layer_info/legacy_typetool.rb +88 -0
  43. data/lib/psd/layer_info/object_effects.rb +16 -0
  44. data/lib/psd/layer_info/placed_layer.rb +14 -0
  45. data/lib/psd/layer_info/reference_point.rb +16 -0
  46. data/lib/psd/layer_info/typetool.rb +127 -0
  47. data/lib/psd/layer_info/unicode_name.rb +18 -0
  48. data/lib/psd/layer_info/vector_mask.rb +25 -0
  49. data/lib/psd/layer_mask.rb +106 -0
  50. data/lib/psd/mask.rb +45 -0
  51. data/lib/psd/node.rb +51 -0
  52. data/lib/psd/node_exporting.rb +20 -0
  53. data/lib/psd/node_group.rb +67 -0
  54. data/lib/psd/node_layer.rb +71 -0
  55. data/lib/psd/node_root.rb +78 -0
  56. data/lib/psd/nodes/ancestry.rb +82 -0
  57. data/lib/psd/nodes/has_children.rb +13 -0
  58. data/lib/psd/nodes/lock_to_origin.rb +7 -0
  59. data/lib/psd/nodes/parse_layers.rb +18 -0
  60. data/lib/psd/nodes/search.rb +28 -0
  61. data/lib/psd/pascal_string.rb +14 -0
  62. data/lib/psd/path_record.rb +180 -0
  63. data/lib/psd/resource.rb +27 -0
  64. data/lib/psd/resources.rb +47 -0
  65. data/lib/psd/section.rb +26 -0
  66. data/lib/psd/util.rb +17 -0
  67. data/lib/psd/version.rb +3 -0
  68. data/psd.gemspec +30 -0
  69. data/spec/files/example.psd +0 -0
  70. data/spec/files/one_layer.psd +0 -0
  71. data/spec/files/path.psd +0 -0
  72. data/spec/files/simplest.psd +0 -0
  73. data/spec/files/text.psd +0 -0
  74. data/spec/hierarchy_spec.rb +86 -0
  75. data/spec/identity_spec.rb +34 -0
  76. data/spec/parsing_spec.rb +134 -0
  77. data/spec/spec_helper.rb +13 -0
  78. data/spec/text_spec.rb +12 -0
  79. metadata +231 -0
@@ -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
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', cli: '--color' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ machine:
2
+ ruby:
3
+ version: 2.0.0-p195
4
+ test:
5
+ override:
6
+ - rspec
@@ -0,0 +1,7 @@
1
+ require './lib/psd'
2
+
3
+ psd = PSD.new('examples/images/example.psd')
4
+ psd.parse!
5
+
6
+
7
+ psd.export_node psd.tree.children.last.children[1], "asset.psd"
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require './lib/psd'
2
+ require 'pp'
3
+
4
+ psd = PSD.new('examples/images/example.psd')
5
+ psd.parse!
6
+
7
+ pp psd.tree.children_at_path("Version D/Version E/Version F").first.to_hash
@@ -0,0 +1,8 @@
1
+ require './lib/psd'
2
+ require 'pp'
3
+
4
+ file = ARGV[0] || 'examples/images/example.psd'
5
+ psd = PSD.new(file)
6
+ psd.parse!
7
+
8
+ pp psd.tree.to_hash
@@ -0,0 +1,9 @@
1
+ require './lib/psd'
2
+ require 'pp'
3
+
4
+ file = ARGV[0] || 'examples/images/example.psd'
5
+ psd = PSD.new(file)
6
+ psd.parse!
7
+
8
+ puts '-> Layer Info Keys'
9
+ pp PSD.keys.uniq
@@ -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