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