png_canvas 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1e5af04de6e23c5a3ed095860288c711674d18e9
4
+ data.tar.gz: 332d2b59daa8aa307cdc84ae87aee025a6c4fe1c
5
+ SHA512:
6
+ metadata.gz: 2d47db813a6c5b02725d89d15d8aabb97ca7d517e0cf07b40bb72109f1ca5b1c20c8c97e787d3cf3fcd0352062b9a8de8dff389daa41dbc02bba5d2b8293559f
7
+ data.tar.gz: 68a192e2c4a1f52c5704be95feab50bd25f1634ee66cc567369fa0b67c8194480e47bd6a289dd66f7ce58b669a8dd9e22b7557f6ae219c102f2fa41bbf223309
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ nbproject
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ .DS_Store
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+ test.png
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Daniel Cruz Horts
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # png_canvas
2
+
3
+ A pure Ruby reimplementation of Rui Carmo's [pngcanvas](https://github.com/rcarmo/pngcanvas) Python library, featuring text writing and few renames.
4
+
5
+
6
+ ## Usage
7
+
8
+ Just add it in your Gemfile:
9
+
10
+ ```ruby
11
+ gem 'png_canvas'
12
+ ```
13
+
14
+ and require it when needed:
15
+
16
+ ```ruby
17
+ require 'png_canvas'
18
+ ```
19
+
20
+ ### Rendering an image
21
+
22
+ You can render the [reference image](https://github.com/rcarmo/pngcanvas/blob/master/reference.png) with:
23
+
24
+ ```ruby
25
+ png = PngCanvas.new(512, 512)
26
+ png.rectangle(0, 0, 512 - 1, 512 - 1)
27
+
28
+ png.vertical_gradient(1, 1, 512 - 2, 512 - 2, [0xff, 0, 0, 0xff], [0x20, 0, 0xff, 0x80])
29
+
30
+ png.color = [0, 0, 0, 0xff]
31
+ png.line(0, 0, 512 - 1, 512 - 1)
32
+ png.line(0, 0, 512 / 2, 512 - 1)
33
+ png.line(0, 0, 512 - 1, 512 / 2)
34
+
35
+ png.copy_rectangle(1, 1, 512 / 2 - 1, 512 / 2 - 1, 1, 512 / 2, png)
36
+
37
+ png.blend_rectangle(1, 1, 512 / 2 - 1, 512 / 2 - 1, 512 / 2, 0, png)
38
+ png.save 'reference.png'
39
+ ```
40
+
41
+ ## Reference
42
+
43
+ - Constructor parameters: **width, height, bgcolor: _default bgcolor_, color: _default fgcolor_**
44
+
45
+ Colors are specified as 4 element arrays, each representing red, green, blue and alpha values ranging from 0 to 255.
46
+
47
+ Default background color is white: [0xff, 0xff, 0xff, 0xff]
48
+
49
+ Default foreground color is black: [0x00, 0x00, 0x00, 0xff]
50
+
51
+ ### Public instance methods
52
+
53
+ - **color(color)**: sets the default foreground color for the following points and lines
54
+ - **point(x, y, color = nil)**: draws a point at x, y using the specified color (if any) or default
55
+ - **text(x0, y0, string, color = nil)**: writes the string of text at x0, y0
56
+ - **line(x0, y0, x1, y1)**: draws a single line using default color
57
+ - **polyline(point_array)**: draws lines using default color
58
+ - **vertical_gradient(x0, y0, x1, y1, from_color, to_color)**: draws a rectangular color gradient from from_color to to_color
59
+ - **rectangle(x0, y0, x1, y1)**: draws a rectangle with default color
60
+ - **filled_rectangle(x0, y0, x1, y1)**: draws a filled rectangle with default color
61
+ - **copy_rectangle(x0, y0, x1, y1, dx, dy, destination_png)**: copies rectangle from destination_png to current canvas with dx, dy offset
62
+ - **blend_rectangle(x0, y0, x1, y1, dx, dy, destination_png, alpha = 0xff)**: copies rectangle from destination_png to current canvas using alpha transparency
63
+ - **save(file_path)**: saves the canvas to the file_path
64
+ - **load(file_path)**: loads the PNG file from the file_path for manipulation
65
+ - **dump**: returns the contents of the canvas as binary
66
+
67
+ ## Contribute
68
+
69
+ - Fork it
70
+ - Write your feature with a test
71
+ - Issue a pull request
72
+ - …
73
+ - Profit!
data/lib/png_canvas.rb ADDED
@@ -0,0 +1,419 @@
1
+ # Based on Rui Carmo's PNGCanvas
2
+ # https://taoofmac.com/space/projects/PNGCanvas
3
+ # https://github.com/rcarmo/pngcanvas
4
+ # Reference: http://apidock.com/ruby/Array/pack
5
+
6
+ require 'zlib'
7
+
8
+ class PngCanvas
9
+
10
+ VERSION = '0.0.1'
11
+
12
+ CHARSET = {
13
+ '0' => [0b01111000, 0b10000100, 0b01111000],
14
+ '1' => [0b01000100, 0b11111100, 0b00000100],
15
+ '2' => [0b01000100, 0b10001100, 0b10010100, 0b01100100],
16
+ '3' => [0b01001000, 0b10000100, 0b10100100, 0b01011000],
17
+ '4' => [0b00110000, 0b01010000, 0b11111100, 0b00010000],
18
+ '5' => [0b11101000, 0b10100100, 0b10100100, 0b10011000],
19
+ '6' => [0b01111000, 0b10010100, 0b10100100, 0b00011000],
20
+ '7' => [0b10000000, 0b10001100, 0b10110000, 0b11000000],
21
+ '8' => [0b01011000, 0b10100100, 0b10100100, 0b01011000],
22
+ '9' => [0b01100000, 0b10010100, 0b10100100, 0b01111000],
23
+ 'A' => [0b01111100, 0b10010000, 0b10010000, 0b01111100],
24
+ 'B' => [0b11111100, 0b10100100, 0b10100100, 0b01011000],
25
+ 'C' => [0b01111000, 0b10000100, 0b10000100, 0b01001000],
26
+ 'D' => [0b11111100, 0b10000100, 0b10000100, 0b01111000],
27
+ 'E' => [0b11111100, 0b10100100, 0b10100100, 0b10000100],
28
+ 'F' => [0b11111100, 0b10100000, 0b10100000, 0b10000000],
29
+ 'G' => [0b01111000, 0b10000100, 0b10010100, 0b01011100],
30
+ 'H' => [0b11111100, 0b00100000, 0b00100000, 0b11111100],
31
+ 'I' => [0b10000100, 0b11111100, 0b10000100],
32
+ 'J' => [0b00001000, 0b00000100, 0b10000100, 0b11111000],
33
+ 'K' => [0b11111100, 0b00100000, 0b01011000, 0b10000100],
34
+ 'L' => [0b11111100, 0b00000100, 0b00000100, 0b00000100],
35
+ 'M' => [0b11111100, 0b01100000, 0b01100000, 0b11111100],
36
+ 'N' => [0b11111100, 0b01100000, 0b00111000, 0b11111100],
37
+ 'O' => [0b01111000, 0b10000100, 0b10000100, 0b01111000],
38
+ 'P' => [0b11111100, 0b10010000, 0b10010000, 0b01100000],
39
+ 'Q' => [0b01111000, 0b10010100, 0b10001100, 0b01111000],
40
+ 'R' => [0b11111100, 0b10010000, 0b10011000, 0b11100100],
41
+ 'S' => [0b01100100, 0b10010100, 0b10010100, 0b10001000],
42
+ 'T' => [0b10000000, 0b10000000, 0b11111100, 0b10000000, 0b10000000],
43
+ 'U' => [0b11111000, 0b00000100, 0b00000100, 0b11111000],
44
+ 'V' => [0b11110000, 0b00001100, 0b00001100, 0b11110000],
45
+ 'W' => [0b11111100, 0b00011000, 0b00011000, 0b11111100],
46
+ 'X' => [0b11001100, 0b00110000, 0b00110000, 0b11001100],
47
+ 'Y' => [0b11000000, 0b00100000, 0b00011100, 0b00100000, 0b11000000],
48
+ 'Z' => [0b10001100, 0b10010100, 0b10100100, 0b11000100],
49
+ 'a' => [0b00011000, 0b00100100, 0b00101000, 0b00111100],
50
+ 'b' => [0b11111100, 0b00100100, 0b00100100, 0b00011000],
51
+ 'c' => [0b00011000, 0b00100100, 0b00100100, 0b00100100],
52
+ 'd' => [0b00011000, 0b00100100, 0b00101000, 0b11111100],
53
+ 'e' => [0b00011000, 0b00101100, 0b00110100, 0b00010100],
54
+ 'f' => [0b00010000, 0b01111100, 0b10010000, 0b01000000],
55
+ 'g' => [0b00010000, 0b00101010, 0b00101010, 0b00111100],
56
+ 'h' => [0b11111100, 0b00100000, 0b00100000, 0b00011100],
57
+ 'i' => [0b00100100, 0b10111100, 0b00000100],
58
+ 'j' => [0b00000100, 0b00000010, 0b10111100],
59
+ 'k' => [0b11111100, 0b00010000, 0b00010000, 0b00101100],
60
+ 'l' => [0b10000100, 0b11111100, 0b00000100],
61
+ 'm' => [0b00111100, 0b00100000, 0b00011000, 0b00100000, 0b00111100],
62
+ 'n' => [0b00111100, 0b00010000, 0b00100000, 0b00011100],
63
+ 'o' => [0b00011000, 0b00100100, 0b00100100, 0b00011000],
64
+ 'p' => [0b00111110, 0b00101000, 0b00101000, 0b00010000],
65
+ 'q' => [0b00010000, 0b00101000, 0b00101000, 0b00111110],
66
+ 'r' => [0b00111100, 0b00010000, 0b00100000, 0b00010000],
67
+ 's' => [0b00010100, 0b00110100, 0b00101100, 0b00101000],
68
+ 't' => [0b00100000, 0b11111000, 0b00100100, 0b00001000],
69
+ 'u' => [0b00111000, 0b00000100, 0b00001000, 0b00111100],
70
+ 'v' => [0b00111000, 0b00000100, 0b00111000],
71
+ 'w' => [0b00111000, 0b00000100, 0b00011100, 0b00000100, 0b00111000],
72
+ 'x' => [0b00100100, 0b00011000, 0b00011000, 0b00100100],
73
+ 'y' => [0b00110000, 0b00001010, 0b00001010, 0b00111100],
74
+ 'z' => [0b00100100, 0b00101100, 0b00110100, 0b00100100],
75
+ '.' => [0b00001100, 0b00001100, 0b00000000],
76
+ ',' => [0b00001101, 0b00001110, 0b00000000],
77
+ ':' => [0b01101100, 0b01101100]
78
+ }
79
+
80
+ attr_accessor :color, :canvas
81
+
82
+ def initialize(width, height, bgcolor: [0xff, 0xff, 0xff, 0xff], color: [0, 0, 0, 0xff])
83
+ @canvas = []
84
+ @width = width
85
+ @height = height
86
+ @color = color
87
+ height.times { @canvas << [bgcolor] * width }
88
+ end
89
+
90
+ def point(x, y, color = nil)
91
+ return if x < 0 || y < 0 || x > (@width - 1) || y > (@height - 1)
92
+ color = @color if color.nil?
93
+ @canvas[y][x] = blend(@canvas[y][x], color)
94
+ end
95
+
96
+ def text(x0, y0, string, color = nil)
97
+ string = string.to_s
98
+ return if string.empty?
99
+ x = 0
100
+ string.each_char do |char|
101
+ x += character(x + x0, y0, char, color)
102
+ end
103
+ end
104
+
105
+ def vertical_gradient(x0, y0, x1, y1, from_color, to_color)
106
+ x0, y0, x1, y1 = rectangle_helper(x0, y0, x1, y1)
107
+ gradient = gradient_list(from_color, to_color, y1 - y0)
108
+ (x0..x1).each do |x|
109
+ (y0..y1).each do |y|
110
+ point(x, y, gradient[y - y0])
111
+ end
112
+ end
113
+ end
114
+
115
+ def rectangle(x0, y0, x1, y1)
116
+ x0, y0, x1, y1 = rectangle_helper(x0, y0, x1, y1)
117
+ polyline([[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]])
118
+ end
119
+
120
+ def filled_rectangle(x0, y0, x1, y1)
121
+ x0, y0, x1, y1 = rectangle_helper(x0, y0, x1, y1)
122
+ (x0..x1).each do |x|
123
+ (y0..y1).each do |y|
124
+ point(x, y, @color)
125
+ end
126
+ end
127
+ end
128
+
129
+ def copy_rectangle(x0, y0, x1, y1, dx, dy, destination_png)
130
+ x0, y0, x1, y1 = rectangle_helper(x0, y0, x1, y1)
131
+ (x0..x1).each do |x|
132
+ (y0..y1).each do |y|
133
+ destination_png.canvas[dy + y - y0][dx + x - x0] = @canvas[y][x]
134
+ end
135
+ end
136
+ end
137
+
138
+ def blend_rectangle(x0, y0, x1, y1, dx, dy, destination_png, alpha = 0xff)
139
+ x0, y0, x1, y1 = rectangle_helper(x0, y0, x1, y1)
140
+ (x0..x1).each do |x|
141
+ (y0..y1).each do |y|
142
+ rgba = @canvas[y][x] + [alpha]
143
+ destination_png.point(dx + x - x0, dy + y - y0, rgba)
144
+ end
145
+ end
146
+ end
147
+
148
+ # Draw a line using Xiaolin Wu's antialiasing technique
149
+ def line(x0, y0, x1, y1)
150
+ # clean params
151
+ x0, y0, x1, y1 = x0.to_i, y0.to_i, x1.to_i, y1.to_i
152
+ if y0 > y1
153
+ y0, y1, x0, x1 = y1, y0, x1, x0
154
+ end
155
+ dx = x1 - x0
156
+ if dx < 0
157
+ sx = -1
158
+ else
159
+ sx = 1
160
+ end
161
+ dx *= sx
162
+ dy = y1 - y0
163
+
164
+ # 'easy' cases
165
+ if dy == 0
166
+ if sx > 0
167
+ ordering = :each
168
+ else
169
+ ordering = :reverse_each
170
+ x0, x1 = x1, x0
171
+ end
172
+ (x0..x1).send(ordering) { |x| point(x, y0) }
173
+ return
174
+ end
175
+ if dx == 0
176
+ (y0..y1).each { |y| point(x0, y) }
177
+ point(x1, y1)
178
+ return
179
+ end
180
+ if dx == dy
181
+ if sx > 0
182
+ ordering = :each
183
+ else
184
+ ordering = :reverse_each
185
+ x0, x1 = x1, x0
186
+ end
187
+ (x0..x1).send(ordering) do |x|
188
+ point(x, y0)
189
+ y0 += 1
190
+ end
191
+ return
192
+ end
193
+
194
+ # main loop
195
+ point(x0, y0)
196
+ e_acc = 0
197
+ if dy > dx # vertical displacement
198
+ e = (dx << 16) / dy
199
+ (y0..y1).each do |i|
200
+ e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
201
+ if e_acc <= e_acc_temp
202
+ x0 += sx
203
+ end
204
+ w = 0xff - (e_acc >> 8)
205
+ point(x0, y0, intensity(@color, w))
206
+ y0 += 1
207
+ point(x0 + sx, y0, intensity(@color, 0xff - w))
208
+ end
209
+ point(x1, y1)
210
+ return
211
+ end
212
+
213
+ # horizontal displacement
214
+ e = (dy << 16) / dx
215
+ ordering = sx > 0 ? :each : :reverse_each
216
+ (x0..x1 - sx).send(ordering) do |i|
217
+ e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
218
+ if e_acc <= e_acc_temp
219
+ y0 += 1
220
+ end
221
+ w = 0xff - (e_acc >> 8)
222
+ point(x0, y0, intensity(@color, w))
223
+ x0 += sx
224
+ point(x0, y0 + 1, intensity(@color, 0xff - w))
225
+ end
226
+ point(x1, y1)
227
+ end
228
+
229
+ def polyline(point_array)
230
+ (point_array.size - 1).times do |i|
231
+ line(
232
+ point_array[i].first,
233
+ point_array[i].last,
234
+ point_array[i + 1].first,
235
+ point_array[i + 1].last
236
+ )
237
+ end
238
+ end
239
+
240
+ def dump
241
+ raw_list = []
242
+ @height.times do |y|
243
+ raw_list << 0.chr # filter type 0 (nil)
244
+ @width.times do |x|
245
+ raw_list << @canvas[y][x].pack('C3')
246
+ end
247
+ end
248
+ raw_data = raw_list.join
249
+
250
+ # 8-bit image represented as RGB tuples
251
+ # simple transparency, alpha is pure white
252
+ [137, 80, 78, 71, 13, 10, 26, 10].pack('C8') +
253
+ pack_chunk('IHDR', [@width, @height, 8, 2, 0, 0, 0].pack('N2C5')) +
254
+ pack_chunk('tRNS', [0xff, 0xff, 0xff, 0xff, 0xff, 0xff].pack('C6')) +
255
+ pack_chunk('IDAT', Zlib::Deflate.deflate(raw_data, 9)) +
256
+ pack_chunk('IEND', '')
257
+ end
258
+
259
+ def save(file_path)
260
+ file = File.open file_path, 'wb'
261
+ file.write dump
262
+ file.close
263
+ end
264
+
265
+ def load(file_path)
266
+ file = File.open file_path, 'rb'
267
+ @canvas = []
268
+
269
+ file.read(8) # load png header
270
+
271
+ chunks(file) do |tag, data|
272
+ if tag == 'IHDR'
273
+ width, height, bitdepth, colortype, compression, filter, interlace = data.unpack('N2C5')
274
+ @width = width
275
+ @height = height
276
+ if [bitdepth, colortype, compression, filter, interlace] != [8, 2, 0, 0, 0]
277
+ raise 'Unsupported PNG format'
278
+ end
279
+ # we ignore tRNS because we use pure white as alpha anyway
280
+ elsif tag == 'IDAT'
281
+ raw_data = Zlib::Inflate.inflate(data)
282
+ prev = nil
283
+ i = 0
284
+ @height.times do |y|
285
+ filtertype = raw_data[i].ord
286
+ i = i + 1
287
+ cur = raw_data[i..i + @width * 3].unpack 'C*'
288
+ rgb = defilter(cur, (y == 0 ? nil : prev), filtertype)
289
+ prev = cur
290
+ i = i + @width * 3
291
+ row = []
292
+ j = 0
293
+ @width.times do |x|
294
+ pixel = rgb[j..j + 3]
295
+ row << pixel
296
+ j = j + 3
297
+ end
298
+ @canvas << row
299
+ end
300
+ end
301
+ end
302
+ file.close
303
+ end
304
+
305
+ private
306
+
307
+ def pack_chunk(tag, data)
308
+ to_check = tag + data
309
+ [data.size].pack('N') + to_check + [Zlib.crc32(to_check)].pack('N')
310
+ end
311
+
312
+ def rectangle_helper(x0, y0, x1, y1)
313
+ x0, y0, x1, y1 = x0.to_i, y0.to_i, x1.to_i, y1.to_i
314
+ if x0 > x1
315
+ x0, x1 = x1, x0
316
+ end
317
+ if y0 > y1
318
+ y0, y1 = y1, y0
319
+ end
320
+ [x0, y0, x1, y1]
321
+ end
322
+
323
+ def defilter(cur, prev, filtertype, bpp = 3)
324
+ if filtertype == 0 # No filter
325
+ return cur
326
+ elsif filtertype == 1 # Sub
327
+ xp = 0
328
+ [bpp..cur.size].each do |xc|
329
+ cur[xc] = (cur[xc] + cur[xp]) % 256
330
+ xp += 1
331
+ end
332
+ elsif filtertype == 2 # Up
333
+ cur.size.times do |xc|
334
+ cur[xc] = (cur[xc] + prev[xc]) % 256
335
+ end
336
+ elsif filtertype == 3 # Average
337
+ xp = 0
338
+ cur.size.times do |xc|
339
+ cur[xc] = (cur[xc] + (cur[xp] + prev[xc]) / 2) % 256
340
+ xp += 1
341
+ end
342
+ elsif filtertype == 4 # Paeth
343
+ xp = 0
344
+ bpp.times do |i|
345
+ cur[i] = (cur[i] + prev[i]) % 256
346
+ end
347
+ [bpp..cur.size].each do |xc|
348
+ a = cur[xp]
349
+ b = prev[xc]
350
+ c = prev[xp]
351
+ p = a + b - c
352
+ pa = (p - a).abs
353
+ pb = (p - b).abs
354
+ pc = (p - c).abs
355
+ if pa <= pb && pa <= pc
356
+ value = a
357
+ elsif pb <= pc
358
+ value = b
359
+ else
360
+ value = c
361
+ end
362
+ cur[xc] = (cur[xc] + value) % 256
363
+ xp += 1
364
+ end
365
+ else
366
+ raise 'Unrecognized scanline filter type'
367
+ end
368
+ cur
369
+ end
370
+
371
+ def chunks(f)
372
+ until f.eof?
373
+ length = f.read(4).unpack("N")[0]
374
+ tag = f.read(4)
375
+ data = f.read(length)
376
+
377
+ crc = f.read(4).unpack("N")[0]
378
+ raise 'File is corrupted' if Zlib.crc32(tag + data) != crc
379
+
380
+ yield [tag, data]
381
+ end
382
+ end
383
+
384
+ # Alpha-blends two colors, using the alpha given by c2
385
+ def blend(c1, c2)
386
+ 3.times.map { |i| c1[i] * (0xff - c2[3]) + c2[i] * c2[3] >> 8 }
387
+ end
388
+
389
+ # Calculate a new alpha given a 0—0xff intensity
390
+ def intensity(c, i)
391
+ [c[0], c[1], c[2], (c[3] * i) >> 8]
392
+ end
393
+
394
+ # Calculate gradient colors
395
+ def gradient_list(from, to, steps)
396
+ delta = 4.times.map { |i| to[i] - from[i] }
397
+ grad = []
398
+ (steps + 1).times do |i|
399
+ grad << 4.times.map { |j| from[j] + delta[j] * i / steps }
400
+ end
401
+ grad
402
+ end
403
+
404
+ def character(x0, y0, char, color)
405
+ x = 0
406
+ if CHARSET[char]
407
+ CHARSET[char].each do |column|
408
+ 8.times do |y|
409
+ y_mask = 2**(y - 7).abs
410
+ point(x + x0, y + y0, color) if column & y_mask == y_mask
411
+ end
412
+ x += 1
413
+ end
414
+ else # any unknown character will appear as white space
415
+ return 4 # whitespace width
416
+ end
417
+ x + 1 # returns char width
418
+ end
419
+ end
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'png_canvas'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'png_canvas'
7
+ gem.version = PngCanvas::VERSION
8
+ gem.authors = ['Daniel Cruz Horts']
9
+ gem.summary = %q{A minimalist library to render PNG images using pure Ruby}
10
+ gem.homepage = 'https://github.com/dncrht/png_canvas'
11
+ gem.license = 'MIT'
12
+
13
+ gem.files = `git ls-files`.split($/)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_development_dependency 'rspec', '>= 3'
19
+ gem.add_development_dependency 'pry-byebug'
20
+ end
@@ -0,0 +1,119 @@
1
+ require 'base64'
2
+
3
+ class String
4
+ def strip_heredoc
5
+ indent = scan(/^[ \t]*(?=\S)/).min.size rescue 0
6
+ gsub(/^[ \t]{#{indent}}/, '')
7
+ end
8
+ end
9
+
10
+ describe PngCanvas do
11
+
12
+ it 'saves and loads a file successfully' do
13
+ begin
14
+ png = PngCanvas.new(4, 4)
15
+ png.line(0, 0, 4, 4)
16
+ saved_file = png.dump
17
+ png.save 'test.png'
18
+ png.load 'test.png'
19
+ loaded_file = png.dump
20
+
21
+ expect(saved_file).to eq loaded_file
22
+ ensure
23
+ FileUtils.rm 'test.png'
24
+ end
25
+ end
26
+
27
+ it 'draws a diagonal line' do
28
+ png = PngCanvas.new(4, 4)
29
+ png.line(0, 0, 4, 4)
30
+
31
+ expect(png.dump.size).to eq 100
32
+ expect(Base64.encode64 png.dump).to eq <<-base64.strip_heredoc
33
+ iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAABnRSTlP/////
34
+ //+evUsyAAAAGUlEQVR42mNgYGD4DwMgFpwPo8B8JDUMDACkuSPdNWcPMwAA
35
+ AABJRU5ErkJggg==
36
+ base64
37
+ end
38
+
39
+ it 'draws a red empty square' do
40
+ png = PngCanvas.new(128, 64)
41
+ png.color = [0xff, 0, 0, 0xff]
42
+ png.rectangle(0, 0, 127, 63)
43
+
44
+ expect(png.dump.size).to eq 193
45
+ expect(Base64.encode64 png.dump).to eq <<-base64.strip_heredoc
46
+ iVBORw0KGgoAAAANSUhEUgAAAIAAAABACAIAAABdtOgoAAAABnRSTlP/////
47
+ //+evUsyAAAAdklEQVR42u3RsREAMAwCMfYfOval9AQ0+oMJlJdYcf+jUgAA
48
+ AAAAAIAAABAAAAIAQAAACAAAAQAgAAAEAIAAABAAAAIAQAAACAAAAQAgAAAE
49
+ AIAAABAAAAIAQAAACAAAAQAgAAAEAIAAABAAAAIAQAAA6AJYcQuila7hrshi
50
+ HAAAAABJRU5ErkJggg==
51
+ base64
52
+ end
53
+
54
+ it 'copies and pastes a rectangle' do
55
+ png = PngCanvas.new(128, 64)
56
+ png.color = [0xff, 0, 0, 0xff]
57
+ png.rectangle(0, 0, 127, 63)
58
+ png.vertical_gradient(1, 1, 126, 62,[0xff, 0, 0, 0xff], [0x20, 0, 0xff, 0x80])
59
+ png.copy_rectangle(1, 1, 63, 31, 0, 32, png)
60
+ png.blend_rectangle(1, 1, 63, 31, 64, 0, png)
61
+
62
+ expect(png.dump.size).to eq 745
63
+ expect(Base64.encode64 png.dump).to eq <<-base64.strip_heredoc
64
+ iVBORw0KGgoAAAANSUhEUgAAAIAAAABACAIAAABdtOgoAAAABnRSTlP/////
65
+ //+evUsyAAACnklEQVR42u3ca0/OARjH8duhREXlUEM5FCkUU9JBmcVa1mI0
66
+ i7VYWBrN0miWlgd6wPa/y12KcppmqUYqRKNWE7JoNWFazaGtsrRVWKzGtV3v
67
+ wb5m+7yAa/s+/e0yjZtMaGNkf+430QOMTrDi0gA/J1pzfZtky6UBvk+24xqy
68
+ cuTSAMPWTlxfpzhzaYBBGxeu/qmuXBrgyzQ3rl5bdy4N0GfnwfXZ3otLA/RM
69
+ 9+b6MMOHSwN8dPDl6nL049IA3U7+XO9nBnJpgM5ZQVxvZodyaYC3c8K4XjuH
70
+ c2mADpdNXG1zI7k0QPu8LVyv5kdzaYBW161cLW47uDTAywUxXM0LY7k0wItF
71
+ u7ieLo7n0gDP3PdwPfHYx6UBmpbs52pYepBLAzR6JnHVLTvMpQHqvZK5Hnmn
72
+ cGmAx8uPcj1ccZxLA9SuTOOq8Unn0gAPfE9y3VuVyaUB7q8+xVW95jSXBrjr
73
+ l8VV6X+GSwNUrT3LVRFgcGmAO+vMXLcCLVwa4HZQLldZcD6XBigPKeAqXV/I
74
+ pQHKQov++yvw8/SbG66iSQD0Nr1kYzGaBEBv02+El6BJAPQ2vXhzOZoEQG/T
75
+ r0dUoEkA9Db9WmQ1mgRAb9OvRNWgSQD0Nv1ydC2aBEBv0y9tq0OTAOhteuH2
76
+ RjQJgN6mX4xpQpMA6G36hZ3P0SQAepteENuCJgHQ2/T83a1oEgC9TT8f144m
77
+ AdDb9Lz4DjQJgN6m5+59hyYB0Nt0S0InmgRAb9MtB7rRJAB6m34u8ROaBEBv
78
+ 03OSetAkAHqbnnOoF00CoLfp2cn9aBIAvU3PPjKAJgHQ23RzyiCaBEBv082p
79
+ Q2gSAL1NN46NoEkA9DbdSPuBJgHQ23TjxCiaBEBv0430X2gSAD3vNjLG0f6F
80
+ 7+l0vwER6/0xkn8cOwAAAABJRU5ErkJggg==
81
+ base64
82
+ end
83
+
84
+ it 'copies and pastes a rectangle wth text' do
85
+ png = PngCanvas.new(128, 64)
86
+ png.color = [0xff, 0, 0, 0xff]
87
+ png.rectangle(0, 0, 127, 63)
88
+ png.vertical_gradient(1, 1, 126, 62,[0xff, 0, 0, 0xff], [0x20, 0, 0xff, 0x80])
89
+ png.copy_rectangle(1, 1, 63, 31, 0, 32, png)
90
+ png.blend_rectangle(1, 1, 63, 31, 64, 0, png)
91
+ png.color = [0, 0, 0, 0xff]
92
+ png.text(0, 50, 'this is a 50,12')
93
+
94
+ expect(png.dump.size).to eq 919
95
+ expect(Base64.encode64 png.dump).to eq <<-base64.strip_heredoc
96
+ iVBORw0KGgoAAAANSUhEUgAAAIAAAABACAIAAABdtOgoAAAABnRSTlP/////
97
+ //+evUsyAAADTElEQVR42u3ce0yNcRzH8celXMqlXGool0oqFVMoxCxmmcVo
98
+ FrMYGS3NymgW4w/+YDtJUeQ6zaJGCNGUaXJbNE2Yprm0JaMtt8U057t+e3b2
99
+ nHOqzR+8TXut/Xp69pxn3uavz2htmob2k8z8/ho9QGs3By4V4Ht3R64vPZy4
100
+ VICvPZ25WhxcuFSAT46uXB97uXGpAM293bma+nhwqQDv+3pyNTp5cakA75y9
101
+ ud728+NSARr6+3O9GhDEpQK8HhjM9dIlhEsFqHcN5XoxKIxLBagbHM71dEgE
102
+ lwrwbOhMridukVwqQK37HK7Hw6K4VICa4fO5Ho2I5lIBqj0WclV5LuFSAR6O
103
+ jOG6PyqWSwV4MHoZ150xcVwqwF2vlVy3vddwqQCVPvFct8au51IBKnwTuMrH
104
+ beBSAW76JXHd8E/mUgHKAlK4ro/fwqUClAamcpUEpXGpANeCt3FdmbCDSwW4
105
+ OnEnV/GkXVwqwOWQ3VwXQ/dwqQCXJu/lKppi4lIBLkxN5zoXlsmlApwPz+Iq
106
+ mJbNpQIUTs/hOjsjl0sFKIg48t8fgZ+nn5l1Ak0CoLfp+bPz0CQAept+OjIf
107
+ TQKgt+l5cwvRJAB6m35qXhGaBEBv009GFaNJAPQ2/fiCEjQJgN6mH4suRZMA
108
+ 6G360UXlaBIAvU3PXVyBJgHQ2/TDMZVoEgC9TT+09B6aBEBv03Niq9AkAHqb
109
+ nr28Gk0CoLfpB1fUoEkA9Db9QFwtmgRAb9OzVj1HkwDobXrm6jo0CVDpE69Z
110
+ LNQ1q7W61rX9uvZ7M3fN4qvjBxre1vyVubYeSgJU+CZoFgt17Q+t1Q2fa/1i
111
+ +nX9ovmwf90bM/2A02b596597W3zR30Lbvit4Xqnt3X6BMvP1b9b36mfMxIa
112
+ zMxX2g84EqAsIEWzWKjrZ5sHzf6WvSu3WT/T3j3WL2b9ELOMxEb5009shJIA
113
+ pYGp1t9tHvQfNVuL9q7cpn+EvV/ZfBnD/YbzvqQmLgnQPvK2d9D/ZTCcDbp4
114
+ m+Vj7T3EcKfhus0PkgwbPxBJAMQG3d57pic3o0mAv3+A3sFLpm9qQZMA6G26
115
+ afNnNAmA3qabUr+hSQD0Nt20tRVNAqC36aa0H2gSAD3vNm1vQ/sX/vd0ul8l
116
+ RVvzUA6odwAAAABJRU5ErkJggg==
117
+ base64
118
+ end
119
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'pry-byebug'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ require 'png_canvas'
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: png_canvas
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Cruz Horts
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".gitignore"
48
+ - ".rspec"
49
+ - Gemfile
50
+ - MIT-LICENSE
51
+ - README.md
52
+ - lib/png_canvas.rb
53
+ - png_canvas.gemspec
54
+ - spec/png_canvas_spec.rb
55
+ - spec/spec_helper.rb
56
+ homepage: https://github.com/dncrht/png_canvas
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.2.2
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: A minimalist library to render PNG images using pure Ruby
80
+ test_files:
81
+ - spec/png_canvas_spec.rb
82
+ - spec/spec_helper.rb
83
+ has_rdoc: