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.
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