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,45 @@
1
+ class PSD
2
+ # Represents the mask for a single layer
3
+ class Mask < BinData::Record
4
+ endian :big
5
+
6
+ int32 :mask_size
7
+
8
+ int32 :top, onlyif: :has_data?
9
+ int32 :left, onlyif: :has_data?
10
+ int32 :bottom, onlyif: :has_data?
11
+ int32 :right, onlyif: :has_data?
12
+ int8 :default_color, onlyif: :has_data?
13
+ bit8 :flags, onlyif: :has_data?
14
+
15
+ skip length: 2, onlyif: lambda { mask_size == 20 }
16
+ skip length: 18, onlyif: lambda { mask_size > 0 && mask_size != 20}
17
+
18
+ # Is there a mask defined?
19
+ def has_data?
20
+ mask_size > 0
21
+ end
22
+
23
+ # Width of the mask
24
+ def width
25
+ right - left
26
+ end
27
+
28
+ # Height of the mask
29
+ def height
30
+ bottom - top
31
+ end
32
+
33
+ def relative
34
+ flags & 0x01
35
+ end
36
+
37
+ def disabled
38
+ (flags & (0x01 << 1)) > 0
39
+ end
40
+
41
+ def invert
42
+ (flags & (0x01 << 2)) > 0
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'nodes/ancestry'
2
+ require_relative 'nodes/search'
3
+
4
+ # Internal structure to help us build trees of a Photoshop documents.
5
+ # A lot of method names borrowed from the Ruby ancestry gem.
6
+ class PSD
7
+ class Node
8
+ include Ancestry
9
+ include Search
10
+
11
+ # Default properties that all nodes contain
12
+ PROPERTIES = [:name, :left, :right, :top, :bottom, :height, :width]
13
+
14
+ attr_accessor :parent, :children
15
+
16
+ def initialize(layers=[])
17
+ @children = []
18
+ layers.each do |layer|
19
+ layer.parent = self
20
+ @children << layer
21
+ end
22
+ end
23
+
24
+ def hidden?
25
+ !@layer.visible?
26
+ end
27
+
28
+ def visible?
29
+ @layer.visible?
30
+ end
31
+
32
+ def to_hash
33
+ hash = {
34
+ type: nil,
35
+ visible: visible?,
36
+ opacity: @layer.opacity / 255.0,
37
+ blending_mode: @layer.blending_mode
38
+ }
39
+
40
+ PROPERTIES.each do |p|
41
+ hash[p] = self.send(p)
42
+ end
43
+
44
+ hash
45
+ end
46
+
47
+ def document_dimensions
48
+ @parent.document_dimensions
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ class PSD
2
+ module NodeExporting #:nodoc:
3
+ def export_node(node, path)
4
+ hide_all_nodes
5
+ node.show!
6
+ node.lock_to_origin
7
+
8
+ width_difference_factor = @header.cols.to_f / node.width
9
+ height_difference_factor = @header.rows.to_f / node.height
10
+ @header.cols, @header.rows = node.width, node.height
11
+
12
+ node.scale_path_components(width_difference_factor, height_difference_factor)
13
+ export path
14
+ end
15
+
16
+ def hide_all_nodes
17
+ tree.descendants.map(&:hide!)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ require_relative 'node'
2
+
3
+ class PSD::Node
4
+ # Represents a group, or folder, in the PSD document. It can have
5
+ # zero or more children nodes.
6
+ class Group < PSD::Node
7
+ include PSD::HasChildren
8
+ include PSD::Node::ParseLayers
9
+ include PSD::Node::LockToOrigin
10
+
11
+ attr_reader :name, :top, :left, :bottom, :right
12
+
13
+ # Parses the descendant tree structure and figures out the bounds
14
+ # of the layers within this folder.
15
+ def initialize(folder)
16
+ @name = folder[:name]
17
+ @layer = folder[:layer]
18
+ parse_layers(folder[:layers])
19
+ get_dimensions
20
+ end
21
+
22
+ # Calculated height of this folder.
23
+ def rows
24
+ @right - @left
25
+ end
26
+ alias :height :rows
27
+
28
+ # Calculated width of this folder.
29
+ def cols
30
+ @bottom - @top
31
+ end
32
+ alias :width :cols
33
+
34
+ # Attempt to translate this folder and all of the descendants.
35
+ def translate(x=0, y=0)
36
+ @children.each{ |c| c.translate(x,y) }
37
+ end
38
+
39
+ # Attempt to hide all children of this layer.
40
+ def hide!
41
+ @children.each{ |c| c.hide! }
42
+ end
43
+
44
+ # Attempt to show all children of this layer.
45
+ def show!
46
+ @children.each{ |c| c.show! }
47
+ end
48
+
49
+ # Export this layer and it's children to a hash recursively.
50
+ def to_hash
51
+ super.merge({
52
+ type: :group,
53
+ visible: visible?,
54
+ children: children.map(&:to_hash)
55
+ })
56
+ end
57
+
58
+ private
59
+
60
+ def get_dimensions
61
+ @left = @children.map(&:left).min || 0
62
+ @top = @children.map(&:top).min || 0
63
+ @bottom = @children.map(&:bottom).max || 0
64
+ @right = @children.map(&:right).max || 0
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,71 @@
1
+ require_relative 'node'
2
+
3
+ class PSD::Node
4
+ class Layer < PSD::Node
5
+ include PSD::Node::LockToOrigin
6
+
7
+ attr_reader :layer
8
+
9
+ # Stores a reference to the PSD::Layer
10
+ def initialize(layer)
11
+ super([])
12
+
13
+ @layer = layer
14
+ layer.node = self
15
+ end
16
+
17
+ # Delegates some methods to the PSD::Layer
18
+ (PROPERTIES + [:text, :ref_x, :ref_y]).each do |meth|
19
+ define_method meth do
20
+ @layer.send(meth)
21
+ end
22
+
23
+ define_method "#{meth}=" do |val|
24
+ @layer.send("#{meth}=", val)
25
+ end
26
+ end
27
+
28
+ # If the method is missing, we blindly send it to the layer.
29
+ # The layer handles the case in which the method doesn't exist.
30
+ def method_missing(method, *args, &block)
31
+ layer.send(method, *args, &block)
32
+ end
33
+
34
+ # Attempt to translate the layer.
35
+ def translate(x=0, y=0)
36
+ @layer.translate x, y
37
+ end
38
+
39
+ # Attempt to scale the path components of the layer.
40
+ def scale_path_components(xr, yr)
41
+ @layer.scale_path_components(xr, yr)
42
+ end
43
+
44
+ # Tries to hide the layer by moving it way off canvas.
45
+ def hide!
46
+ # TODO actually mess with the blend modes instead of
47
+ # just putting things way off canvas
48
+ return if @hidden_by_kelly
49
+ translate(100000, 10000)
50
+ @hidden_by_kelly = true
51
+ end
52
+
53
+ # Tries to re-show the canvas by moving it back to it's original position.
54
+ def show!
55
+ if @hidden_by_kelly
56
+ translate(-100000, -10000)
57
+ @hidden_by_kelly = false
58
+ end
59
+ end
60
+
61
+ # Exports this layer to a Hash.
62
+ def to_hash
63
+ super.merge({
64
+ type: :layer,
65
+ text: @layer.text,
66
+ ref_x: @layer.reference_point.x,
67
+ ref_y: @layer.reference_point.y
68
+ })
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'node'
2
+
3
+ class PSD::Node
4
+ # Represents the root node of a Photoshop document
5
+ class Root < PSD::Node
6
+ include PSD::HasChildren
7
+ include PSD::Node::ParseLayers
8
+
9
+ attr_reader :children
10
+
11
+ # Stores a reference to the parsed PSD and builds the
12
+ # tree hierarchy.
13
+ def initialize(psd)
14
+ @psd = psd
15
+ build_hierarchy
16
+ end
17
+
18
+ # Recursively exports the hierarchy to a Hash
19
+ def to_hash
20
+ {
21
+ children: children.map(&:to_hash),
22
+ document: {
23
+ width: document_width,
24
+ height: document_height
25
+ }
26
+ }
27
+ end
28
+
29
+ # Returns the width and height of the entire PSD document.
30
+ def document_dimensions
31
+ [@psd.header.width, @psd.header.height]
32
+ end
33
+
34
+ # The width of the full PSD document as defined in the header.
35
+ def document_width
36
+ @psd.header.width.to_i
37
+ end
38
+
39
+ # The height of the full PSD document as defined in the header.
40
+ def document_height
41
+ @psd.header.height.to_i
42
+ end
43
+
44
+ # The root node has no name since it's not an actual layer or group.
45
+ def name
46
+ nil
47
+ end
48
+
49
+ # The depth of the root node is always 0.
50
+ def depth
51
+ 0
52
+ end
53
+
54
+ private
55
+
56
+ def build_hierarchy
57
+ @children = []
58
+ result = { layers: [] }
59
+ parseStack = []
60
+
61
+ # First we build the hierarchy
62
+ @psd.layers.each do |layer|
63
+ if layer.folder?
64
+ parseStack << result
65
+ result = { name: layer.name, layer: layer, layers: [] }
66
+ elsif layer.folder_end?
67
+ temp = result
68
+ result = parseStack.pop
69
+ result[:layers] << temp
70
+ else
71
+ result[:layers] << layer
72
+ end
73
+ end
74
+
75
+ parse_layers(result[:layers])
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,82 @@
1
+ class PSD
2
+ class Node
3
+ # Collection of methods to help in traversing the PSD tree structure.
4
+ module Ancestry
5
+ # Returns the root node
6
+ def root
7
+ return self if is_root?
8
+ return parent.root
9
+ end
10
+
11
+ # Is this node the root node?
12
+ def root?
13
+ self.is_a?(PSD::Node::Root)
14
+ end
15
+ alias :is_root? :root?
16
+
17
+ # Returns all ancestors in the path of this node. This
18
+ # does NOT return the root node.
19
+ def ancestors
20
+ return [] if parent.nil? || parent.is_root?
21
+ return parent.ancestors + [parent]
22
+ end
23
+
24
+ # Does this node have any children nodes?
25
+ def has_children?
26
+ children.length > 0
27
+ end
28
+
29
+ # Inverse of has_children?
30
+ def is_childless?
31
+ !has_children?
32
+ end
33
+
34
+ # Returns all sibling nodes including the current node. Can also
35
+ # be thought of as all children of the parent of this node.
36
+ def siblings
37
+ return [] if parent.nil?
38
+ parent.children
39
+ end
40
+
41
+ # Does this node have any siblings?
42
+ def has_siblings?
43
+ siblings.length > 1
44
+ end
45
+
46
+ # Is this node the only descendant of its parent?
47
+ def is_only_child?
48
+ siblings.length == 1
49
+ end
50
+
51
+ # Recursively get all descendant nodes, not including this node.
52
+ def descendants
53
+ children + children.map(&:children).flatten
54
+ end
55
+
56
+ # Same as descendants, except it includes this node.
57
+ def subtree
58
+ [self] + descendants
59
+ end
60
+
61
+ # Depth from the root node. Root depth is 0.
62
+ def depth
63
+ return ancestors.length + 1
64
+ end
65
+
66
+ def method_missing(method, *args, &block)
67
+ test = /^(.+)_(layers|groups)$/.match(method)
68
+ if test
69
+ m = self.respond_to?(test[1]) ? test[1] : "#{test[1]}s"
70
+ self.send(m).select &method("#{test[2]}_only")
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def layers_only(d); d.is_a?(PSD::Node::Layer); end
79
+ def groups_only(d); d.is_a?(PSD::Node::Group); end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,13 @@
1
+ class PSD
2
+ module HasChildren
3
+ # Returns all group/folder children of this node.
4
+ def groups
5
+ @children.select{ |c| c.is_a?(PSD::Group) }
6
+ end
7
+
8
+ # Returns all layer children of this node.
9
+ def layers
10
+ @children.select{ |c| c.is_a?(PSD::Layer) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class PSD::Node
2
+ module LockToOrigin #:nodoc:
3
+ def lock_to_origin
4
+ translate(-left - 1, -top - 1)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ class PSD::Node
2
+ module ParseLayers
3
+ # Organizes the flat layer structure into tree nodes.
4
+ def parse_layers(layers)
5
+ @children = []
6
+ layers.each do |layer|
7
+ if layer.is_a?(Hash)
8
+ node = PSD::Node::Group.new(layer)
9
+ elsif layer.is_a?(PSD::Layer)
10
+ node = PSD::Node::Layer.new(layer)
11
+ end
12
+
13
+ node.parent = self
14
+ @children << node
15
+ end
16
+ end
17
+ end
18
+ end