chunky_png 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +3 -0
- data/lib/chunky_png/canvas.rb +99 -62
- data/lib/chunky_png/canvas/drawing.rb +91 -68
- data/lib/chunky_png/canvas/operations.rb +155 -120
- data/lib/chunky_png/canvas/stream_importing.rb +2 -2
- data/lib/chunky_png/chunk.rb +111 -93
- data/lib/chunky_png/color.rb +312 -253
- data/lib/chunky_png/datastream.rb +1 -1
- data/lib/chunky_png/palette.rb +66 -49
- data/lib/chunky_png/version.rb +1 -1
- data/spec/chunky_png/canvas/stream_importing_spec.rb +9 -0
- data/spec/chunky_png/color_spec.rb +92 -68
- data/spec/resources/pixelstream.bgr +67 -0
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -54,6 +54,9 @@ provides a massive speed boost to encoding and decoding.
|
|
54
54
|
png_stream = ChunkyPNG::Datastream.from_file('filename.png')
|
55
55
|
png_stream.each_chunk { |chunk| p chunk.type }
|
56
56
|
|
57
|
+
Also check out the following screencast by John Davison, which illustrates
|
58
|
+
basic usage of the library: http://devcasts.co/ruby-chunky_png-a-great-gem-for-on-the-fly-png-manipulation.
|
59
|
+
|
57
60
|
For more information, see the project wiki on http://github.com/wvanbergen/chunky_png/wiki
|
58
61
|
or the RDOC documentation on http://rdoc.info/gems/chunky_png/frames
|
59
62
|
|
data/lib/chunky_png/canvas.rb
CHANGED
@@ -11,7 +11,6 @@ require 'chunky_png/canvas/resampling'
|
|
11
11
|
require 'chunky_png/canvas/masking'
|
12
12
|
|
13
13
|
module ChunkyPNG
|
14
|
-
|
15
14
|
# The ChunkyPNG::Canvas class represents a raster image as a matrix of
|
16
15
|
# pixels.
|
17
16
|
#
|
@@ -32,7 +31,6 @@ module ChunkyPNG
|
|
32
31
|
# the whole canvas, like cropping and alpha compositing. Simple drawing
|
33
32
|
# functions are imported from the {ChunkyPNG::Canvas::Drawing} module.
|
34
33
|
class Canvas
|
35
|
-
|
36
34
|
include PNGEncoding
|
37
35
|
extend PNGDecoding
|
38
36
|
extend Adam7Interlacing
|
@@ -55,7 +53,7 @@ module ChunkyPNG
|
|
55
53
|
attr_reader :height
|
56
54
|
|
57
55
|
# @return [Array<ChunkyPNG::Color>] The list of pixels in this canvas.
|
58
|
-
#
|
56
|
+
# This array always should have +width * height+ elements.
|
59
57
|
attr_reader :pixels
|
60
58
|
|
61
59
|
|
@@ -68,26 +66,28 @@ module ChunkyPNG
|
|
68
66
|
# @overload initialize(width, height, background_color)
|
69
67
|
# @param [Integer] width The width in pixels of this canvas
|
70
68
|
# @param [Integer] height The height in pixels of this canvas
|
71
|
-
# @param [Integer, ...] background_color The initial background color of
|
72
|
-
#
|
69
|
+
# @param [Integer, ...] background_color The initial background color of
|
70
|
+
# this canvas. This can be a color value or any value that
|
71
|
+
# {ChunkyPNG::Color.parse} can handle.
|
73
72
|
#
|
74
73
|
# @overload initialize(width, height, initial)
|
75
74
|
# @param [Integer] width The width in pixels of this canvas
|
76
75
|
# @param [Integer] height The height in pixels of this canvas
|
77
|
-
# @param [Array<Integer>] initial The initial pizel values. Must be an
|
78
|
-
# <tt>width * height</tt> elements.
|
76
|
+
# @param [Array<Integer>] initial The initial pizel values. Must be an
|
77
|
+
# array with <tt>width * height</tt> elements.
|
79
78
|
def initialize(width, height, initial = ChunkyPNG::Color::TRANSPARENT)
|
80
|
-
|
81
79
|
@width, @height = width, height
|
82
80
|
|
83
81
|
if initial.kind_of?(Array)
|
84
|
-
|
82
|
+
unless initial.length == width * height
|
83
|
+
raise ArgumentError, "The initial array should have #{width}x#{height} = #{width*height} elements!"
|
84
|
+
end
|
85
85
|
@pixels = initial
|
86
86
|
else
|
87
87
|
@pixels = Array.new(width * height, ChunkyPNG::Color.parse(initial))
|
88
88
|
end
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
# Initializes a new Canvas instances when being cloned.
|
92
92
|
# @param [ChunkyPNG::Canvas] other The canvas to duplicate
|
93
93
|
# @return [void]
|
@@ -101,7 +101,7 @@ module ChunkyPNG
|
|
101
101
|
# @param [ChunkyPNG::Canvas] canvas The canvas to duplicate
|
102
102
|
# @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
|
103
103
|
def self.from_canvas(canvas)
|
104
|
-
|
104
|
+
new(canvas.width, canvas.height, canvas.pixels.dup)
|
105
105
|
end
|
106
106
|
|
107
107
|
|
@@ -110,11 +110,12 @@ module ChunkyPNG
|
|
110
110
|
#################################################################
|
111
111
|
|
112
112
|
# Returns the dimension (width x height) for this canvas.
|
113
|
-
# @return [ChunkyPNG::Dimension] A dimension instance with the width and
|
113
|
+
# @return [ChunkyPNG::Dimension] A dimension instance with the width and
|
114
|
+
# height set for this canvas.
|
114
115
|
def dimension
|
115
116
|
ChunkyPNG::Dimension.new(width, height)
|
116
117
|
end
|
117
|
-
|
118
|
+
|
118
119
|
# Returns the area of this canvas in number of pixels.
|
119
120
|
# @return [Integer] The number of pixels in this canvas
|
120
121
|
def area
|
@@ -125,8 +126,10 @@ module ChunkyPNG
|
|
125
126
|
# @param [Integer] x The x-coordinate of the pixel (column)
|
126
127
|
# @param [Integer] y The y-coordinate of the pixel (row)
|
127
128
|
# @param [Integer] color The new color for the provided coordinates.
|
128
|
-
# @return [Integer] The new color value for this pixel, i.e.
|
129
|
-
#
|
129
|
+
# @return [Integer] The new color value for this pixel, i.e.
|
130
|
+
# <tt>color</tt>.
|
131
|
+
# @raise [ChunkyPNG::OutOfBounds] when the coordinates are outside of the
|
132
|
+
# image's dimensions.
|
130
133
|
# @see #set_pixel
|
131
134
|
def []=(x, y, color)
|
132
135
|
assert_xy!(x, y)
|
@@ -135,25 +138,26 @@ module ChunkyPNG
|
|
135
138
|
|
136
139
|
# Replaces a single pixel in this canvas, without bounds checking.
|
137
140
|
#
|
138
|
-
# This method return value and effects are undefined for coordinates
|
141
|
+
# This method return value and effects are undefined for coordinates
|
139
142
|
# out of bounds of the canvas.
|
140
143
|
#
|
141
144
|
# @param [Integer] x The x-coordinate of the pixel (column)
|
142
145
|
# @param [Integer] y The y-coordinate of the pixel (row)
|
143
146
|
# @param [Integer] pixel The new color for the provided coordinates.
|
144
|
-
# @return [Integer] The new color value for this pixel, i.e.
|
147
|
+
# @return [Integer] The new color value for this pixel, i.e.
|
148
|
+
# <tt>color</tt>.
|
145
149
|
def set_pixel(x, y, color)
|
146
150
|
@pixels[y * width + x] = color
|
147
151
|
end
|
148
|
-
|
152
|
+
|
149
153
|
# Replaces a single pixel in this canvas, with bounds checking. It will do
|
150
154
|
# noting if the provided coordinates are out of bounds.
|
151
155
|
#
|
152
156
|
# @param [Integer] x The x-coordinate of the pixel (column)
|
153
157
|
# @param [Integer] y The y-coordinate of the pixel (row)
|
154
158
|
# @param [Integer] pixel The new color value for the provided coordinates.
|
155
|
-
# @return [Integer] The new color value for this pixel, i.e.
|
156
|
-
#
|
159
|
+
# @return [Integer] The new color value for this pixel, i.e.
|
160
|
+
# <tt>color</tt>, or <tt>nil</tt> if the coordinates are out of bounds.
|
157
161
|
def set_pixel_if_within_bounds(x, y, color)
|
158
162
|
return unless include_xy?(x, y)
|
159
163
|
@pixels[y * width + x] = color
|
@@ -163,15 +167,18 @@ module ChunkyPNG
|
|
163
167
|
# @param [Integer] x The x-coordinate of the pixel (column)
|
164
168
|
# @param [Integer] y The y-coordinate of the pixel (row)
|
165
169
|
# @return [Integer] The current color value at the provided coordinates.
|
166
|
-
# @raise [ChunkyPNG::OutOfBounds] when the coordinates are outside of the
|
170
|
+
# @raise [ChunkyPNG::OutOfBounds] when the coordinates are outside of the
|
171
|
+
# image's dimensions.
|
167
172
|
# @see #get_pixel
|
168
173
|
def [](x, y)
|
169
174
|
assert_xy!(x, y)
|
170
175
|
@pixels[y * width + x]
|
171
176
|
end
|
172
177
|
|
173
|
-
# Returns a single pixel from this canvas, without checking bounds. The
|
174
|
-
# this method is undefined if the coordinates are out of
|
178
|
+
# Returns a single pixel from this canvas, without checking bounds. The
|
179
|
+
# return value for this method is undefined if the coordinates are out of
|
180
|
+
# bounds.
|
181
|
+
#
|
175
182
|
# @param (see #[])
|
176
183
|
# @return [Integer] The current pixel at the provided coordinates.
|
177
184
|
def get_pixel(x, y)
|
@@ -196,7 +203,8 @@ module ChunkyPNG
|
|
196
203
|
|
197
204
|
# Replaces a row of pixels on this canvas.
|
198
205
|
# @param [Integer] y The 0-based row index.
|
199
|
-
# @param [Array<Integer>] vector The vector of pixels to replace the row
|
206
|
+
# @param [Array<Integer>] vector The vector of pixels to replace the row
|
207
|
+
# with.
|
200
208
|
# @return [void]
|
201
209
|
def replace_row!(y, vector)
|
202
210
|
assert_y!(y) && assert_width!(vector.length)
|
@@ -205,7 +213,8 @@ module ChunkyPNG
|
|
205
213
|
|
206
214
|
# Replaces a column of pixels on this canvas.
|
207
215
|
# @param [Integer] x The 0-based column index.
|
208
|
-
# @param [Array<Integer>] vector The vector of pixels to replace the column
|
216
|
+
# @param [Array<Integer>] vector The vector of pixels to replace the column
|
217
|
+
# with.
|
209
218
|
# @return [void]
|
210
219
|
def replace_column!(x, vector)
|
211
220
|
assert_x!(x) && assert_height!(vector.length)
|
@@ -215,49 +224,55 @@ module ChunkyPNG
|
|
215
224
|
end
|
216
225
|
|
217
226
|
# Checks whether the given coordinates are in the range of the canvas
|
218
|
-
# @param [ChunkyPNG::Point, Array, Hash, String] point_like The point to
|
219
|
-
#
|
220
|
-
#
|
227
|
+
# @param [ChunkyPNG::Point, Array, Hash, String] point_like The point to
|
228
|
+
# check.
|
229
|
+
# @return [true, false] True if the x and y coordinates of the point are
|
230
|
+
# within the limits of this canvas.
|
221
231
|
# @see ChunkyPNG.Point
|
222
232
|
def include_point?(*point_like)
|
223
233
|
dimension.include?(ChunkyPNG::Point(*point_like))
|
224
234
|
end
|
225
|
-
|
235
|
+
|
226
236
|
alias_method :include?, :include_point?
|
227
|
-
|
228
|
-
# Checks whether the given x- and y-coordinate are in the range of the
|
237
|
+
|
238
|
+
# Checks whether the given x- and y-coordinate are in the range of the
|
239
|
+
# canvas
|
240
|
+
#
|
229
241
|
# @param [Integer] x The x-coordinate of the pixel (column)
|
230
242
|
# @param [Integer] y The y-coordinate of the pixel (row)
|
231
|
-
# @return [true, false] True if the x- and y-coordinate is in the range of
|
243
|
+
# @return [true, false] True if the x- and y-coordinate is in the range of
|
244
|
+
# this canvas.
|
232
245
|
def include_xy?(x, y)
|
233
246
|
y >= 0 && y < height && x >= 0 && x < width
|
234
247
|
end
|
235
|
-
|
248
|
+
|
236
249
|
# Checks whether the given y-coordinate is in the range of the canvas
|
237
250
|
# @param [Integer] y The y-coordinate of the pixel (row)
|
238
|
-
# @return [true, false] True if the y-coordinate is in the range of this
|
251
|
+
# @return [true, false] True if the y-coordinate is in the range of this
|
252
|
+
# canvas.
|
239
253
|
def include_y?(y)
|
240
254
|
y >= 0 && y < height
|
241
255
|
end
|
242
256
|
|
243
257
|
# Checks whether the given x-coordinate is in the range of the canvas
|
244
258
|
# @param [Integer] x The y-coordinate of the pixel (column)
|
245
|
-
# @return [true, false] True if the x-coordinate is in the range of this
|
259
|
+
# @return [true, false] True if the x-coordinate is in the range of this
|
260
|
+
# canvas.
|
246
261
|
def include_x?(x)
|
247
262
|
x >= 0 && x < width
|
248
263
|
end
|
249
264
|
|
250
265
|
# Returns the palette used for this canvas.
|
251
|
-
# @return [ChunkyPNG::Palette] A palette which contains all the colors that
|
252
|
-
#
|
266
|
+
# @return [ChunkyPNG::Palette] A palette which contains all the colors that
|
267
|
+
# are being used for this image.
|
253
268
|
def palette
|
254
269
|
ChunkyPNG::Palette.from_canvas(self)
|
255
270
|
end
|
256
271
|
|
257
272
|
# Equality check to compare this canvas with other matrices.
|
258
273
|
# @param other The object to compare this Matrix to.
|
259
|
-
# @return [true, false] True if the size and pixel values of the other
|
260
|
-
#
|
274
|
+
# @return [true, false] True if the size and pixel values of the other
|
275
|
+
# canvas are exactly the same as this canvas's size and pixel values.
|
261
276
|
def eql?(other)
|
262
277
|
other.kind_of?(self.class) && other.pixels == self.pixels &&
|
263
278
|
other.width == self.width && other.height == self.height
|
@@ -274,7 +289,7 @@ module ChunkyPNG
|
|
274
289
|
def to_image
|
275
290
|
ChunkyPNG::Image.from_canvas(self)
|
276
291
|
end
|
277
|
-
|
292
|
+
|
278
293
|
# Alternative implementation of the inspect method.
|
279
294
|
# @return [String] A nicely formatted string representation of this canvas.
|
280
295
|
# @private
|
@@ -285,51 +300,73 @@ module ChunkyPNG
|
|
285
300
|
end
|
286
301
|
inspected << "\n]>"
|
287
302
|
end
|
288
|
-
|
303
|
+
|
289
304
|
protected
|
290
|
-
|
305
|
+
|
291
306
|
# Replaces the image, given a new width, new height, and a new pixel array.
|
292
307
|
def replace_canvas!(new_width, new_height, new_pixels)
|
293
|
-
|
308
|
+
unless new_pixels.length == new_width * new_height
|
309
|
+
raise ArgumentError, "The provided pixel array should have #{new_width * new_height} items"
|
310
|
+
end
|
294
311
|
@width, @height, @pixels = new_width, new_height, new_pixels
|
295
|
-
|
312
|
+
self
|
296
313
|
end
|
297
|
-
|
314
|
+
|
298
315
|
# Throws an exception if the x-coordinate is out of bounds.
|
299
316
|
def assert_x!(x)
|
300
|
-
|
301
|
-
|
317
|
+
unless include_x?(x)
|
318
|
+
raise ChunkyPNG::OutOfBounds, "Column index #{x} out of bounds!"
|
319
|
+
end
|
320
|
+
true
|
302
321
|
end
|
303
|
-
|
322
|
+
|
304
323
|
# Throws an exception if the y-coordinate is out of bounds.
|
305
324
|
def assert_y!(y)
|
306
|
-
|
307
|
-
|
325
|
+
unless include_y?(y)
|
326
|
+
raise ChunkyPNG::OutOfBounds, "Row index #{y} out of bounds!"
|
327
|
+
end
|
328
|
+
true
|
308
329
|
end
|
309
|
-
|
330
|
+
|
310
331
|
# Throws an exception if the x- or y-coordinate is out of bounds.
|
311
332
|
def assert_xy!(x, y)
|
312
|
-
|
313
|
-
|
333
|
+
unless include_xy?(x, y)
|
334
|
+
raise ChunkyPNG::OutOfBounds, "Coordinates (#{x},#{y}) out of bounds!"
|
335
|
+
end
|
336
|
+
true
|
314
337
|
end
|
315
338
|
|
316
|
-
# Throws an exception if the vector_length does not match this canvas'
|
339
|
+
# Throws an exception if the vector_length does not match this canvas'
|
340
|
+
# height.
|
317
341
|
def assert_height!(vector_length)
|
318
|
-
|
319
|
-
|
342
|
+
if height != vector_length
|
343
|
+
raise ChunkyPNG::ExpectationFailed,
|
344
|
+
"The length of the vector (#{vector_length}) does not match the canvas height (#{height})!"
|
345
|
+
end
|
346
|
+
true
|
320
347
|
end
|
321
348
|
|
322
|
-
# Throws an exception if the vector_length does not match this canvas'
|
349
|
+
# Throws an exception if the vector_length does not match this canvas'
|
350
|
+
# width.
|
323
351
|
def assert_width!(vector_length)
|
324
|
-
|
325
|
-
|
352
|
+
if width != vector_length
|
353
|
+
raise ChunkyPNG::ExpectationFailed,
|
354
|
+
"The length of the vector (#{vector_length}) does not match the canvas width (#{width})!"
|
355
|
+
end
|
356
|
+
true
|
326
357
|
end
|
327
358
|
|
328
359
|
# Throws an exception if the matrix width and height does not match this canvas' dimensions.
|
329
360
|
def assert_size!(matrix_width, matrix_height)
|
330
|
-
|
331
|
-
|
332
|
-
|
361
|
+
if width != matrix_width
|
362
|
+
raise ChunkyPNG::ExpectationFailed,
|
363
|
+
'The width of the matrix does not match the canvas width!'
|
364
|
+
end
|
365
|
+
if height != matrix_height
|
366
|
+
raise ChunkyPNG::ExpectationFailed,
|
367
|
+
'The height of the matrix does not match the canvas height!'
|
368
|
+
end
|
369
|
+
true
|
333
370
|
end
|
334
371
|
end
|
335
372
|
end
|
@@ -1,17 +1,19 @@
|
|
1
1
|
module ChunkyPNG
|
2
2
|
class Canvas
|
3
|
-
|
3
|
+
|
4
4
|
# Module that adds some primitive drawing methods to {ChunkyPNG::Canvas}.
|
5
5
|
#
|
6
|
-
# All of these methods change the current canvas instance and do not create
|
7
|
-
# even though the method names do not end with a bang.
|
6
|
+
# All of these methods change the current canvas instance and do not create
|
7
|
+
# a new one, even though the method names do not end with a bang.
|
8
8
|
#
|
9
|
-
# @note Drawing operations will not fail when something is drawn outside of
|
10
|
-
#
|
9
|
+
# @note Drawing operations will not fail when something is drawn outside of
|
10
|
+
# the bounds of the canvas; these pixels will simply be ignored.
|
11
11
|
# @see ChunkyPNG::Canvas
|
12
12
|
module Drawing
|
13
|
-
|
14
|
-
# Composes a pixel on the canvas by alpha blending a color with its
|
13
|
+
|
14
|
+
# Composes a pixel on the canvas by alpha blending a color with its
|
15
|
+
# background color.
|
16
|
+
#
|
15
17
|
# @param [Integer] x The x-coordinate of the pixel to blend.
|
16
18
|
# @param [Integer] y The y-coordinate of the pixel to blend.
|
17
19
|
# @param [Integer] color The foreground color to blend with
|
@@ -20,49 +22,49 @@ module ChunkyPNG
|
|
20
22
|
return unless include_xy?(x, y)
|
21
23
|
compose_pixel_unsafe(x, y, ChunkyPNG::Color.parse(color))
|
22
24
|
end
|
23
|
-
|
24
|
-
# Composes a pixel on the canvas by alpha blending a color with its
|
25
|
-
# without bounds checking.
|
25
|
+
|
26
|
+
# Composes a pixel on the canvas by alpha blending a color with its
|
27
|
+
# background color, without bounds checking.
|
28
|
+
#
|
26
29
|
# @param (see #compose_pixel)
|
27
30
|
# @return [Integer] The composed color.
|
28
31
|
def compose_pixel_unsafe(x, y, color)
|
29
32
|
set_pixel(x, y, ChunkyPNG::Color.compose(color, get_pixel(x, y)))
|
30
33
|
end
|
31
|
-
|
34
|
+
|
32
35
|
# Draws a Bezier curve
|
33
36
|
# @param [Array, Point] A collection of control points
|
34
37
|
# @return [Chunky:PNG::Canvas] Itself, with the curve drawn
|
35
38
|
def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)
|
36
|
-
|
37
39
|
points = ChunkyPNG::Vector(*points)
|
38
40
|
case points.length
|
39
41
|
when 0, 1; return self
|
40
42
|
when 2; return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color)
|
41
43
|
end
|
42
|
-
|
44
|
+
|
43
45
|
curve_points = Array.new
|
44
|
-
|
45
|
-
t
|
46
|
-
n
|
46
|
+
|
47
|
+
t = 0
|
48
|
+
n = points.length - 1
|
47
49
|
bicof = 0
|
48
|
-
|
50
|
+
|
49
51
|
while t <= 100
|
50
52
|
cur_p = ChunkyPNG::Point.new(0,0)
|
51
|
-
|
53
|
+
|
52
54
|
# Generate a float of t.
|
53
55
|
t_f = t / 100.00
|
54
|
-
|
56
|
+
|
55
57
|
cur_p.x += ((1 - t_f) ** n) * points[0].x
|
56
58
|
cur_p.y += ((1 - t_f) ** n) * points[0].y
|
57
|
-
|
59
|
+
|
58
60
|
for i in 1...points.length - 1
|
59
61
|
bicof = binomial_coefficient(n , i)
|
60
|
-
|
61
|
-
cur_p.x += (bicof * (1 - t_f) ** (n - i)) * (t_f ** i) * points[i].x
|
62
|
-
cur_p.y += (bicof * (1 - t_f) ** (n - i)) * (t_f ** i) * points[i].y
|
62
|
+
|
63
|
+
cur_p.x += (bicof * (1 - t_f) ** (n - i)) * (t_f ** i) * points[i].x
|
64
|
+
cur_p.y += (bicof * (1 - t_f) ** (n - i)) * (t_f ** i) * points[i].y
|
63
65
|
i += 1
|
64
66
|
end
|
65
|
-
|
67
|
+
|
66
68
|
cur_p.x += (t_f ** n) * points[n].x
|
67
69
|
cur_p.y += (t_f ** n) * points[n].y
|
68
70
|
|
@@ -76,10 +78,9 @@ module ChunkyPNG
|
|
76
78
|
line_xiaolin_wu(p1.x.round, p1.y.round, p2.x.round, p2.y.round, stroke_color)
|
77
79
|
end
|
78
80
|
|
79
|
-
|
81
|
+
self
|
80
82
|
end
|
81
|
-
|
82
|
-
|
83
|
+
|
83
84
|
# Draws an anti-aliased line using Xiaolin Wu's algorithm.
|
84
85
|
#
|
85
86
|
# @param [Integer] x0 The x-coordinate of the first control point.
|
@@ -87,81 +88,93 @@ module ChunkyPNG
|
|
87
88
|
# @param [Integer] x1 The x-coordinate of the second control point.
|
88
89
|
# @param [Integer] y1 The y-coordinate of the second control point.
|
89
90
|
# @param [Integer] stroke_color The color to use for this line.
|
90
|
-
# @param [true, false] inclusive Whether to draw the last pixel.
|
91
|
-
#
|
91
|
+
# @param [true, false] inclusive Whether to draw the last pixel. Set to
|
92
|
+
# false when drawing multiple lines in a path.
|
92
93
|
# @return [ChunkyPNG::Canvas] Itself, with the line drawn.
|
93
94
|
def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true)
|
94
|
-
|
95
95
|
stroke_color = ChunkyPNG::Color.parse(stroke_color)
|
96
|
-
|
96
|
+
|
97
97
|
dx = x1 - x0
|
98
98
|
sx = dx < 0 ? -1 : 1
|
99
99
|
dx *= sx
|
100
100
|
dy = y1 - y0
|
101
101
|
sy = dy < 0 ? -1 : 1
|
102
102
|
dy *= sy
|
103
|
-
|
103
|
+
|
104
104
|
if dy == 0 # vertical line
|
105
105
|
x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
|
106
106
|
compose_pixel(x, y0, stroke_color)
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
elsif dx == 0 # horizontal line
|
110
110
|
y0.step(inclusive ? y1 : y1 - sy, sy) do |y|
|
111
111
|
compose_pixel(x0, y, stroke_color)
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
elsif dx == dy # diagonal
|
115
115
|
x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
|
116
116
|
compose_pixel(x, y0, stroke_color)
|
117
117
|
y0 += sy
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
elsif dy > dx # vertical displacement
|
121
121
|
compose_pixel(x0, y0, stroke_color)
|
122
122
|
e_acc = 0
|
123
123
|
e = ((dx << 16) / dy.to_f).round
|
124
124
|
(dy - 1).downto(0) do |i|
|
125
125
|
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
|
126
|
-
x0 += sx if
|
126
|
+
x0 += sx if e_acc <= e_acc_temp
|
127
127
|
w = 0xff - (e_acc >> 8)
|
128
128
|
compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
|
129
|
-
|
129
|
+
if inclusive || i > 0
|
130
|
+
compose_pixel(x0 + sx,
|
131
|
+
y0 + sy,
|
132
|
+
ChunkyPNG::Color.fade(stroke_color, 0xff - w))
|
133
|
+
end
|
130
134
|
y0 += sy
|
131
135
|
end
|
132
136
|
compose_pixel(x1, y1, stroke_color) if inclusive
|
133
|
-
|
137
|
+
|
134
138
|
else # horizontal displacement
|
135
139
|
compose_pixel(x0, y0, stroke_color)
|
136
140
|
e_acc = 0
|
137
141
|
e = ((dy << 16) / dx.to_f).round
|
138
142
|
(dx - 1).downto(0) do |i|
|
139
143
|
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
|
140
|
-
y0 += sy if
|
144
|
+
y0 += sy if e_acc <= e_acc_temp
|
141
145
|
w = 0xff - (e_acc >> 8)
|
142
146
|
compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
|
143
|
-
|
147
|
+
if inclusive || i > 0
|
148
|
+
compose_pixel(x0 + sx,
|
149
|
+
y0 + sy,
|
150
|
+
ChunkyPNG::Color.fade(stroke_color, 0xff - w))
|
151
|
+
end
|
144
152
|
x0 += sx
|
145
153
|
end
|
146
154
|
compose_pixel(x1, y1, stroke_color) if inclusive
|
147
155
|
end
|
148
|
-
|
149
|
-
|
156
|
+
|
157
|
+
self
|
150
158
|
end
|
151
|
-
|
159
|
+
|
152
160
|
alias_method :line, :line_xiaolin_wu
|
153
|
-
|
154
|
-
|
155
|
-
#
|
161
|
+
|
162
|
+
# Draws a polygon on the canvas using the stroke_color, filled using the
|
163
|
+
# fill_color if any.
|
156
164
|
#
|
157
|
-
# @param [Array, String] The control point vector. Accepts everything
|
165
|
+
# @param [Array, String] The control point vector. Accepts everything
|
166
|
+
# {ChunkyPNG.Vector} accepts.
|
158
167
|
# @param [Integer] stroke_color The stroke color to use for this polygon.
|
159
168
|
# @param [Integer] fill_color The fill color to use for this polygon.
|
160
169
|
# @return [ChunkyPNG::Canvas] Itself, with the polygon drawn.
|
161
|
-
def polygon(path,
|
162
|
-
|
170
|
+
def polygon(path,
|
171
|
+
stroke_color = ChunkyPNG::Color::BLACK,
|
172
|
+
fill_color = ChunkyPNG::Color::TRANSPARENT)
|
173
|
+
|
163
174
|
vector = ChunkyPNG::Vector(*path)
|
164
|
-
|
175
|
+
if path.length < 3
|
176
|
+
raise ArgumentError, 'A polygon requires at least 3 points'
|
177
|
+
end
|
165
178
|
|
166
179
|
stroke_color = ChunkyPNG::Color.parse(stroke_color)
|
167
180
|
fill_color = ChunkyPNG::Color.parse(fill_color)
|
@@ -184,15 +197,15 @@ module ChunkyPNG
|
|
184
197
|
end
|
185
198
|
end
|
186
199
|
end
|
187
|
-
|
200
|
+
|
188
201
|
# Stroke
|
189
202
|
vector.each_edge do |(from_x, from_y), (to_x, to_y)|
|
190
203
|
line(from_x, from_y, to_x, to_y, stroke_color, false)
|
191
204
|
end
|
192
205
|
|
193
|
-
|
206
|
+
self
|
194
207
|
end
|
195
|
-
|
208
|
+
|
196
209
|
# Draws a rectangle on the canvas, using two control points.
|
197
210
|
#
|
198
211
|
# @param [Integer] x0 The x-coordinate of the first control point.
|
@@ -202,11 +215,13 @@ module ChunkyPNG
|
|
202
215
|
# @param [Integer] stroke_color The line color to use for this rectangle.
|
203
216
|
# @param [Integer] fill_color The fill color to use for this rectangle.
|
204
217
|
# @return [ChunkyPNG::Canvas] Itself, with the rectangle drawn.
|
205
|
-
def rect(x0, y0, x1, y1,
|
206
|
-
|
218
|
+
def rect(x0, y0, x1, y1,
|
219
|
+
stroke_color = ChunkyPNG::Color::BLACK,
|
220
|
+
fill_color = ChunkyPNG::Color::TRANSPARENT)
|
221
|
+
|
207
222
|
stroke_color = ChunkyPNG::Color.parse(stroke_color)
|
208
223
|
fill_color = ChunkyPNG::Color.parse(fill_color)
|
209
|
-
|
224
|
+
|
210
225
|
# Fill
|
211
226
|
unless fill_color == ChunkyPNG::Color::TRANSPARENT
|
212
227
|
[x0, x1].min.upto([x0, x1].max) do |x|
|
@@ -215,16 +230,16 @@ module ChunkyPNG
|
|
215
230
|
end
|
216
231
|
end
|
217
232
|
end
|
218
|
-
|
233
|
+
|
219
234
|
# Stroke
|
220
235
|
line(x0, y0, x0, y1, stroke_color, false)
|
221
236
|
line(x0, y1, x1, y1, stroke_color, false)
|
222
237
|
line(x1, y1, x1, y0, stroke_color, false)
|
223
238
|
line(x1, y0, x0, y0, stroke_color, false)
|
224
|
-
|
225
|
-
|
239
|
+
|
240
|
+
self
|
226
241
|
end
|
227
|
-
|
242
|
+
|
228
243
|
# Draws a circle on the canvas.
|
229
244
|
#
|
230
245
|
# @param [Integer] x0 The x-coordinate of the center of the circle.
|
@@ -233,7 +248,9 @@ module ChunkyPNG
|
|
233
248
|
# @param [Integer] stroke_color The color to use for the line.
|
234
249
|
# @param [Integer] fill_color The color to use that fills the circle.
|
235
250
|
# @return [ChunkyPNG::Canvas] Itself, with the circle drawn.
|
236
|
-
def circle(x0, y0, radius,
|
251
|
+
def circle(x0, y0, radius,
|
252
|
+
stroke_color = ChunkyPNG::Color::BLACK,
|
253
|
+
fill_color = ChunkyPNG::Color::TRANSPARENT)
|
237
254
|
|
238
255
|
stroke_color = ChunkyPNG::Color.parse(stroke_color)
|
239
256
|
fill_color = ChunkyPNG::Color.parse(fill_color)
|
@@ -283,20 +300,26 @@ module ChunkyPNG
|
|
283
300
|
|
284
301
|
unless fill_color == ChunkyPNG::Color::TRANSPARENT
|
285
302
|
lines.each_with_index do |length, y|
|
286
|
-
|
287
|
-
|
303
|
+
if length > 0
|
304
|
+
line(x0 - length, y0 - y, x0 + length, y0 - y, fill_color)
|
305
|
+
end
|
306
|
+
if length > 0 && y > 0
|
307
|
+
line(x0 - length, y0 + y, x0 + length, y0 + y, fill_color)
|
308
|
+
end
|
288
309
|
end
|
289
310
|
end
|
290
311
|
|
291
|
-
|
312
|
+
self
|
292
313
|
end
|
293
|
-
|
314
|
+
|
294
315
|
private
|
295
|
-
|
316
|
+
|
296
317
|
# Calculates the binomial coefficient for n over k.
|
297
318
|
#
|
298
|
-
# @param [Integer] n first parameter in coeffient (the number on top when
|
299
|
-
#
|
319
|
+
# @param [Integer] n first parameter in coeffient (the number on top when
|
320
|
+
# looking at the mathematic formula)
|
321
|
+
# @param [Integer] k k-element, second parameter in coeffient (the number
|
322
|
+
# on the bottom when looking at the mathematic formula)
|
300
323
|
# @return [Integer] The binomial coeffcient of (n,k)
|
301
324
|
def binomial_coefficient(n, k)
|
302
325
|
return 1 if n == k || k == 0
|