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.
- data/lib/ruby-psd.rb +249 -0
- metadata +8 -7
- data/lib/main.rb +0 -0
data/lib/ruby-psd.rb
ADDED
|
@@ -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
|
|
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-
|
|
12
|
+
date: 2013-02-28 00:00:00.000000000 Z
|
|
13
13
|
dependencies: []
|
|
14
|
-
description:
|
|
15
|
-
email:
|
|
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/
|
|
21
|
-
homepage: http://
|
|
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:
|
|
45
|
+
summary: Generating psd skeleton file simple and by scriping of Ruby
|
|
45
46
|
test_files: []
|
data/lib/main.rb
DELETED
|
File without changes
|