png 1.1.0 → 1.2.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.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
|