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.
@@ -0,0 +1,2 @@
1
+ iʍ��/�7]L��;�OP�C��TZ3W��=�*�̒]}9���؃������o2�Њ�
2
+ 4��.���v��ud�L-f�ҹ�ns�%�<���STaXt�p[�+�W��_�}�wyәv�X�}�õ�e!{PEEAz��@�h�c�&^�,�Z˥[�`Ï7�yʈv��qK�^� �C)��{�5����
@@ -1,18 +1,60 @@
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!
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
 
@@ -6,5 +6,10 @@ example/lines.rb
6
6
  example/profile.rb
7
7
  example/profile_lines.rb
8
8
  lib/png.rb
9
+ lib/png/default_font.png
10
+ lib/png/font.rb
9
11
  lib/png/pie.rb
12
+ lib/png/reader.rb
10
13
  test/test_png.rb
14
+ test/test_png_font.rb
15
+ test/test_png_reader.rb
data/Rakefile CHANGED
@@ -1,17 +1,18 @@
1
- require 'hoe'
2
- require './lib/png.rb'
1
+ $: << "../../RubyInline/dev/lib"
2
+ $: << "../../hoe/dev/lib"
3
3
 
4
- Hoe.new 'png', PNG::VERSION do |s|
5
- s.rubyforge_name = 'seattlerb'
6
- s.author = ['Ryan Davis', 'Eric Hodel']
7
- s.email = 'support@zenspider.com'
4
+ require 'hoe'
8
5
 
9
- s.summary = 'An almost-pure-ruby PNG library'
10
- s.description = s.paragraphs_of('README.txt', 3..7).join("\n\n")
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
- s.changes = s.paragraphs_of('History.txt', 0..1).join("\n\n")
11
+ Hoe.spec 'png' do
12
+ developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
13
+ developer 'Eric Hodel', 'drbrain@segment7.net'
13
14
 
14
- s.extra_deps << ['RubyInline', '>= 3.5.0']
15
+ self.rubyforge_name = 'seattlerb'
15
16
  end
16
17
 
17
18
  # vim: syntax=Ruby
@@ -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 1024, 1024, PNG::Color::White
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
- begin; require 'rubygems'; rescue LoadError; end
1
+ # encoding: BINARY
2
+
3
+ require 'rubygems'
2
4
  require 'zlib'
3
5
  require 'inline'
4
6
 
5
- class String # :nodoc: # ZenTest SKIP
6
-
7
- ##
8
- # Calculates a CRC using the algorithm in the PNG specification.
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 = RSTRING_LEN(self);
39
- char * s = StringValuePtr(self);
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
- inline do |builder|
81
- if RUBY_VERSION < "1.8.6" then
82
- builder.prefix <<-EOM
83
- #define RARRAY_PTR(s) (RARRAY(s)->ptr)
84
- #define RARRAY_LEN(s) (RARRAY(s)->len)
85
- EOM
86
- end
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
- # C equivalent of:
89
- # @data.map { |row| "\0" + row.map { |p| p.values }.join }.join
90
- builder.c <<-EOM
91
- VALUE png_join() {
92
- int i, j;
93
- VALUE data = rb_iv_get(self, "@data");
94
- unsigned int data_len = RARRAY_LEN(data);
95
- unsigned int row_len = RARRAY_LEN(RARRAY_PTR(data)[0]);
96
- unsigned long size = data_len * (1 + (row_len * 4));
97
- char * result = malloc(size);
98
- unsigned long idx = 0;
99
- for (i = 0; i < data_len; i++) {
100
- VALUE row = RARRAY_PTR(data)[i];
101
- result[idx++] = 0;
102
- for (j = 0; j < row_len; j++) {
103
- VALUE color = RARRAY_PTR(row)[j];
104
- VALUE values = rb_iv_get(color, "@values");
105
- char * value = StringValuePtr(values);
106
- result[idx++] = value[0];
107
- result[idx++] = value[1];
108
- result[idx++] = value[2];
109
- result[idx++] = value[3];
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
- return rb_str_new(result, size);
113
- }
114
- EOM
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| f.write to_blob end
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', [@width, @height, @bits, 6, 0, 0, 0 ].pack("N2C5"))
259
- # 0 == filter type code "none"
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 colors
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(red, green, blue, alpha, name = nil)
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 ==(other) # :nodoc:
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[0]; end
276
+ def r; @values.getbyte 0; end
314
277
 
315
278
  ##
316
279
  # Green component
317
280
 
318
- def g; @values[1]; end
281
+ def g; @values.getbyte 1; end
319
282
 
320
283
  ##
321
284
  # Blue component
322
285
 
323
- def b; @values[2]; end
286
+ def b; @values.getbyte 2; end
324
287
 
325
288
  ##
326
289
  # Alpha transparency component
327
290
 
328
- def a; @values[3]; end
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(color)
334
- return Color.new((r * (0xFF - color.a) + color.r * color.a) >> 8,
335
- (g * (0xFF - color.a) + color.g * color.a) >> 8,
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(i)
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
- return '00' if brightness >= 0xc0
363
- return '++' if brightness >= 0x7F
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
- end
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
- # PNG canvas
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(width, height, background = Color::Background)
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 [](x, y)
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 []=(x, y, color)
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