png_canvas 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: