zpng 0.0.2 → 0.1.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/Gemfile +3 -4
- data/Gemfile.lock +18 -16
- data/README.md +62 -48
- data/README.md.tpl +11 -1
- data/Rakefile +43 -0
- data/VERSION +1 -1
- data/lib/zpng/chunk.rb +114 -17
- data/lib/zpng/cli.rb +30 -5
- data/lib/zpng/color.rb +30 -0
- data/lib/zpng/image.rb +122 -19
- data/lib/zpng/scan_line.rb +138 -74
- data/samples/captcha_4bpp.png +0 -0
- data/spec/create_image_spec.rb +78 -0
- data/spec/crop_spec.rb +93 -0
- data/spec/image_spec.rb +54 -0
- data/spec/modify_spec.rb +2 -9
- data/spec/running_pixel_spec.rb +47 -0
- data/spec/spec_helper.rb +7 -1
- data/zpng.gemspec +17 -15
- metadata +57 -38
data/Gemfile
CHANGED
@@ -8,8 +8,7 @@ gem 'hexdump'
|
|
8
8
|
# Add dependencies to develop your gem here.
|
9
9
|
# Include everything needed to run rake, tests, features, etc.
|
10
10
|
group :development do
|
11
|
-
gem "rspec",
|
12
|
-
gem "bundler", "
|
13
|
-
gem "jeweler", "~> 1.
|
14
|
-
gem "rcov", ">= 0"
|
11
|
+
gem "rspec", ">= 2.8.0"
|
12
|
+
gem "bundler", ">= 1.0.0"
|
13
|
+
gem "jeweler", "~> 1.8.4"
|
15
14
|
end
|
data/Gemfile.lock
CHANGED
@@ -4,29 +4,31 @@ GEM
|
|
4
4
|
colorize (0.5.8)
|
5
5
|
diff-lcs (1.1.3)
|
6
6
|
git (1.2.5)
|
7
|
-
hexdump (0.2.
|
8
|
-
jeweler (1.
|
7
|
+
hexdump (0.2.3)
|
8
|
+
jeweler (1.8.4)
|
9
9
|
bundler (~> 1.0)
|
10
10
|
git (>= 1.2.5)
|
11
11
|
rake
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
rspec-
|
12
|
+
rdoc
|
13
|
+
json (1.7.5)
|
14
|
+
rake (10.0.2)
|
15
|
+
rdoc (3.12)
|
16
|
+
json (~> 1.4)
|
17
|
+
rspec (2.12.0)
|
18
|
+
rspec-core (~> 2.12.0)
|
19
|
+
rspec-expectations (~> 2.12.0)
|
20
|
+
rspec-mocks (~> 2.12.0)
|
21
|
+
rspec-core (2.12.1)
|
22
|
+
rspec-expectations (2.12.0)
|
23
|
+
diff-lcs (~> 1.1.3)
|
24
|
+
rspec-mocks (2.12.0)
|
22
25
|
|
23
26
|
PLATFORMS
|
24
27
|
ruby
|
25
28
|
|
26
29
|
DEPENDENCIES
|
27
|
-
bundler (
|
30
|
+
bundler (>= 1.0.0)
|
28
31
|
colorize
|
29
32
|
hexdump
|
30
|
-
jeweler (~> 1.
|
31
|
-
|
32
|
-
rspec (~> 2.8.0)
|
33
|
+
jeweler (~> 1.8.4)
|
34
|
+
rspec (>= 2.8.0)
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
zpng
|
1
|
+
zpng [](http://secure.travis-ci.org/zed-0xff/zpng) [](https://gemnasium.com/zed-0xff/zpng)
|
2
2
|
======
|
3
3
|
|
4
4
|
|
@@ -15,14 +15,18 @@ Usage
|
|
15
15
|
|
16
16
|
# zpng -h
|
17
17
|
|
18
|
-
Usage: zpng [options]
|
18
|
+
Usage: zpng [options] filename.png
|
19
19
|
-v, --verbose Run verbosely (can be used multiple times)
|
20
20
|
-q, --quiet Silent any warnings (can be used multiple times)
|
21
|
-
-I, --info General image info
|
22
21
|
-C, --chunks Show file chunks (default)
|
22
|
+
-i, --info General image info (default)
|
23
23
|
-A, --ascii Try to display image as ASCII (works best with monochrome images)
|
24
24
|
-S, --scanlines Show scanlines info
|
25
25
|
-P, --palette Show palette
|
26
|
+
-E, --extract-chunk ID extract a single chunk
|
27
|
+
-U, --unpack-imagedata unpack Image Data (IDAT) chunk(s), output to stdout
|
28
|
+
-c, --crop GEOMETRY crop image, {WIDTH}x{HEIGHT}+{X}+{Y},
|
29
|
+
puts results on stdout unless --ascii given
|
26
30
|
|
27
31
|
### Info
|
28
32
|
|
@@ -35,15 +39,15 @@ Usage
|
|
35
39
|
|
36
40
|
# zpng --chunks qr_aux_chunks.png
|
37
41
|
|
38
|
-
[.]
|
39
|
-
[.]
|
40
|
-
[.]
|
41
|
-
[.]
|
42
|
-
[.]
|
43
|
-
[.]
|
44
|
-
[.]
|
45
|
-
[.]
|
46
|
-
[.]
|
42
|
+
[.] <Chunk #00 IHDR size= 13, crc=36a28ef4, width=35, height=35, depth=1, color=0, compression=0, filter=0, interlace=0, idx=0> CRC OK
|
43
|
+
[.] <Chunk #01 gAMA size= 4, crc=0bfc6105 > CRC OK
|
44
|
+
[.] <Chunk #02 sRGB size= 1, crc=aece1ce9 > CRC OK
|
45
|
+
[.] <Chunk #03 cHRM size= 32, crc=9cba513c > CRC OK
|
46
|
+
[.] <Chunk #04 pHYs size= 9, crc=46c96b3e > CRC OK
|
47
|
+
[.] <Chunk #05 IDAT size= 213, crc=5f3f1ff9 > CRC OK
|
48
|
+
[.] <Chunk #06 tEXt size= 37, crc=8d62fd1a > CRC OK
|
49
|
+
[.] <Chunk #07 tEXt size= 37, crc=fc3f45a6 > CRC OK
|
50
|
+
[.] <Chunk #08 IEND size= 0, crc=ae426082 > CRC OK
|
47
51
|
|
48
52
|
### ASCII
|
49
53
|
|
@@ -90,47 +94,47 @@ source image:  do |f|
|
172
|
+
f << img.export
|
173
|
+
end
|
160
174
|
|
161
175
|
License
|
162
176
|
-------
|
data/README.md.tpl
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
zpng
|
1
|
+
zpng [](http://secure.travis-ci.org/zed-0xff/zpng) [](https://gemnasium.com/zed-0xff/zpng)
|
2
2
|
======
|
3
3
|
|
4
4
|
|
@@ -61,6 +61,16 @@ source image:  do |f|
|
72
|
+
f << img.export
|
73
|
+
end
|
64
74
|
|
65
75
|
License
|
66
76
|
-------
|
data/Rakefile
CHANGED
@@ -72,3 +72,46 @@ task :readme do
|
|
72
72
|
Dir.chdir '..'
|
73
73
|
File.open('README.md','w'){ |f| f << result }
|
74
74
|
end
|
75
|
+
|
76
|
+
desc "generate"
|
77
|
+
task :gen do
|
78
|
+
$:.unshift("./lib")
|
79
|
+
require 'zpng'
|
80
|
+
img = ZPNG::Image.new :width => 16, :height => 16, :bpp => 4
|
81
|
+
img.save "out.png"
|
82
|
+
end
|
83
|
+
|
84
|
+
Rake::Task[:console].clear
|
85
|
+
|
86
|
+
# from /usr/local/lib64/ruby/gems/1.9.1/gems/jeweler-1.8.4/lib/jeweler/tasks.rb
|
87
|
+
desc "Start IRB with all runtime dependencies loaded"
|
88
|
+
task :console, [:script] do |t,args|
|
89
|
+
# TODO move to a command
|
90
|
+
dirs = ['ext', 'lib'].select { |dir| File.directory?(dir) }
|
91
|
+
|
92
|
+
original_load_path = $LOAD_PATH
|
93
|
+
|
94
|
+
cmd = if File.exist?('Gemfile')
|
95
|
+
require 'bundler'
|
96
|
+
Bundler.setup(:default)
|
97
|
+
end
|
98
|
+
|
99
|
+
# add the project code directories
|
100
|
+
$LOAD_PATH.unshift(*dirs)
|
101
|
+
|
102
|
+
# clear ARGV so IRB is not confused
|
103
|
+
ARGV.clear
|
104
|
+
|
105
|
+
require 'irb'
|
106
|
+
|
107
|
+
# ZZZ actually added only these 2 lines
|
108
|
+
require 'zpng'
|
109
|
+
include ZPNG
|
110
|
+
|
111
|
+
# set the optional script to run
|
112
|
+
IRB.conf[:SCRIPT] = args.script
|
113
|
+
IRB.start
|
114
|
+
|
115
|
+
# return the $LOAD_PATH to it's original state
|
116
|
+
$LOAD_PATH.reject! { |path| !(original_load_path.include?(path)) }
|
117
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/lib/zpng/chunk.rb
CHANGED
@@ -18,25 +18,42 @@ module ZPNG
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def initialize
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
def initialize x = {}
|
22
|
+
if x.respond_to?(:read)
|
23
|
+
# IO
|
24
|
+
@size, @type = x.read(8).unpack('Na4')
|
25
|
+
@data = x.read(size)
|
26
|
+
@crc = x.read(4).to_s.unpack('N').first
|
27
|
+
elsif x.respond_to?(:[])
|
28
|
+
# Hash
|
29
|
+
%w'size type data crc'.each do |k|
|
30
|
+
instance_variable_set "@#{k}", x[k.to_sym]
|
31
|
+
end
|
32
|
+
if !@type && self.class.superclass == ZPNG::Chunk
|
33
|
+
# guess @type from self class name, e.g. ZPNG::Chunk::IHDR => "IHDR"
|
34
|
+
@type = self.class.to_s.split("::").last
|
35
|
+
end
|
36
|
+
if !@size && @data
|
37
|
+
# guess @size from @data
|
38
|
+
@size = @data.size
|
39
|
+
end
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
43
|
def export
|
28
44
|
@data = self.export_data # virtual
|
45
|
+
@size = @data.size # XXX hmm.. is it always is?
|
29
46
|
@crc = Zlib.crc32(data, Zlib.crc32(type))
|
30
47
|
[@size,@type].pack('Na4') + @data + [@crc].pack('N')
|
31
48
|
end
|
32
49
|
|
33
50
|
def export_data
|
34
51
|
#STDERR.puts "[!] Chunk::#{type} must realize 'export_data' virtual method".yellow if @size != 0
|
35
|
-
@data
|
52
|
+
@data || ''
|
36
53
|
end
|
37
54
|
|
38
55
|
def inspect
|
39
|
-
size = @size ? sprintf("%
|
56
|
+
size = @size ? sprintf("%6d",@size) : sprintf("%6s","???")
|
40
57
|
crc = @crc ? sprintf("%08x",@crc) : sprintf("%8s","???")
|
41
58
|
type = @type.to_s.gsub(/[^0-9a-z]/i){ |x| sprintf("\\x%02X",x.ord) }
|
42
59
|
sprintf "<Chunk #%02d %4s size=%s, crc=%s >", idx.to_i, type, size, crc
|
@@ -54,11 +71,16 @@ module ZPNG
|
|
54
71
|
COLOR_USED = 2
|
55
72
|
ALPHA_USED = 4
|
56
73
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
74
|
+
# put constants in the scope of ZPNG module
|
75
|
+
# to be able to create new images easily with
|
76
|
+
# include ZPNG
|
77
|
+
# img = Image.new :width => 16, :height => 16, :color => COLOR_RGB
|
78
|
+
|
79
|
+
ZPNG::COLOR_GRAYSCALE = 0 # Each pixel is a grayscale sample
|
80
|
+
ZPNG::COLOR_RGB = 2 # Each pixel is an R,G,B triple.
|
81
|
+
ZPNG::COLOR_INDEXED = 3 # Each pixel is a palette index; a PLTE chunk must appear.
|
82
|
+
ZPNG::COLOR_GRAY_ALPHA = 4 # Each pixel is a grayscale sample, followed by an alpha sample.
|
83
|
+
ZPNG::COLOR_RGBA = 6 # Each pixel is an R,G,B triple, followed by an alpha sample.
|
62
84
|
|
63
85
|
SAMPLES_PER_COLOR = {
|
64
86
|
COLOR_GRAYSCALE => 1,
|
@@ -68,11 +90,67 @@ module ZPNG
|
|
68
90
|
COLOR_RGBA => 4
|
69
91
|
}
|
70
92
|
|
93
|
+
# http://www.w3.org/TR/PNG/#table111
|
94
|
+
ALLOWED_DEPTHS = {
|
95
|
+
COLOR_GRAYSCALE => [ 1, 2, 4, 8, 16 ],
|
96
|
+
COLOR_RGB => [ 8, 16 ],
|
97
|
+
COLOR_INDEXED => [ 1, 2, 4, 8 ],
|
98
|
+
COLOR_GRAY_ALPHA => [ 8, 16 ],
|
99
|
+
COLOR_RGBA => [ 8, 16 ],
|
100
|
+
}
|
101
|
+
|
71
102
|
FORMAT = 'NNC5'
|
72
103
|
|
73
|
-
def initialize
|
104
|
+
def initialize x
|
74
105
|
super
|
75
|
-
|
106
|
+
vars = %w'width height depth color compression filter interlace' # order is important
|
107
|
+
if x.respond_to?(:read)
|
108
|
+
# IO
|
109
|
+
elsif x.respond_to?(:[])
|
110
|
+
# Hash
|
111
|
+
vars.each{ |k| instance_variable_set "@#{k}", x[k.to_sym] }
|
112
|
+
|
113
|
+
raise "[!] width not set" unless @width
|
114
|
+
raise "[!] height not set" unless @height
|
115
|
+
|
116
|
+
# allow easier image creation like
|
117
|
+
# img = Image.new :width => 16, :height => 16, :bpp => 4, :color => false
|
118
|
+
# img = Image.new :width => 16, :height => 16, :bpp => 1, :color => true
|
119
|
+
# img = Image.new :width => 16, :height => 16, :bpp => 32
|
120
|
+
if x[:bpp]
|
121
|
+
unless [true,false,nil].include?(@color)
|
122
|
+
raise "[!] :color must be either 'true' or 'false' when :bpp is set"
|
123
|
+
end
|
124
|
+
if @depth
|
125
|
+
raise "[!] don't use :depth when :bpp is set"
|
126
|
+
end
|
127
|
+
@color, @depth = case x[:bpp]
|
128
|
+
when 1,2,4,8; [ @color ? COLOR_INDEXED : COLOR_GRAYSCALE, x[:bpp] ]
|
129
|
+
when 16;
|
130
|
+
raise "[!] I don't know how to make COLOR 16 bpp PNG. do you?" if @color
|
131
|
+
[ COLOR_GRAY_ALPHA, 8 ]
|
132
|
+
when 24; [ COLOR_RGB, 8 ]
|
133
|
+
when 32; [ COLOR_RGBA, 8 ]
|
134
|
+
else
|
135
|
+
raise "[!] unsupported bpp=#{x[:bpp].inspect}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
@color ||= COLOR_RGBA
|
140
|
+
@depth ||= 8
|
141
|
+
@compression ||= 0
|
142
|
+
@filter ||= 0
|
143
|
+
@interlace ||= 0
|
144
|
+
|
145
|
+
unless ALLOWED_DEPTHS[@color].include?(@depth)
|
146
|
+
raise "[!] invalid color mode (#{@color.inspect}) / bit depth (#{@depth.inspect}) combination"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
if data
|
150
|
+
data.unpack(FORMAT).each_with_index do |value,idx|
|
151
|
+
instance_variable_set "@#{vars[idx]}", value
|
152
|
+
end
|
153
|
+
end
|
76
154
|
end
|
77
155
|
|
78
156
|
def export_data
|
@@ -109,13 +187,20 @@ module ZPNG
|
|
109
187
|
end
|
110
188
|
|
111
189
|
class PLTE < Chunk
|
190
|
+
attr_accessor :max_colors
|
191
|
+
|
112
192
|
def [] idx
|
113
193
|
rgb = @data[idx*3,3]
|
114
|
-
rgb && ZPNG::Color.new(*rgb.
|
194
|
+
rgb && ZPNG::Color.new(*rgb.unpack('C3'))
|
195
|
+
end
|
196
|
+
|
197
|
+
def []= idx, color
|
198
|
+
@data ||= ''
|
199
|
+
@data[idx*3,3] = [color.r, color.g, color.b].pack('C3')
|
115
200
|
end
|
116
201
|
|
117
202
|
def ncolors
|
118
|
-
@size/3
|
203
|
+
@data.to_s.size/3
|
119
204
|
end
|
120
205
|
|
121
206
|
def index color
|
@@ -125,11 +210,23 @@ module ZPNG
|
|
125
210
|
end
|
126
211
|
nil
|
127
212
|
end
|
128
|
-
end
|
129
213
|
|
130
|
-
|
214
|
+
def add color
|
215
|
+
raise "palette full, cannot add color #{ncolors}" if ncolors >= max_colors
|
216
|
+
idx = ncolors
|
217
|
+
self[idx] = color
|
218
|
+
idx
|
219
|
+
end
|
220
|
+
|
221
|
+
def find_or_add color
|
222
|
+
index(color) || add(color)
|
223
|
+
end
|
224
|
+
alias :<< :find_or_add
|
131
225
|
end
|
132
226
|
|
227
|
+
class IDAT < Chunk; end
|
228
|
+
class IEND < Chunk; end
|
229
|
+
|
133
230
|
class ZTXT < Chunk
|
134
231
|
attr_accessor :keyword, :comp_method, :text
|
135
232
|
def initialize *args
|