aslakhellesoy-png 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +18 -0
- data/Manifest.txt +13 -0
- data/README.txt +65 -0
- data/Rakefile +19 -0
- data/example/lines.rb +20 -0
- data/example/profile.rb +16 -0
- data/example/profile_lines.rb +37 -0
- data/lib/png.rb +584 -0
- data/lib/png/default_font.png +0 -0
- data/lib/png/font.rb +73 -0
- data/lib/png/pie.rb +47 -0
- data/lib/png/reader.rb +139 -0
- data/test/test_png.rb +529 -0
- data/test/test_png_font.rb +77 -0
- data/test/test_png_reader.rb +87 -0
- metadata +94 -0
data/History.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
*** 1.1.0 / 2007-03-26
|
2
|
+
|
3
|
+
+ 4 major enhancements:
|
4
|
+
+ Fixed and incorporated Dominik Barathon's optimizations.
|
5
|
+
+ Wrote inline methods for png_crc and png_join. Now about 15x faster overall.
|
6
|
+
+ Basic PNG loading.
|
7
|
+
+ Reoriented x/y origin to bottom left. This will break things!
|
8
|
+
+ 3 minor enhancements:
|
9
|
+
+ Awesome ascii art patches from Tom Werner: Canvas#inpsect, Canvas#to_s, Color#to_ascii.
|
10
|
+
+ Switched to Hoe.
|
11
|
+
+ PNG.pie_chart from png/pie.
|
12
|
+
+ 1 bug fix:
|
13
|
+
+ Fixed bug in PNG::Canvas#each.
|
14
|
+
|
15
|
+
*** 1.0.0 / 2006-09-31
|
16
|
+
|
17
|
+
+ Birthday!
|
18
|
+
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= PNG
|
2
|
+
|
3
|
+
* http://seattlerb.rubyforge.org/
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
PNG is an almost-pure-ruby PNG library. It lets you write a PNG
|
8
|
+
without any C libraries.
|
9
|
+
|
10
|
+
== FEATURES
|
11
|
+
|
12
|
+
* Very simple interface.
|
13
|
+
* Outputs simple PNG files with ease.
|
14
|
+
* Basic PNG reader as well (someday it might do compositing and the like!).
|
15
|
+
* Almost pure ruby, does require a compiler.
|
16
|
+
|
17
|
+
== SYNOPSYS
|
18
|
+
|
19
|
+
require 'png'
|
20
|
+
|
21
|
+
canvas = PNG::Canvas.new 200, 200
|
22
|
+
|
23
|
+
# Set a point to a color
|
24
|
+
canvas[100, 100] = PNG::Color::Black
|
25
|
+
|
26
|
+
# draw an anti-aliased line
|
27
|
+
canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
28
|
+
|
29
|
+
png = PNG.new canvas
|
30
|
+
png.save 'blah.png'
|
31
|
+
|
32
|
+
== REQUIREMENTS
|
33
|
+
|
34
|
+
+ C compiler
|
35
|
+
+ RubyInline
|
36
|
+
+ Hoe
|
37
|
+
|
38
|
+
== INSTALL
|
39
|
+
|
40
|
+
+ sudo gem install -y png
|
41
|
+
|
42
|
+
== LICENSE
|
43
|
+
|
44
|
+
(The MIT License)
|
45
|
+
|
46
|
+
Copyright (c) 2006-2007 Ryan Davis, Eric Hodel, Zen Spider Software
|
47
|
+
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
49
|
+
a copy of this software and associated documentation files (the
|
50
|
+
"Software"), to deal in the Software without restriction, including
|
51
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
52
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
53
|
+
permit persons to whom the Software is furnished to do so, subject to
|
54
|
+
the following conditions:
|
55
|
+
|
56
|
+
The above copyright notice and this permission notice shall be
|
57
|
+
included in all copies or substantial portions of the Software.
|
58
|
+
|
59
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
60
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
61
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
62
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
63
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
64
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
65
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$: << "../../RubyInline/dev/lib"
|
2
|
+
|
3
|
+
require 'hoe'
|
4
|
+
require './lib/png.rb'
|
5
|
+
|
6
|
+
Hoe.add_include_dirs("../../RubyInline/dev/lib",
|
7
|
+
"lib")
|
8
|
+
|
9
|
+
Hoe.new 'png', PNG::VERSION do |png|
|
10
|
+
png.rubyforge_name = 'seattlerb'
|
11
|
+
|
12
|
+
png.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
|
13
|
+
png.developer('Eric Hodel', 'drbrain@segment7.net')
|
14
|
+
|
15
|
+
png.clean_globs << File.expand_path("~/.ruby_inline")
|
16
|
+
png.extra_deps << ['RubyInline', '>= 3.5.0']
|
17
|
+
end
|
18
|
+
|
19
|
+
# vim: syntax=Ruby
|
data/example/lines.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
require 'png'
|
4
|
+
require 'png/font'
|
5
|
+
|
6
|
+
canvas = PNG::Canvas.new 201, 201, PNG::Color::White
|
7
|
+
|
8
|
+
canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
9
|
+
canvas.line 50, 50, 50, 100, PNG::Color::Blue
|
10
|
+
canvas.line 100, 50, 150, 100, PNG::Color::Blue
|
11
|
+
canvas.line 100, 50, 125, 100, PNG::Color::Green
|
12
|
+
canvas.line 100, 50, 200, 75, PNG::Color::Green
|
13
|
+
canvas.line 0, 200, 200, 0, PNG::Color::Black
|
14
|
+
canvas.line 0, 200, 150, 0, PNG::Color::Red
|
15
|
+
|
16
|
+
canvas.annotate 'Hello World', 10, 10
|
17
|
+
|
18
|
+
png = PNG.new canvas
|
19
|
+
png.save 'blah.png'
|
20
|
+
`open blah.png`
|
data/example/profile.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'png'
|
2
|
+
|
3
|
+
class PNGProfileLine
|
4
|
+
|
5
|
+
COLORS = [
|
6
|
+
PNG::Color::Red,
|
7
|
+
PNG::Color::Orange,
|
8
|
+
PNG::Color::Yellow,
|
9
|
+
PNG::Color::Green,
|
10
|
+
PNG::Color::Blue,
|
11
|
+
PNG::Color::Purple,
|
12
|
+
]
|
13
|
+
|
14
|
+
def draw
|
15
|
+
line = 0
|
16
|
+
canvas = PNG::Canvas.new 100, 100
|
17
|
+
|
18
|
+
0.step 99, 10 do |x|
|
19
|
+
canvas.line x, 0, 99 - x, 99, COLORS[line % 6]
|
20
|
+
line += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
0.step 99, 10 do |y|
|
24
|
+
canvas.line 0, y, 99, y, COLORS[line % 6]
|
25
|
+
line += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
canvas
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
ppl = PNGProfileLine.new
|
34
|
+
|
35
|
+
5.times do ppl.draw end
|
36
|
+
#PNG.new(ppl.draw).save 'x.png'
|
37
|
+
|
data/lib/png.rb
ADDED
@@ -0,0 +1,584 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'zlib'
|
3
|
+
require 'inline'
|
4
|
+
|
5
|
+
class String # :nodoc: # ZenTest SKIP
|
6
|
+
inline do |builder|
|
7
|
+
raise CompilationError if defined?(JRUBY_VERSION)
|
8
|
+
if RUBY_VERSION < "1.8.6" then
|
9
|
+
builder.prefix <<-EOM
|
10
|
+
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
11
|
+
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
12
|
+
EOM
|
13
|
+
end
|
14
|
+
|
15
|
+
builder.c <<-EOM
|
16
|
+
unsigned long png_crc() {
|
17
|
+
static unsigned long crc[256];
|
18
|
+
static char crc_table_computed = 0;
|
19
|
+
|
20
|
+
if (! crc_table_computed) {
|
21
|
+
unsigned long c;
|
22
|
+
int n, k;
|
23
|
+
|
24
|
+
for (n = 0; n < 256; n++) {
|
25
|
+
c = (unsigned long) n;
|
26
|
+
for (k = 0; k < 8; k++) {
|
27
|
+
c = (c & 1) ? 0xedb88320L ^ (c >> 1) : c >> 1;
|
28
|
+
}
|
29
|
+
crc[n] = c;
|
30
|
+
}
|
31
|
+
crc_table_computed = 1;
|
32
|
+
}
|
33
|
+
|
34
|
+
unsigned long c = 0xffffffff;
|
35
|
+
unsigned len = RSTRING_LEN(self);
|
36
|
+
char * s = StringValuePtr(self);
|
37
|
+
unsigned i;
|
38
|
+
|
39
|
+
for (i = 0; i < len; i++) {
|
40
|
+
c = crc[(c ^ s[i]) & 0xff] ^ (c >> 8);
|
41
|
+
}
|
42
|
+
|
43
|
+
return c ^ 0xffffffff;
|
44
|
+
}
|
45
|
+
EOM
|
46
|
+
end
|
47
|
+
rescue CompilationError
|
48
|
+
unless defined? @@crc then
|
49
|
+
@@crc = Array.new(256)
|
50
|
+
256.times do |n|
|
51
|
+
c = n
|
52
|
+
8.times do
|
53
|
+
c = (c & 1 == 1) ? 0xedb88320 ^ (c >> 1) : c >> 1
|
54
|
+
end
|
55
|
+
@@crc[n] = c
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Calculates a CRC using the algorithm in the PNG specification.
|
61
|
+
|
62
|
+
def png_crc()
|
63
|
+
c = 0xffffffff
|
64
|
+
each_byte do |b|
|
65
|
+
c = @@crc[(c^b) & 0xff] ^ (c >> 8)
|
66
|
+
end
|
67
|
+
return c ^ 0xffffffff
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# An almost-pure-ruby Portable Network Graphics (PNG) writer.
|
73
|
+
#
|
74
|
+
# http://www.libpng.org/pub/png/spec/1.2/
|
75
|
+
#
|
76
|
+
# PNG supports:
|
77
|
+
# + 8 bit truecolor PNGs
|
78
|
+
#
|
79
|
+
# PNG does not support:
|
80
|
+
# + any other color depth
|
81
|
+
# + extra data chunks
|
82
|
+
# + filters
|
83
|
+
#
|
84
|
+
# = Example
|
85
|
+
#
|
86
|
+
# require 'png'
|
87
|
+
#
|
88
|
+
# canvas = PNG::Canvas.new 200, 200
|
89
|
+
# canvas[100, 100] = PNG::Color::Black
|
90
|
+
# canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
91
|
+
# png = PNG.new canvas
|
92
|
+
# png.save 'blah.png'
|
93
|
+
#
|
94
|
+
# = TODO:
|
95
|
+
#
|
96
|
+
# + Get everything orinted entirely on [x,y,h,w] with x,y origin being
|
97
|
+
# bottom left.
|
98
|
+
|
99
|
+
class PNG
|
100
|
+
VERSION = '1.2.0'
|
101
|
+
SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
|
102
|
+
|
103
|
+
# Color Types:
|
104
|
+
GRAY = 0 # DEPTH = 1,2,4,8,16
|
105
|
+
RGB = 2 # DEPTH = 8,16
|
106
|
+
INDEXED = 3 # DEPTH = 1,2,4,8
|
107
|
+
GRAYA = 4 # DEPTH = 8,16
|
108
|
+
RGBA = 6 # DEPTH = 8,16
|
109
|
+
|
110
|
+
# Filter Types:
|
111
|
+
NONE = 0
|
112
|
+
SUB = 1
|
113
|
+
UP = 2
|
114
|
+
AVG = 3
|
115
|
+
PAETH = 4
|
116
|
+
|
117
|
+
begin
|
118
|
+
inline do |builder|
|
119
|
+
raise CompilationError if defined?(JRUBY_VERSION)
|
120
|
+
if RUBY_VERSION < "1.8.6" then
|
121
|
+
builder.prefix <<-EOM
|
122
|
+
#define RARRAY_PTR(s) (RARRAY(s)->ptr)
|
123
|
+
#define RARRAY_LEN(s) (RARRAY(s)->len)
|
124
|
+
EOM
|
125
|
+
end
|
126
|
+
|
127
|
+
builder.c <<-EOM
|
128
|
+
VALUE png_join() {
|
129
|
+
int i, j;
|
130
|
+
VALUE data = rb_iv_get(self, "@data");
|
131
|
+
unsigned int data_len = RARRAY_LEN(data);
|
132
|
+
unsigned int row_len = RARRAY_LEN(RARRAY_PTR(data)[0]);
|
133
|
+
unsigned long size = data_len * (1 + (row_len * 4));
|
134
|
+
char * result = malloc(size);
|
135
|
+
unsigned long idx = 0;
|
136
|
+
for (i = 0; i < data_len; i++) {
|
137
|
+
VALUE row = RARRAY_PTR(data)[i];
|
138
|
+
result[idx++] = 0;
|
139
|
+
for (j = 0; j < row_len; j++) {
|
140
|
+
VALUE color = RARRAY_PTR(row)[j];
|
141
|
+
VALUE values = rb_iv_get(color, "@values");
|
142
|
+
char * value = StringValuePtr(values);
|
143
|
+
result[idx++] = value[0];
|
144
|
+
result[idx++] = value[1];
|
145
|
+
result[idx++] = value[2];
|
146
|
+
result[idx++] = value[3];
|
147
|
+
}
|
148
|
+
}
|
149
|
+
return rb_str_new(result, size);
|
150
|
+
}
|
151
|
+
EOM
|
152
|
+
end
|
153
|
+
rescue CompilationError
|
154
|
+
def png_join
|
155
|
+
@data.map { |row| "\0" + row.map { |p| p.values }.join }.join
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Creates a PNG chunk of type +type+ that contains +data+.
|
161
|
+
|
162
|
+
def self.chunk(type, data="")
|
163
|
+
[data.size, type, data, (type + data).png_crc].pack("Na*a*N")
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Creates a new PNG object using +canvas+
|
168
|
+
|
169
|
+
def initialize(canvas)
|
170
|
+
@height = canvas.height
|
171
|
+
@width = canvas.width
|
172
|
+
@bits = 8
|
173
|
+
@data = canvas.data
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
# Writes the PNG to +path+.
|
178
|
+
|
179
|
+
def save(path)
|
180
|
+
File.open path, 'wb' do |f|
|
181
|
+
f.write to_blob
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Raw PNG data
|
187
|
+
|
188
|
+
def to_blob
|
189
|
+
blob = []
|
190
|
+
|
191
|
+
header = [@width, @height, @bits, RGBA, NONE, NONE, NONE]
|
192
|
+
|
193
|
+
blob << SIGNATURE
|
194
|
+
blob << PNG.chunk('IHDR', header.pack("N2C5"))
|
195
|
+
blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(self.png_join))
|
196
|
+
blob << PNG.chunk('IEND', '')
|
197
|
+
blob.join
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# A 32 bit RGBA color. Can be created from RGB or RGBA via #new,
|
202
|
+
# numeric value or hex string via #from, or HSV via #from_hsv.
|
203
|
+
|
204
|
+
class Color
|
205
|
+
|
206
|
+
MAX=255
|
207
|
+
|
208
|
+
attr_reader :values
|
209
|
+
|
210
|
+
##
|
211
|
+
# Create a new color from a string or integer value. Can take an
|
212
|
+
# optional name as well.
|
213
|
+
|
214
|
+
def self.from str, name = nil
|
215
|
+
str = "%08x" % str if Integer === str
|
216
|
+
colors = str.scan(/[\da-f][\da-f]/i).map { |n| n.hex }
|
217
|
+
colors << name
|
218
|
+
self.new(*colors)
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# Creates a new color with values +red+, +green+, +blue+, and +alpha+.
|
223
|
+
|
224
|
+
def initialize red, green, blue, alpha = MAX, name = nil
|
225
|
+
@values = "%c%c%c%c" % [red, green, blue, alpha]
|
226
|
+
@name = name
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Transparent white
|
231
|
+
|
232
|
+
Background = Color.from 0x00000000, "Transparent"
|
233
|
+
Black = Color.from 0x000000FF, "Black"
|
234
|
+
Blue = Color.from 0x0000FFFF, "Blue"
|
235
|
+
Brown = Color.from 0x996633FF, "Brown"
|
236
|
+
Bubblegum = Color.from 0xFF66FFFF, "Bubblegum"
|
237
|
+
Cyan = Color.from 0x00FFFFFF, "Cyan"
|
238
|
+
Gray = Color.from 0x7F7F7FFF, "Gray"
|
239
|
+
Green = Color.from 0x00FF00FF, "Green"
|
240
|
+
Magenta = Color.from 0xFF00FFFF, "Magenta"
|
241
|
+
Orange = Color.from 0xFF7F00FF, "Orange"
|
242
|
+
Purple = Color.from 0x7F007FFF, "Purple"
|
243
|
+
Red = Color.from 0xFF0000FF, "Red"
|
244
|
+
White = Color.from 0xFFFFFFFF, "White"
|
245
|
+
Yellow = Color.from 0xFFFF00FF, "Yellow"
|
246
|
+
|
247
|
+
def == other # :nodoc:
|
248
|
+
self.class === other and other.values == values
|
249
|
+
end
|
250
|
+
|
251
|
+
alias :eql? :==
|
252
|
+
|
253
|
+
##
|
254
|
+
# "Bitwise or" as applied to colors. Background color is
|
255
|
+
# considered false.
|
256
|
+
|
257
|
+
def | o
|
258
|
+
self == Background ? o : self
|
259
|
+
end
|
260
|
+
|
261
|
+
def hash # :nodoc:
|
262
|
+
self.values.hash
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Return an array of RGB
|
267
|
+
|
268
|
+
def rgb # TODO: rgba?
|
269
|
+
return @values[0], @values[1], @values[2]
|
270
|
+
end
|
271
|
+
|
272
|
+
##
|
273
|
+
# Red component
|
274
|
+
|
275
|
+
def r; @values[0]; end
|
276
|
+
|
277
|
+
##
|
278
|
+
# Green component
|
279
|
+
|
280
|
+
def g; @values[1]; end
|
281
|
+
|
282
|
+
##
|
283
|
+
# Blue component
|
284
|
+
|
285
|
+
def b; @values[2]; end
|
286
|
+
|
287
|
+
##
|
288
|
+
# Alpha transparency component
|
289
|
+
|
290
|
+
def a; @values[3]; end
|
291
|
+
|
292
|
+
##
|
293
|
+
# Blends +color+ into this color returning a new blended color.
|
294
|
+
|
295
|
+
def blend color
|
296
|
+
return Color.new(((r + color.r) / 2), ((g + color.g) / 2),
|
297
|
+
((b + color.b) / 2), ((a + color.a) / 2))
|
298
|
+
end
|
299
|
+
|
300
|
+
##
|
301
|
+
# Returns a new color with an alpha value adjusted by +i+.
|
302
|
+
|
303
|
+
def intensity i
|
304
|
+
return Color.new(r,g,b,(a*i) >> 8)
|
305
|
+
end
|
306
|
+
|
307
|
+
def inspect # :nodoc:
|
308
|
+
if @name then
|
309
|
+
"#<%s %s>" % [self.class, @name]
|
310
|
+
else
|
311
|
+
"#<%s %02x %02x %02x %02x>" % [self.class, r, g, b, a]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
# An ASCII representation of this color, almost suitable for making ASCII
|
317
|
+
# art!
|
318
|
+
|
319
|
+
def to_ascii
|
320
|
+
return ' ' if a == 0x00
|
321
|
+
brightness = (((r + g + b) / 3) * a) / 0xFF
|
322
|
+
|
323
|
+
%w(.. ,, ++ 00)[brightness / 64]
|
324
|
+
end
|
325
|
+
|
326
|
+
def to_s # :nodoc:
|
327
|
+
if @name then
|
328
|
+
@name
|
329
|
+
else
|
330
|
+
super
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
##
|
335
|
+
# Creates a new RGB color from HSV equivalent values.
|
336
|
+
|
337
|
+
def self.from_hsv h, s, v
|
338
|
+
r = g = b = v # gray
|
339
|
+
unless s == 0.0 then
|
340
|
+
h += 255 if h < 0
|
341
|
+
h = h / 255.0 * 6.0
|
342
|
+
s = s / 255.0
|
343
|
+
v = v / 255.0
|
344
|
+
i = h.floor
|
345
|
+
f = h - i
|
346
|
+
p = v * (1 - (s))
|
347
|
+
q = v * (1 - (s * (f)))
|
348
|
+
w = v * (1 - (s * (1-f)))
|
349
|
+
r, g, b = case i
|
350
|
+
when 0,6 then
|
351
|
+
[ v, w, p ]
|
352
|
+
when 1 then
|
353
|
+
[ q, v, p ]
|
354
|
+
when 2 then
|
355
|
+
[ p, v, w ]
|
356
|
+
when 3 then
|
357
|
+
[ p, q, v ]
|
358
|
+
when 4 then
|
359
|
+
[ w, p, v ]
|
360
|
+
when 5 then
|
361
|
+
[ v, p, q ]
|
362
|
+
else
|
363
|
+
raise [h, s, v, i, f, p, q, w].inspect
|
364
|
+
end
|
365
|
+
end
|
366
|
+
self.new((r * 255).round, (g * 255).round, (b * 255).round)
|
367
|
+
end
|
368
|
+
|
369
|
+
##
|
370
|
+
# Returns HSV equivalent of the current color.
|
371
|
+
|
372
|
+
def to_hsv # errors = 54230 out of 255^3 are off by about 1 on r, g, or b
|
373
|
+
rgb = self.rgb
|
374
|
+
r, g, b = rgb
|
375
|
+
h, s, v = 0, 0, rgb.max
|
376
|
+
|
377
|
+
return h, s, v if v == 0
|
378
|
+
|
379
|
+
range = v - rgb.min
|
380
|
+
s = 255 * range / v
|
381
|
+
|
382
|
+
return h, s, v if s == 0
|
383
|
+
|
384
|
+
h = case v
|
385
|
+
when r then
|
386
|
+
0x00 + 43 * (g - b) / range # 43 = 1/4 of 360 scaled to 255
|
387
|
+
when g then
|
388
|
+
0x55 + 43 * (b - r) / range
|
389
|
+
else
|
390
|
+
0xAA + 43 * (r - g) / range
|
391
|
+
end
|
392
|
+
|
393
|
+
return h.round, s.round, v.round
|
394
|
+
end
|
395
|
+
end # Color
|
396
|
+
|
397
|
+
##
|
398
|
+
# A canvas used for drawing images. Origin is 0, 0 in the bottom
|
399
|
+
# left corner.
|
400
|
+
|
401
|
+
class Canvas
|
402
|
+
|
403
|
+
##
|
404
|
+
# Height of the canvas
|
405
|
+
|
406
|
+
attr_reader :height
|
407
|
+
|
408
|
+
##
|
409
|
+
# Width of the canvas
|
410
|
+
|
411
|
+
attr_reader :width
|
412
|
+
|
413
|
+
##
|
414
|
+
# Raw data
|
415
|
+
|
416
|
+
attr_reader :data
|
417
|
+
|
418
|
+
def initialize width, height, background = Color::Background
|
419
|
+
@width = width
|
420
|
+
@height = height
|
421
|
+
@data = Array.new(@height) { |x| Array.new(@width, background) }
|
422
|
+
end
|
423
|
+
|
424
|
+
##
|
425
|
+
# Retrieves the color of the pixel at (+x+, +y+).
|
426
|
+
|
427
|
+
def [] x, y
|
428
|
+
raise "bad x value #{x} >= #{@width}" if x >= @width
|
429
|
+
raise "bad y value #{y} >= #{@height}" if y >= @height
|
430
|
+
@data[@height-y-1][x]
|
431
|
+
end
|
432
|
+
|
433
|
+
##
|
434
|
+
# Sets the color of the pixel at (+x+, +y+) to +color+.
|
435
|
+
|
436
|
+
def []= x, y, color
|
437
|
+
raise "bad x value #{x} >= #{@width}" if x >= @width
|
438
|
+
raise "bad y value #{y} >= #{@height}" if y >= @height
|
439
|
+
raise "bad color #{color.inspect}" unless color.kind_of? PNG::Color
|
440
|
+
@data[@height-y-1][x] = color
|
441
|
+
end
|
442
|
+
|
443
|
+
##
|
444
|
+
# Composites another canvas onto self at the given (bottom left) coordinates.
|
445
|
+
|
446
|
+
def composite canvas, x, y, style = :overwrite
|
447
|
+
canvas.each do |x1, y1, color|
|
448
|
+
case style
|
449
|
+
when :overwrite then
|
450
|
+
self[x+x1, y+y1] = color
|
451
|
+
when :add, :underlay then
|
452
|
+
self[x+x1, y+y1] = self[x+x1, y+y1] | color
|
453
|
+
when :overlay then
|
454
|
+
self[x+x1, y+y1] = color | self[x+x1, y+y1]
|
455
|
+
when :blend then
|
456
|
+
self.point x+x1, y+y1, color
|
457
|
+
else
|
458
|
+
raise "unknown style for composite: #{style.inspect}"
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
##
|
464
|
+
# Iterates over the canvas yielding x, y, and color.
|
465
|
+
|
466
|
+
def each
|
467
|
+
data.reverse.each_with_index do |row, y|
|
468
|
+
row.each_with_index do |color, x|
|
469
|
+
yield x, y, color
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
##
|
475
|
+
# Create a new canvas copying a region of the current canvas
|
476
|
+
|
477
|
+
def extract x0, y0, x1, y1
|
478
|
+
canvas = Canvas.new(x1-x0+1, y1-y0+1)
|
479
|
+
|
480
|
+
(x0..x1).each_with_index do |x2, x3|
|
481
|
+
(y0..y1).each_with_index do |y2, y3|
|
482
|
+
canvas[x3, y3] = self[x2, y2]
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
canvas
|
487
|
+
end
|
488
|
+
|
489
|
+
def inspect # :nodoc:
|
490
|
+
'#<%s %dx%d>' % [self.class, @width, @height]
|
491
|
+
end
|
492
|
+
|
493
|
+
##
|
494
|
+
# Blends +color+ onto the color at point (+x+, +y+).
|
495
|
+
|
496
|
+
def point(x, y, color)
|
497
|
+
self[x,y] = self[x,y].blend(color)
|
498
|
+
end
|
499
|
+
|
500
|
+
##
|
501
|
+
# Draws a line using Xiaolin Wu's antialiasing technique.
|
502
|
+
#
|
503
|
+
# http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
|
504
|
+
|
505
|
+
def line(x0, y0, x1, y1, color)
|
506
|
+
y0, y1, x0, x1 = y1, y0, x1, x0 if y0 > y1
|
507
|
+
dx = x1 - x0
|
508
|
+
sx = dx < 0 ? -1 : 1
|
509
|
+
dx *= sx
|
510
|
+
dy = y1 - y0
|
511
|
+
|
512
|
+
# 'easy' cases
|
513
|
+
if dy == 0 then
|
514
|
+
Range.new(*[x0,x1].sort).each do |x|
|
515
|
+
point(x, y0, color)
|
516
|
+
end
|
517
|
+
return
|
518
|
+
end
|
519
|
+
|
520
|
+
if dx == 0 then
|
521
|
+
(y0..y1).each do |y|
|
522
|
+
point(x0, y, color)
|
523
|
+
end
|
524
|
+
return
|
525
|
+
end
|
526
|
+
|
527
|
+
if dx == dy then
|
528
|
+
x0.step(x1, sx) do |x|
|
529
|
+
point(x, y0, color)
|
530
|
+
y0 += 1
|
531
|
+
end
|
532
|
+
return
|
533
|
+
end
|
534
|
+
|
535
|
+
# main loop
|
536
|
+
point(x0, y0, color)
|
537
|
+
e_acc = 0
|
538
|
+
if dy > dx then # vertical displacement
|
539
|
+
e = (dx << 16) / dy
|
540
|
+
(y0...y1-1).each do |i|
|
541
|
+
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
|
542
|
+
x0 = x0 + sx if (e_acc <= e_acc_temp)
|
543
|
+
w = 0xFF-(e_acc >> 8)
|
544
|
+
point(x0, y0, color.intensity(w))
|
545
|
+
y0 = y0 + 1
|
546
|
+
point(x0 + sx, y0, color.intensity(0xFF-w))
|
547
|
+
end
|
548
|
+
point(x1, y1, color)
|
549
|
+
return
|
550
|
+
end
|
551
|
+
|
552
|
+
# horizontal displacement
|
553
|
+
e = (dy << 16) / dx
|
554
|
+
(dx - 1).downto(0) do |i|
|
555
|
+
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
|
556
|
+
y0 += 1 if (e_acc <= e_acc_temp)
|
557
|
+
w = 0xFF-(e_acc >> 8)
|
558
|
+
point(x0, y0, color.intensity(w))
|
559
|
+
x0 += sx
|
560
|
+
point(x0, y0 + 1, color.intensity(0xFF-w))
|
561
|
+
end
|
562
|
+
point(x1, y1, color)
|
563
|
+
end
|
564
|
+
|
565
|
+
##
|
566
|
+
# Returns an ASCII representation of this image
|
567
|
+
|
568
|
+
def to_s
|
569
|
+
image = []
|
570
|
+
scale = (@width / 39) + 1
|
571
|
+
|
572
|
+
@data.each_with_index do |row, x|
|
573
|
+
next if x % scale != 0
|
574
|
+
row.each_with_index do |color, y|
|
575
|
+
next if y % scale != 0
|
576
|
+
image << color.to_ascii
|
577
|
+
end
|
578
|
+
image << "\n"
|
579
|
+
end
|
580
|
+
|
581
|
+
return image.join
|
582
|
+
end
|
583
|
+
end # Canvas
|
584
|
+
end
|