ruby-psd 0.0.1 → 1.0.0

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 (3) hide show
  1. data/lib/ruby-psd.rb +249 -0
  2. metadata +8 -7
  3. data/lib/main.rb +0 -0
@@ -0,0 +1,249 @@
1
+ class RubyPSD
2
+
3
+ # Official documents of PSD File Format is here.
4
+ # http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_20023
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ @width = 0
9
+ @height = 0
10
+ @layers = []
11
+ end
12
+ attr_accessor :width, :height, :layers
13
+
14
+ def get_file_header
15
+ [
16
+ [0x38, 0x42, 0x50, 0x53], # Signature[4] : 8BPS
17
+ [0x00, 0x01], #Version[2] : always it's must be 1
18
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x00], #Reserved[6]
19
+ [0x00, 0x04], # The number of channels in the image, Usually it's RGBA so it will be 4
20
+ num2bytes(@height, 4), # Height[4]
21
+ num2bytes(@width, 4), # Width[4]
22
+ [0x00, 0x08], #Depth[2]: the number of bits per channel. Supported values are 1, 8, 16 and 32. opted 8bit as default.
23
+ [0x00, 0x03] #ColorMode: opted RGB Mode(3) as default.
24
+ ].flatten.pack("C*")
25
+ end
26
+
27
+ def get_color_mode_data
28
+ # I'm not sure about this specs so I makes it as only 4 byte length of zero
29
+ [0x00, 0x00, 0x00, 0x00].pack("C*")
30
+ end
31
+
32
+ def get_image_resources
33
+ # I'm not sure about this specs so I makes it as only 4 byte length of zero
34
+ [0x00, 0x00, 0x00, 0x00].pack("C*")
35
+ end
36
+
37
+ def get_layer_and_mask_information
38
+
39
+ layer_info = get_layer_info
40
+ global_layer_info = get_global_layer_info
41
+ additional_layer_info = get_additional_layer_info
42
+ size_of_layer_and_mask_information = layer_info.size + global_layer_info.size + additional_layer_info.size
43
+
44
+ [
45
+ num2bytes(size_of_layer_and_mask_information, 4),
46
+ layer_info,
47
+ global_layer_info,
48
+ additional_layer_info
49
+ ].flatten.pack("C*")
50
+
51
+ end
52
+
53
+ def get_layer_info
54
+
55
+ _LAYER_COUNT_LENGTH = 2
56
+ layer_counts = get_layer_counts
57
+ layer_records = get_layer_records
58
+ channel_image_data = get_channel_image_data(layer_counts)
59
+
60
+ size_of_layer_info = _LAYER_COUNT_LENGTH + layer_records.size + channel_image_data.size
61
+
62
+ [
63
+ num2bytes(size_of_layer_info, 4),
64
+ num2bytes(layer_counts, 2),
65
+ layer_records,
66
+ channel_image_data
67
+ ].flatten
68
+
69
+ end
70
+
71
+ def get_layer_counts
72
+ # 1 is the count of default art layer named "background"
73
+ 1 + (get_key_nums @layers) * 2
74
+ end
75
+
76
+ def get_key_nums(layers)
77
+ ret = layers.size
78
+ layers.each do |v|
79
+ if v.class == Array
80
+ ret += get_key_nums(v) - 1
81
+ end
82
+ end
83
+ ret
84
+ end
85
+
86
+ def get_layer_records
87
+ art_layer_record = get_art_layer_record
88
+ layers_array = convert_layers_to_array(@layers)
89
+ group_layer_records = layers_array.map do |name|
90
+ get_group_layer_record name
91
+ end
92
+ [art_layer_record, group_layer_records].flatten
93
+ end
94
+
95
+ def get_art_layer_record
96
+ get_layer_record "background", []
97
+ end
98
+
99
+ def convert_layers_to_array(arr)
100
+ def pick_up_from_array(ar, prev = nil)
101
+ ret = []
102
+ ar.each_with_index do |a, i|
103
+ if a.class == Array
104
+ ret += pick_up_from_array(a)
105
+ if prev != nil and ar[i+1].class != Array
106
+ ret << "</Layer group>"
107
+ prev = nil
108
+ end
109
+ else
110
+ if prev != nil
111
+ ret << "</Layer group>"
112
+ end
113
+ ret << a
114
+ prev = a
115
+ end
116
+ end
117
+ ret << "</Layer group>" if prev != nil
118
+ ret
119
+ end
120
+ (pick_up_from_array arr).flatten.reverse
121
+ end
122
+
123
+ def get_group_layer_record(name)
124
+ if name == "</Layer group>"
125
+ additional_data = [
126
+ [0x38, 0x42, 0x49, 0x4D], # Blend mode signature: '8BIM'
127
+ [0x6c, 0x73, 0x63, 0x74], # Section divider setting 'lscr'
128
+ [0x00, 0x00, 0x00, 0x04], # Length of this additional record
129
+ [0x00, 0x00, 0x00, 0x03] # Type. This is 3 = bounding section divider.
130
+ ]
131
+ else
132
+ additional_data = [
133
+ [0x38, 0x42, 0x49, 0x4D], # Blend mode signature: '8BIM'
134
+ [0x6c, 0x73, 0x63, 0x74], # Section divider setting 'lscr'
135
+ [0x00, 0x00, 0x00, 0x0c], # Length of this additional record. It might be 12.
136
+ [0x00, 0x00, 0x00, 0x01], # Type. This is 1 = bounding section divider.
137
+ # Following is only present if length = 12
138
+ [0x38, 0x42, 0x49, 0x4D], # Blend mode signature: '8BIM'
139
+ [0x70, 0x61, 0x73, 0x73], # Blend mode key: 'pass'
140
+ ]
141
+ end
142
+ get_layer_record name, additional_data.flatten
143
+ end
144
+
145
+ def get_layer_record (name, additional_data)
146
+
147
+ pascal_name = get_pascal_name(name, 4)
148
+ size_of_additional_data = 8 + pascal_name.size + additional_data.size
149
+ [
150
+ [0x00] * 16, #Rectangle containing the contents of the layer
151
+ [0x00, 0x04], #Number of channels in the layer ( Usually it will be 4 as RGBA )
152
+ # Channel information. Six bytes per channel, consisting of: 2 bytes for Channel ID
153
+ # 4 bytes for length of corresponding channel data
154
+ [0xFF, 0xFF] + [0x00, 0x00, 0x00, 0x02], #-1 = transparency
155
+ [0x00, 0x00] + [0x00, 0x00, 0x00, 0x02], #0 = red
156
+ [0x00, 0x01] + [0x00, 0x00, 0x00, 0x02], #1 = green
157
+ [0x00, 0x02] + [0x00, 0x00, 0x00, 0x02], #2 = blue
158
+ [0x38, 0x42, 0x49, 0x4D], # Blend mode signature: '8BIM'
159
+ [0x6E, 0x6F, 0x72, 0x6D], # Blend mode key: 'norm'
160
+ [0xFF], #Opacity. 0 = transparent ... 255 = opaque
161
+ [0x00], #Clipping: 0 = base, 1 = non-base
162
+ [0x08], #Flags: [00001000]
163
+ # bit 0 = transparency protected;
164
+ # bit 1 = visible;
165
+ # bit 2 = obsolete;
166
+ # bit 3 = 1 for Photoshop 5.0 and later, tells if bit 4 has useful information;
167
+ # bit 4 = pixel data irrelevant to appearance of document
168
+ [0x00], # Just Filler (zero)
169
+ num2bytes(size_of_additional_data, 4),
170
+ [0x00] * 4, # Layer mask data. It won't be.
171
+ [0x00] * 4, # Layer blending ranges. It won't be.
172
+ pascal_name,
173
+ additional_data
174
+ ].flatten
175
+
176
+ end
177
+
178
+ def get_pascal_name(name, padding)
179
+ arr = name.unpack("C*")
180
+ arr = [arr.size] + arr
181
+ if ((arr.size % padding) > 0)
182
+ arr += ([0] * (padding - (arr.size % padding)))
183
+ end
184
+ arr
185
+ end
186
+
187
+ def get_channel_image_data(layer_count)
188
+ [0x00] * 8 * layer_count
189
+ end
190
+
191
+ def get_global_layer_info
192
+ [0x00] * 4
193
+ end
194
+
195
+ def get_additional_layer_info
196
+ []
197
+ end
198
+
199
+ def get_image_data
200
+ image_data = generate_image_data
201
+ [
202
+ [0x00, 0x01],
203
+ image_data # Compression method: 1 = RLE compressed the image data starts with the byte
204
+ ].flatten.pack("C*")
205
+ end
206
+
207
+ def generate_image_data
208
+
209
+ packet = get_packet_bytes(@width)
210
+ channels = 4
211
+ [
212
+ [0x00, 0x00] * @height * channels,
213
+ packet * @height * channels
214
+ ]
215
+
216
+ end
217
+
218
+ def get_packet_bytes(width)
219
+ divided = width / 128
220
+ remainder = width % 128
221
+ ret = []
222
+ [divided, remainder]
223
+ divided.times do
224
+ ret << [0x101 - 128, 0xFF]
225
+ end
226
+ ret << [0x101 - remainder, 0xFF]
227
+ ret.flatten
228
+ end
229
+
230
+ def generate
231
+ psd = open @path, "w"
232
+ psd.print get_file_header
233
+ psd.print get_color_mode_data
234
+ psd.print get_image_resources
235
+ psd.print get_layer_and_mask_information
236
+ psd.print get_image_data
237
+ psd.close
238
+ end
239
+
240
+ def output_psd
241
+ end
242
+
243
+ def num2bytes (num, size)
244
+ num.to_s(16).rjust(size*2,"0").unpack("a2"*(size)).map{|a| "0x#{a}".to_i(16)}
245
+ end
246
+
247
+ end
248
+
249
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-psd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,16 +9,17 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-27 00:00:00.000000000 Z
12
+ date: 2013-02-28 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: ! 'This gem is made for generating and accessing psd file from ruby. '
15
- email: shunter1112@gmail.com
14
+ description: Generating psd skeleton file simple and by scriping of Ruby
15
+ email:
16
+ - shunter1112@gmail.com
16
17
  executables: []
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
20
- - lib/main.rb
21
- homepage: http://rubygems.org/gems/ruby-psd
21
+ - lib/ruby-psd.rb
22
+ homepage: http://github.com/shunter1112/ruby-psd
22
23
  licenses: []
23
24
  post_install_message:
24
25
  rdoc_options: []
@@ -41,5 +42,5 @@ rubyforge_project:
41
42
  rubygems_version: 1.8.25
42
43
  signing_key:
43
44
  specification_version: 3
44
- summary: Accessing and generating psd files.
45
+ summary: Generating psd skeleton file simple and by scriping of Ruby
45
46
  test_files: []
File without changes