png 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +2 -0
- data/History.txt +59 -17
- data/Manifest.txt +5 -0
- data/Rakefile +11 -10
- data/example/lines.rb +4 -12
- data/lib/png.rb +260 -190
- data/lib/png/default_font.png +0 -0
- data/lib/png/font.rb +73 -0
- data/lib/png/pie.rb +1 -1
- data/lib/png/reader.rb +142 -0
- data/test/test_png.rb +179 -69
- data/test/test_png_font.rb +84 -0
- data/test/test_png_reader.rb +94 -0
- metadata +88 -52
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
data/History.txt
CHANGED
@@ -1,18 +1,60 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
1
|
+
=== 1.2.0 / 2009-06-23
|
2
|
+
|
3
|
+
* 26 minor enhancements:
|
4
|
+
|
5
|
+
* Add load_metadata flag to PNG.load to extract dimensions and depth.
|
6
|
+
* Add pure-ruby versions back for png_join and png_crc.
|
7
|
+
* Added Canvas#composite(canvas, x, y, style) with overwrite, underlay, overlay, and blend.
|
8
|
+
* Added Canvas#each, passes x, y, color.
|
9
|
+
* Added Canvas#extract.
|
10
|
+
* Added Color#|, Background color is effective false.
|
11
|
+
* Added Font#coordinates and cached each letter when extracted.
|
12
|
+
* Added PNG::load_file.
|
13
|
+
* Added reader tests.
|
14
|
+
* Added tests for PNG::Font.
|
15
|
+
* Added tests for PNG::load.
|
16
|
+
* Added to/from hsv methods to Color.
|
17
|
+
* Clean up tests to remove duplication.
|
18
|
+
* Cleaned up Color#to_ascii to make much easier to read and extend.
|
19
|
+
* Cleaned up a fair amount of code, removing as many raw literals as possible.
|
20
|
+
* Color#blend is a simple averaging algorithm now.
|
21
|
+
* Extended reader to include RGB as well, paving the way for grayscale.
|
22
|
+
* Fake support for RGB as well as RGBA (default alpha to 255).
|
23
|
+
* Made reader work again (was flipped and all sorts of broken).
|
24
|
+
* Split out reader.
|
25
|
+
* Switched to minitest.
|
26
|
+
* Time to refactor PNG::Font to use #extract and #composite...
|
27
|
+
* Updated Rakefile to new hoe capabilities.
|
28
|
+
* Updated manifest.
|
29
|
+
* metadata_only now cleaner.
|
30
|
+
* read_IHDR now returns height, width instead of a canvas.
|
31
|
+
|
32
|
+
* 3 bug fixes:
|
33
|
+
|
34
|
+
* Fixed example/lines and added text.
|
35
|
+
* Fixes for 1.9.
|
36
|
+
* Ignore color profile if it exists (fixes problems on osx screenshots).
|
37
|
+
|
38
|
+
=== 1.1.0 / 2007-03-26
|
39
|
+
|
40
|
+
* 4 major enhancements:
|
41
|
+
|
42
|
+
* Fixed and incorporated Dominik Barathon's optimizations.
|
43
|
+
* Wrote inline methods for png_crc and png_join. Now about 15x faster overall.
|
44
|
+
* Basic PNG loading.
|
45
|
+
* Reoriented x/y origin to bottom left. This will break things!
|
46
|
+
|
47
|
+
* 3 minor enhancements:
|
48
|
+
|
49
|
+
* Awesome ascii art patches from Tom Werner: Canvas#inpsect, Canvas#to_s, Color#to_ascii.
|
50
|
+
* Switched to Hoe.
|
51
|
+
* PNG.pie_chart from png/pie.
|
52
|
+
|
53
|
+
* 1 bug fix:
|
54
|
+
|
55
|
+
* Fixed bug in PNG::Canvas#each.
|
56
|
+
|
57
|
+
=== 1.0.0 / 2006-09-31
|
58
|
+
|
59
|
+
* Birthday!
|
18
60
|
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,17 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
$: << "../../RubyInline/dev/lib"
|
2
|
+
$: << "../../hoe/dev/lib"
|
3
3
|
|
4
|
-
|
5
|
-
s.rubyforge_name = 'seattlerb'
|
6
|
-
s.author = ['Ryan Davis', 'Eric Hodel']
|
7
|
-
s.email = 'support@zenspider.com'
|
4
|
+
require 'hoe'
|
8
5
|
|
9
|
-
|
10
|
-
|
6
|
+
Hoe.add_include_dirs "../../hoe/dev/lib" # HACK remove
|
7
|
+
Hoe.add_include_dirs "../../RubyInline/dev/lib", "lib"
|
8
|
+
Hoe.plugin :seattlerb
|
9
|
+
Hoe.plugin :inline
|
11
10
|
|
12
|
-
|
11
|
+
Hoe.spec 'png' do
|
12
|
+
developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
|
13
|
+
developer 'Eric Hodel', 'drbrain@segment7.net'
|
13
14
|
|
14
|
-
|
15
|
+
self.rubyforge_name = 'seattlerb'
|
15
16
|
end
|
16
17
|
|
17
18
|
# vim: syntax=Ruby
|
data/example/lines.rb
CHANGED
@@ -1,18 +1,9 @@
|
|
1
1
|
#!/usr/local/bin/ruby -w
|
2
2
|
|
3
3
|
require 'png'
|
4
|
+
require 'png/font'
|
4
5
|
|
5
|
-
canvas = PNG::Canvas.new
|
6
|
-
|
7
|
-
#canvas.each do |x, y|
|
8
|
-
# case x
|
9
|
-
# when y then
|
10
|
-
# canvas.point(x, y, Color::Black)
|
11
|
-
# when 50 then
|
12
|
-
# canvas.point(x, y, Color::Background)
|
13
|
-
# end
|
14
|
-
# canvas.point(x, y, Color::Green) if y = 200
|
15
|
-
#end
|
6
|
+
canvas = PNG::Canvas.new 201, 201, PNG::Color::White
|
16
7
|
|
17
8
|
canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
18
9
|
canvas.line 50, 50, 50, 100, PNG::Color::Blue
|
@@ -22,7 +13,8 @@ canvas.line 100, 50, 200, 75, PNG::Color::Green
|
|
22
13
|
canvas.line 0, 200, 200, 0, PNG::Color::Black
|
23
14
|
canvas.line 0, 200, 150, 0, PNG::Color::Red
|
24
15
|
|
16
|
+
canvas.annotate 'Hello World', 10, 10
|
17
|
+
|
25
18
|
png = PNG.new canvas
|
26
19
|
png.save 'blah.png'
|
27
20
|
`open blah.png`
|
28
|
-
|
data/lib/png.rb
CHANGED
@@ -1,29 +1,26 @@
|
|
1
|
-
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
require 'rubygems'
|
2
4
|
require 'zlib'
|
3
5
|
require 'inline'
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
unless "".respond_to? :getbyte then
|
8
|
+
class String
|
9
|
+
alias :getbyte :[]
|
10
|
+
end
|
11
|
+
end
|
9
12
|
|
13
|
+
class String # :nodoc: # ZenTest SKIP
|
10
14
|
inline do |builder|
|
11
|
-
if RUBY_VERSION < "1.8.6" then
|
12
|
-
builder.prefix <<-EOM
|
13
|
-
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
14
|
-
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
15
|
-
EOM
|
16
|
-
end
|
17
|
-
|
18
15
|
builder.c <<-EOM
|
19
16
|
unsigned long png_crc() {
|
20
17
|
static unsigned long crc[256];
|
21
18
|
static char crc_table_computed = 0;
|
22
|
-
|
19
|
+
|
23
20
|
if (! crc_table_computed) {
|
24
21
|
unsigned long c;
|
25
22
|
int n, k;
|
26
|
-
|
23
|
+
|
27
24
|
for (n = 0; n < 256; n++) {
|
28
25
|
c = (unsigned long) n;
|
29
26
|
for (k = 0; k < 8; k++) {
|
@@ -35,8 +32,8 @@ class String # :nodoc: # ZenTest SKIP
|
|
35
32
|
}
|
36
33
|
|
37
34
|
unsigned long c = 0xffffffff;
|
38
|
-
unsigned len
|
39
|
-
char * s
|
35
|
+
unsigned len = RSTRING_LEN(self);
|
36
|
+
char * s = StringValuePtr(self);
|
40
37
|
unsigned i;
|
41
38
|
|
42
39
|
for (i = 0; i < len; i++) {
|
@@ -47,6 +44,30 @@ class String # :nodoc: # ZenTest SKIP
|
|
47
44
|
}
|
48
45
|
EOM
|
49
46
|
end
|
47
|
+
rescue CompilationError => e
|
48
|
+
warn "COMPLIATION ERROR: #{e}"
|
49
|
+
|
50
|
+
unless defined? @@crc then
|
51
|
+
@@crc = Array.new(256)
|
52
|
+
256.times do |n|
|
53
|
+
c = n
|
54
|
+
8.times do
|
55
|
+
c = (c & 1 == 1) ? 0xedb88320 ^ (c >> 1) : c >> 1
|
56
|
+
end
|
57
|
+
@@crc[n] = c
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Calculates a CRC using the algorithm in the PNG specification.
|
63
|
+
|
64
|
+
def png_crc()
|
65
|
+
c = 0xffffffff
|
66
|
+
each_byte do |b|
|
67
|
+
c = @@crc[(c^b) & 0xff] ^ (c >> 8)
|
68
|
+
end
|
69
|
+
return c ^ 0xffffffff
|
70
|
+
end
|
50
71
|
end
|
51
72
|
|
52
73
|
##
|
@@ -71,47 +92,69 @@ end
|
|
71
92
|
# canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
72
93
|
# png = PNG.new canvas
|
73
94
|
# png.save 'blah.png'
|
95
|
+
#
|
96
|
+
# = TODO:
|
97
|
+
#
|
98
|
+
# + Get everything orinted entirely on [x,y,h,w] with x,y origin being
|
99
|
+
# bottom left.
|
74
100
|
|
75
101
|
class PNG
|
76
|
-
|
77
|
-
VERSION = '1.1.0'
|
102
|
+
VERSION = '1.2.0'
|
78
103
|
SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
|
79
104
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
105
|
+
# Color Types:
|
106
|
+
GRAY = 0 # DEPTH = 1,2,4,8,16
|
107
|
+
RGB = 2 # DEPTH = 8,16
|
108
|
+
INDEXED = 3 # DEPTH = 1,2,4,8
|
109
|
+
GRAYA = 4 # DEPTH = 8,16
|
110
|
+
RGBA = 6 # DEPTH = 8,16
|
111
|
+
|
112
|
+
# Filter Types:
|
113
|
+
NONE = 0
|
114
|
+
SUB = 1
|
115
|
+
UP = 2
|
116
|
+
AVG = 3
|
117
|
+
PAETH = 4
|
118
|
+
|
119
|
+
begin
|
120
|
+
inline do |builder|
|
121
|
+
if RUBY_VERSION < "1.8.6" then
|
122
|
+
builder.prefix <<-EOM
|
123
|
+
#define RARRAY_PTR(s) (RARRAY(s)->ptr)
|
124
|
+
#define RARRAY_LEN(s) (RARRAY(s)->len)
|
125
|
+
EOM
|
126
|
+
end
|
87
127
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
128
|
+
builder.c <<-EOM
|
129
|
+
VALUE png_join() {
|
130
|
+
int i, j;
|
131
|
+
VALUE data = rb_iv_get(self, "@data");
|
132
|
+
unsigned int data_len = RARRAY_LEN(data);
|
133
|
+
unsigned int row_len = RARRAY_LEN(RARRAY_PTR(data)[0]);
|
134
|
+
unsigned long size = data_len * (1 + (row_len * 4));
|
135
|
+
char * result = malloc(size);
|
136
|
+
unsigned long idx = 0;
|
137
|
+
for (i = 0; i < data_len; i++) {
|
138
|
+
VALUE row = RARRAY_PTR(data)[i];
|
139
|
+
result[idx++] = 0;
|
140
|
+
for (j = 0; j < row_len; j++) {
|
141
|
+
VALUE color = RARRAY_PTR(row)[j];
|
142
|
+
VALUE values = rb_iv_get(color, "@values");
|
143
|
+
char * value = StringValuePtr(values);
|
144
|
+
result[idx++] = value[0];
|
145
|
+
result[idx++] = value[1];
|
146
|
+
result[idx++] = value[2];
|
147
|
+
result[idx++] = value[3];
|
148
|
+
}
|
149
|
+
}
|
150
|
+
return rb_str_new(result, size);
|
111
151
|
}
|
112
|
-
|
113
|
-
|
114
|
-
|
152
|
+
EOM
|
153
|
+
end
|
154
|
+
rescue CompilationError
|
155
|
+
def png_join
|
156
|
+
@data.map { |row| "\0" + row.map { |p| p.values }.join }.join
|
157
|
+
end
|
115
158
|
end
|
116
159
|
|
117
160
|
##
|
@@ -121,116 +164,6 @@ class PNG
|
|
121
164
|
[data.size, type, data, (type + data).png_crc].pack("Na*a*N")
|
122
165
|
end
|
123
166
|
|
124
|
-
def self.load(png)
|
125
|
-
png = png.dup
|
126
|
-
signature = png.slice! 0, 8
|
127
|
-
raise ArgumentError, 'Invalid PNG signature' unless signature == SIGNATURE
|
128
|
-
|
129
|
-
type, data = read_chunk png
|
130
|
-
|
131
|
-
raise ArgumentError, 'Invalid PNG, no IHDR chunk' unless type == 'IHDR'
|
132
|
-
|
133
|
-
canvas = read_IHDR data
|
134
|
-
type, data = read_chunk png
|
135
|
-
read_IDAT data, canvas
|
136
|
-
type, data = read_chunk png
|
137
|
-
raise 'oh no! IEND not next? crashing and burning!' unless type == 'IEND'
|
138
|
-
|
139
|
-
new canvas
|
140
|
-
end
|
141
|
-
|
142
|
-
def self.check_crc(type, data, crc)
|
143
|
-
return true if (type + data).png_crc == crc
|
144
|
-
raise ArgumentError, "Invalid CRC encountered in #{type} chunk"
|
145
|
-
end
|
146
|
-
|
147
|
-
def self.paeth(a, b, c) # left, above, upper left
|
148
|
-
p = a + b - c
|
149
|
-
pa = (p - a).abs
|
150
|
-
pb = (p - b).abs
|
151
|
-
pc = (p - c).abs
|
152
|
-
|
153
|
-
return a if pa <= pb && pa <= pc
|
154
|
-
return b if pb <= pc
|
155
|
-
c
|
156
|
-
end
|
157
|
-
|
158
|
-
def self.read_chunk(png)
|
159
|
-
size, type = png.slice!(0, 8).unpack 'Na4'
|
160
|
-
data, crc = png.slice!(0, size + 4).unpack "a#{size}N"
|
161
|
-
|
162
|
-
check_crc type, data, crc
|
163
|
-
|
164
|
-
return type, data
|
165
|
-
end
|
166
|
-
|
167
|
-
def self.read_IDAT(data, canvas)
|
168
|
-
data = Zlib::Inflate.inflate(data).unpack 'C*'
|
169
|
-
scanline_length = 4 * canvas.width + 1 # for filter
|
170
|
-
row = 0
|
171
|
-
until data.empty? do
|
172
|
-
row_data = data.slice! 0, scanline_length
|
173
|
-
filter = row_data.shift
|
174
|
-
case filter
|
175
|
-
when 0 then # None
|
176
|
-
when 1 then # Sub
|
177
|
-
row_data.each_with_index do |byte, index|
|
178
|
-
left = index < 4 ? 0 : row_data[index - 4]
|
179
|
-
row_data[index] = (byte + left) % 256
|
180
|
-
#p [byte, left, row_data[index]]
|
181
|
-
end
|
182
|
-
when 2 then # Up
|
183
|
-
row_data.each_with_index do |byte, index|
|
184
|
-
col = index / 4
|
185
|
-
upper = row == 0 ? 0 : canvas[col, row - 1].values[index % 4]
|
186
|
-
row_data[index] = (upper + byte) % 256
|
187
|
-
end
|
188
|
-
when 3 then # Average
|
189
|
-
row_data.each_with_index do |byte, index|
|
190
|
-
col = index / 4
|
191
|
-
upper = row == 0 ? 0 : canvas[col, row - 1].values[index % 4]
|
192
|
-
left = index < 4 ? 0 : row_data[index - 4]
|
193
|
-
|
194
|
-
row_data[index] = (byte + ((left + upper)/2).floor) % 256
|
195
|
-
end
|
196
|
-
when 4 then # Paeth
|
197
|
-
left = upper = upper_left = nil
|
198
|
-
row_data.each_with_index do |byte, index|
|
199
|
-
col = index / 4
|
200
|
-
|
201
|
-
left = index < 4 ? 0 : row_data[index - 4]
|
202
|
-
if row == 0 then
|
203
|
-
upper = upper_left = 0
|
204
|
-
else
|
205
|
-
upper = canvas[col, row - 1].values[index % 4]
|
206
|
-
upper_left = col == 0 ? 0 :
|
207
|
-
canvas[col - 1, row - 1].values[index % 4]
|
208
|
-
end
|
209
|
-
|
210
|
-
paeth = paeth left, upper, upper_left
|
211
|
-
row_data[index] = (byte + paeth) % 256
|
212
|
-
#p [byte, paeth, row_data[index]]
|
213
|
-
end
|
214
|
-
else
|
215
|
-
raise ArgumentError, "Invalid filter algorithm #{filter}"
|
216
|
-
end
|
217
|
-
|
218
|
-
col = 0
|
219
|
-
row_data.each_slice 4 do |slice|
|
220
|
-
canvas[col, row] = PNG::Color.new(*slice)
|
221
|
-
col += 1
|
222
|
-
end
|
223
|
-
|
224
|
-
row += 1
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
def self.read_IHDR(data)
|
229
|
-
width, height, *rest = data.unpack 'N2C5'
|
230
|
-
raise ArgumentError, 'unsupported PNG file' unless rest == [8, 6, 0, 0, 0]
|
231
|
-
return PNG::Canvas.new(height, width)
|
232
|
-
end
|
233
|
-
|
234
167
|
##
|
235
168
|
# Creates a new PNG object using +canvas+
|
236
169
|
|
@@ -245,7 +178,9 @@ class PNG
|
|
245
178
|
# Writes the PNG to +path+.
|
246
179
|
|
247
180
|
def save(path)
|
248
|
-
File.open path, 'wb' do |f|
|
181
|
+
File.open path, 'wb' do |f|
|
182
|
+
f.write to_blob
|
183
|
+
end
|
249
184
|
end
|
250
185
|
|
251
186
|
##
|
@@ -254,22 +189,29 @@ class PNG
|
|
254
189
|
def to_blob
|
255
190
|
blob = []
|
256
191
|
|
192
|
+
header = [@width, @height, @bits, RGBA, NONE, NONE, NONE]
|
193
|
+
|
257
194
|
blob << SIGNATURE
|
258
|
-
blob << PNG.chunk('IHDR',
|
259
|
-
|
260
|
-
data = self.png_join
|
261
|
-
blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(data))
|
195
|
+
blob << PNG.chunk('IHDR', header.pack("N2C5"))
|
196
|
+
blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(self.png_join))
|
262
197
|
blob << PNG.chunk('IEND', '')
|
263
198
|
blob.join
|
264
199
|
end
|
265
200
|
|
266
201
|
##
|
267
|
-
# RGBA
|
202
|
+
# A 32 bit RGBA color. Can be created from RGB or RGBA via #new,
|
203
|
+
# numeric value or hex string via #from, or HSV via #from_hsv.
|
268
204
|
|
269
205
|
class Color
|
270
206
|
|
207
|
+
MAX=255
|
208
|
+
|
271
209
|
attr_reader :values
|
272
210
|
|
211
|
+
##
|
212
|
+
# Create a new color from a string or integer value. Can take an
|
213
|
+
# optional name as well.
|
214
|
+
|
273
215
|
def self.from str, name = nil
|
274
216
|
str = "%08x" % str if Integer === str
|
275
217
|
colors = str.scan(/[\da-f][\da-f]/i).map { |n| n.hex }
|
@@ -280,7 +222,7 @@ class PNG
|
|
280
222
|
##
|
281
223
|
# Creates a new color with values +red+, +green+, +blue+, and +alpha+.
|
282
224
|
|
283
|
-
def initialize
|
225
|
+
def initialize red, green, blue, alpha = MAX, name = nil
|
284
226
|
@values = "%c%c%c%c" % [red, green, blue, alpha]
|
285
227
|
@name = name
|
286
228
|
end
|
@@ -303,44 +245,63 @@ class PNG
|
|
303
245
|
White = Color.from 0xFFFFFFFF, "White"
|
304
246
|
Yellow = Color.from 0xFFFF00FF, "Yellow"
|
305
247
|
|
306
|
-
def ==
|
248
|
+
def == other # :nodoc:
|
307
249
|
self.class === other and other.values == values
|
308
250
|
end
|
309
251
|
|
252
|
+
alias :eql? :==
|
253
|
+
|
254
|
+
##
|
255
|
+
# "Bitwise or" as applied to colors. Background color is
|
256
|
+
# considered false.
|
257
|
+
|
258
|
+
def | o
|
259
|
+
self == Background ? o : self
|
260
|
+
end
|
261
|
+
|
262
|
+
def hash # :nodoc:
|
263
|
+
self.values.hash
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Return an array of RGB
|
268
|
+
|
269
|
+
def rgb # TODO: rgba?
|
270
|
+
return r, g, b
|
271
|
+
end
|
272
|
+
|
310
273
|
##
|
311
274
|
# Red component
|
312
275
|
|
313
|
-
def r; @values
|
276
|
+
def r; @values.getbyte 0; end
|
314
277
|
|
315
278
|
##
|
316
279
|
# Green component
|
317
280
|
|
318
|
-
def g; @values
|
281
|
+
def g; @values.getbyte 1; end
|
319
282
|
|
320
283
|
##
|
321
284
|
# Blue component
|
322
285
|
|
323
|
-
def b; @values
|
286
|
+
def b; @values.getbyte 2; end
|
324
287
|
|
325
288
|
##
|
326
289
|
# Alpha transparency component
|
327
290
|
|
328
|
-
def a; @values
|
291
|
+
def a; @values.getbyte 3; end
|
329
292
|
|
330
293
|
##
|
331
294
|
# Blends +color+ into this color returning a new blended color.
|
332
295
|
|
333
|
-
def blend
|
334
|
-
return Color.new((r
|
335
|
-
(
|
336
|
-
(b * (0xFF - color.a) + color.b * color.a) >> 8,
|
337
|
-
(a * (0xFF - color.a) + color.a * color.a) >> 8)
|
296
|
+
def blend color
|
297
|
+
return Color.new(((r + color.r) / 2), ((g + color.g) / 2),
|
298
|
+
((b + color.b) / 2), ((a + color.a) / 2))
|
338
299
|
end
|
339
300
|
|
340
301
|
##
|
341
302
|
# Returns a new color with an alpha value adjusted by +i+.
|
342
303
|
|
343
|
-
def intensity
|
304
|
+
def intensity i
|
344
305
|
return Color.new(r,g,b,(a*i) >> 8)
|
345
306
|
end
|
346
307
|
|
@@ -358,24 +319,86 @@ class PNG
|
|
358
319
|
|
359
320
|
def to_ascii
|
360
321
|
return ' ' if a == 0x00
|
322
|
+
|
361
323
|
brightness = (((r + g + b) / 3) * a) / 0xFF
|
362
|
-
|
363
|
-
|
364
|
-
return ',,' if brightness >= 0x40
|
365
|
-
return '..'
|
324
|
+
|
325
|
+
%w(.. ,, ++ 00)[brightness / 64]
|
366
326
|
end
|
367
327
|
|
368
|
-
def to_s
|
328
|
+
def to_s # :nodoc:
|
369
329
|
if @name then
|
370
330
|
@name
|
371
331
|
else
|
372
332
|
super
|
373
333
|
end
|
374
334
|
end
|
375
|
-
|
335
|
+
|
336
|
+
##
|
337
|
+
# Creates a new RGB color from HSV equivalent values.
|
338
|
+
|
339
|
+
def self.from_hsv h, s, v
|
340
|
+
r = g = b = v # gray
|
341
|
+
unless s == 0.0 then
|
342
|
+
h += 255 if h < 0
|
343
|
+
h = h / 255.0 * 6.0
|
344
|
+
s = s / 255.0
|
345
|
+
v = v / 255.0
|
346
|
+
i = h.floor
|
347
|
+
f = h - i
|
348
|
+
p = v * (1 - (s))
|
349
|
+
q = v * (1 - (s * (f)))
|
350
|
+
w = v * (1 - (s * (1-f)))
|
351
|
+
r, g, b = case i
|
352
|
+
when 0,6 then
|
353
|
+
[ v, w, p ]
|
354
|
+
when 1 then
|
355
|
+
[ q, v, p ]
|
356
|
+
when 2 then
|
357
|
+
[ p, v, w ]
|
358
|
+
when 3 then
|
359
|
+
[ p, q, v ]
|
360
|
+
when 4 then
|
361
|
+
[ w, p, v ]
|
362
|
+
when 5 then
|
363
|
+
[ v, p, q ]
|
364
|
+
else
|
365
|
+
raise [h, s, v, i, f, p, q, w].inspect
|
366
|
+
end
|
367
|
+
end
|
368
|
+
self.new((r * 255).round, (g * 255).round, (b * 255).round)
|
369
|
+
end
|
370
|
+
|
371
|
+
##
|
372
|
+
# Returns HSV equivalent of the current color.
|
373
|
+
|
374
|
+
def to_hsv # errors = 54230 out of 255^3 are off by about 1 on r, g, or b
|
375
|
+
rgb = self.rgb
|
376
|
+
r, g, b = rgb
|
377
|
+
h, s, v = 0, 0, rgb.max
|
378
|
+
|
379
|
+
return h, s, v if v == 0
|
380
|
+
|
381
|
+
range = v - rgb.min
|
382
|
+
s = 255 * range / v
|
383
|
+
|
384
|
+
return h, s, v if s == 0
|
385
|
+
|
386
|
+
h = case v
|
387
|
+
when r then
|
388
|
+
0x00 + 43 * (g - b) / range # 43 = 1/4 of 360 scaled to 255
|
389
|
+
when g then
|
390
|
+
0x55 + 43 * (b - r) / range
|
391
|
+
else
|
392
|
+
0xAA + 43 * (r - g) / range
|
393
|
+
end
|
394
|
+
|
395
|
+
return h.round, s.round, v.round
|
396
|
+
end
|
397
|
+
end # Color
|
376
398
|
|
377
399
|
##
|
378
|
-
#
|
400
|
+
# A canvas used for drawing images. Origin is 0, 0 in the bottom
|
401
|
+
# left corner.
|
379
402
|
|
380
403
|
class Canvas
|
381
404
|
|
@@ -394,7 +417,7 @@ class PNG
|
|
394
417
|
|
395
418
|
attr_reader :data
|
396
419
|
|
397
|
-
def initialize
|
420
|
+
def initialize width, height, background = Color::Background
|
398
421
|
@width = width
|
399
422
|
@height = height
|
400
423
|
@data = Array.new(@height) { |x| Array.new(@width, background) }
|
@@ -403,7 +426,7 @@ class PNG
|
|
403
426
|
##
|
404
427
|
# Retrieves the color of the pixel at (+x+, +y+).
|
405
428
|
|
406
|
-
def []
|
429
|
+
def [] x, y
|
407
430
|
raise "bad x value #{x} >= #{@width}" if x >= @width
|
408
431
|
raise "bad y value #{y} >= #{@height}" if y >= @height
|
409
432
|
@data[@height-y-1][x]
|
@@ -412,12 +435,59 @@ class PNG
|
|
412
435
|
##
|
413
436
|
# Sets the color of the pixel at (+x+, +y+) to +color+.
|
414
437
|
|
415
|
-
def []=
|
438
|
+
def []= x, y, color
|
416
439
|
raise "bad x value #{x} >= #{@width}" if x >= @width
|
417
440
|
raise "bad y value #{y} >= #{@height}" if y >= @height
|
441
|
+
raise "bad color #{color.inspect}" unless color.kind_of? PNG::Color
|
418
442
|
@data[@height-y-1][x] = color
|
419
443
|
end
|
420
444
|
|
445
|
+
##
|
446
|
+
# Composites another canvas onto self at the given (bottom left) coordinates.
|
447
|
+
|
448
|
+
def composite canvas, x, y, style = :overwrite
|
449
|
+
canvas.each do |x1, y1, color|
|
450
|
+
case style
|
451
|
+
when :overwrite then
|
452
|
+
self[x+x1, y+y1] = color
|
453
|
+
when :add, :underlay then
|
454
|
+
self[x+x1, y+y1] = self[x+x1, y+y1] | color
|
455
|
+
when :overlay then
|
456
|
+
self[x+x1, y+y1] = color | self[x+x1, y+y1]
|
457
|
+
when :blend then
|
458
|
+
self.point x+x1, y+y1, color
|
459
|
+
else
|
460
|
+
raise "unknown style for composite: #{style.inspect}"
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
##
|
466
|
+
# Iterates over the canvas yielding x, y, and color.
|
467
|
+
|
468
|
+
def each
|
469
|
+
data.reverse.each_with_index do |row, y|
|
470
|
+
row.each_with_index do |color, x|
|
471
|
+
yield x, y, color
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
##
|
477
|
+
# Create a new canvas copying a region of the current canvas
|
478
|
+
|
479
|
+
def extract x0, y0, x1, y1
|
480
|
+
canvas = Canvas.new(x1-x0+1, y1-y0+1)
|
481
|
+
|
482
|
+
(x0..x1).each_with_index do |x2, x3|
|
483
|
+
(y0..y1).each_with_index do |y2, y3|
|
484
|
+
canvas[x3, y3] = self[x2, y2]
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
canvas
|
489
|
+
end
|
490
|
+
|
421
491
|
def inspect # :nodoc:
|
422
492
|
'#<%s %dx%d>' % [self.class, @width, @height]
|
423
493
|
end
|
@@ -512,5 +582,5 @@ class PNG
|
|
512
582
|
|
513
583
|
return image.join
|
514
584
|
end
|
515
|
-
end
|
585
|
+
end # Canvas
|
516
586
|
end
|