chunky_png 1.0.0.beta2 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.infinity_test +1 -1
- data/README.rdoc +2 -2
- data/chunky_png.gemspec +4 -4
- data/lib/chunky_png.rb +14 -8
- data/lib/chunky_png/canvas.rb +110 -38
- data/lib/chunky_png/canvas/drawing.rb +175 -34
- data/lib/chunky_png/canvas/masking.rb +91 -0
- data/lib/chunky_png/canvas/operations.rb +141 -112
- data/lib/chunky_png/canvas/png_decoding.rb +13 -13
- data/lib/chunky_png/canvas/resampling.rb +42 -0
- data/lib/chunky_png/color.rb +252 -11
- data/lib/chunky_png/compatibility.rb +5 -5
- data/lib/chunky_png/dimension.rb +106 -0
- data/lib/chunky_png/point.rb +110 -0
- data/lib/chunky_png/vector.rb +92 -0
- data/spec/chunky_png/canvas/drawing_spec.rb +107 -14
- data/spec/chunky_png/canvas/masking_spec.rb +51 -0
- data/spec/chunky_png/canvas/operations_spec.rb +142 -75
- data/spec/chunky_png/canvas/resampling_spec.rb +31 -0
- data/spec/chunky_png/canvas/stream_exporting_spec.rb +20 -0
- data/spec/chunky_png/canvas/stream_importing_spec.rb +22 -0
- data/spec/chunky_png/canvas_spec.rb +151 -22
- data/spec/chunky_png/color_spec.rb +53 -0
- data/spec/chunky_png/dimension_spec.rb +43 -0
- data/spec/chunky_png/point_spec.rb +71 -0
- data/spec/chunky_png/vector_spec.rb +58 -0
- data/spec/resources/circles.png +0 -0
- data/spec/resources/clock_nn_xdown_ydown.png +0 -0
- data/spec/resources/clock_nn_xdown_yup.png +0 -0
- data/spec/resources/clock_nn_xup_yup.png +0 -0
- data/spec/resources/lines.png +0 -0
- data/spec/resources/partial_circles.png +0 -0
- data/spec/resources/polygon_filled_horizontal.png +0 -0
- data/spec/resources/polygon_filled_vertical.png +0 -0
- data/spec/resources/polygon_triangle_filled.png +0 -0
- data/spec/resources/polygon_unfilled.png +0 -0
- data/spec/resources/rect.png +0 -0
- metadata +31 -9
- data/spec/resources/clock_flip_horizontally.png +0 -0
- data/spec/resources/clock_flip_vertically.png +0 -0
- data/spec/resources/clock_rotate_180.png +0 -0
- data/spec/resources/clock_rotate_left.png +0 -0
- data/spec/resources/clock_rotate_right.png +0 -0
- data/spec/resources/clock_stubbed.png +0 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
# Factory method to create {ChunkyPNG::Point} instances.
|
4
|
+
#
|
5
|
+
# This method tries to be as flexible as possible with regards to the given input: besides
|
6
|
+
# explit coordinates, this method also accepts arrays, hashes, strings, {ChunkyPNG::Dimension}
|
7
|
+
# instances and anything that responds to <tt>:x</tt> and <tt>:y</tt>.
|
8
|
+
#
|
9
|
+
# @overload Point(x, y)
|
10
|
+
# @param [Integer, :to_i] x The x-coordinate
|
11
|
+
# @param [Integer, :to_i] y The y-coordinate
|
12
|
+
# @return [ChunkyPNG::Point] The instantiated point.
|
13
|
+
#
|
14
|
+
# @overload Point(array)
|
15
|
+
# @param [Array<Integer>] array A two element array which represent the x- and y-coordinate.
|
16
|
+
# @return [ChunkyPNG::Point] The instantiated point.
|
17
|
+
#
|
18
|
+
# @overload Point(hash)
|
19
|
+
# @param [Hash] array A hash with the <tt>:x</tt> or <tt>'x'</tt> and <tt>:y</tt> or
|
20
|
+
# <tt>'y'</tt> keys set, which will be used as coordinates.
|
21
|
+
# @return [ChunkyPNG::Point] The instantiated point.
|
22
|
+
#
|
23
|
+
# @overload Point(string)
|
24
|
+
# @param [String] string A string that contains the coordinates, e.g. <tt>'0, 4'</tt>,
|
25
|
+
# <tt>'(0 4)'</tt>, <tt>[0,4}'</tt>, etc.
|
26
|
+
# @return [ChunkyPNG::Point] The instantiated point.
|
27
|
+
#
|
28
|
+
# @raise [ChunkyPNG::ExpectationFailed] if the arguments weren't understood.
|
29
|
+
# @see ChunkyPNG::Point
|
30
|
+
def self.Point(*args)
|
31
|
+
case args.length
|
32
|
+
when 2; ChunkyPNG::Point.new(*args)
|
33
|
+
when 1; case source = args.first
|
34
|
+
when ChunkyPNG::Point; source
|
35
|
+
when ChunkyPNG::Dimension; ChunkyPNG::Point.new(source.width, source.height)
|
36
|
+
when Array; ChunkyPNG::Point.new(source[0], source[1])
|
37
|
+
when Hash; ChunkyPNG::Point.new(source[:x] || source['x'], source[:y] || source['y'])
|
38
|
+
when /^[\(\[\{]?(\d+)\s*[,]?\s*(\d+)[\)\]\}]?$/; ChunkyPNG::Point.new($1.to_i, $2.to_i)
|
39
|
+
else
|
40
|
+
if source.respond_to?(:x) && source.respond_to?(:y)
|
41
|
+
ChunkyPNG::Point.new(source.x, source.y)
|
42
|
+
else
|
43
|
+
raise ChunkyPNG::ExpectationFailed, "Don't know how to construct a point from #{source.inspect}!"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else raise ChunkyPNG::ExpectationFailed, "Don't know how to construct a point from #{args.inspect}!"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Simple class that represents a point on a canvas using an x and y coordinate.
|
51
|
+
#
|
52
|
+
# This class implements some basic methods to handle comparison, the splat operator and
|
53
|
+
# bounds checking that make it easier to work with coordinates.
|
54
|
+
#
|
55
|
+
# @see ChunkyPNG.Point
|
56
|
+
class Point
|
57
|
+
|
58
|
+
# @return [Integer] The x-coordinate of the point.
|
59
|
+
attr_accessor :x
|
60
|
+
|
61
|
+
# @return [Integer] The y-coordinate of the point.
|
62
|
+
attr_accessor :y
|
63
|
+
|
64
|
+
# Initializes a new point instance.
|
65
|
+
# @param [Integer, :to_i] x The x-coordinate.
|
66
|
+
# @param [Integer, :to_i] y The y-coordinate.
|
67
|
+
def initialize(x, y)
|
68
|
+
@x, @y = x.to_i, y.to_i
|
69
|
+
end
|
70
|
+
|
71
|
+
# Checks whether 2 points are identical.
|
72
|
+
# @return [true, false] <tt>true</tt> iff the x and y coordinates match
|
73
|
+
def eql?(other)
|
74
|
+
other.x == x && other.y == y
|
75
|
+
end
|
76
|
+
|
77
|
+
alias_method :==, :eql?
|
78
|
+
|
79
|
+
# Comparses 2 points.
|
80
|
+
#
|
81
|
+
# It will first compare the y coordinate, and it only takes the x-coordinate into
|
82
|
+
# account if the y-coordinates of the points are identical. This way, an array of
|
83
|
+
# points will be sorted into the order in which thet would occur in the pixels
|
84
|
+
# array returned by {ChunkyPNG::Canvas#pixels}.
|
85
|
+
#
|
86
|
+
# @param [ChunkyPNG::Point] other The point to compare this point with.
|
87
|
+
# @return [-1, 0, 1] <tt>-1</tt> If this point comes before the other one, <tt>1</tt>
|
88
|
+
# if after, and <tt>0</tt> if the points are identical.
|
89
|
+
def <=>(other)
|
90
|
+
((y <=> other.y) == 0) ? x <=> other.x : y <=> other.y
|
91
|
+
end
|
92
|
+
|
93
|
+
# Converts the point instance to an array.
|
94
|
+
# @return [Array] A 2-element array, i.e. <tt>[x, y]</tt>.
|
95
|
+
def to_a
|
96
|
+
[x, y]
|
97
|
+
end
|
98
|
+
|
99
|
+
alias_method :to_ary, :to_a
|
100
|
+
|
101
|
+
# Checks whether the point falls into a dimension
|
102
|
+
# @param [ChunkyPNG::Dimension, ...] dimension_like The dimension of which the bounds
|
103
|
+
# should be taken for the check.
|
104
|
+
# @return [true, false] <tt>true</tt> iff the x and y coordinate fall width the width
|
105
|
+
# and height of the dimension.
|
106
|
+
def within_bounds?(*dimension_like)
|
107
|
+
ChunkyPNG::Dimension(*dimension_like).include?(self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
# Factory method for {ChunkyPNG::Vector} instances.
|
4
|
+
#
|
5
|
+
# @overload Vector(x0, y0, x1, y1, x2, y2, ...)
|
6
|
+
# Creates a vector by parsing two subsequent values in the argument list
|
7
|
+
# as x- and y-coordinate of a point.
|
8
|
+
# @return [ChunkyPNG::Vector] The instantiated vector.
|
9
|
+
# @overload Vector(string)
|
10
|
+
# Creates a vector by parsing coordinates from the input string.
|
11
|
+
# @return [ChunkyPNG::Vector] The instantiated vector.
|
12
|
+
# @overload Vector(pointlike, pointlike, pointlike, ...)
|
13
|
+
# Creates a vector by converting every argument to a point using {ChunkyPNG.Point}.
|
14
|
+
# @return [ChunkyPNG::Vector] The instantiated vector.
|
15
|
+
def self.Vector(*args)
|
16
|
+
|
17
|
+
return args.first if args.length == 1 && args.first.kind_of?(ChunkyPNG::Vector)
|
18
|
+
|
19
|
+
if args.length == 1 && args.first.respond_to?(:scan)
|
20
|
+
ChunkyPNG::Vector.new(ChunkyPNG::Vector.multiple_from_string(args.first)) # e.g. ['1,1 2,2 3,3']
|
21
|
+
else
|
22
|
+
ChunkyPNG::Vector.new(ChunkyPNG::Vector.multiple_from_array(args)) # e.g. [[1,1], [2,2], [3,3]] or [1,1,2,2,3,3]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Class that represents a vector of points, i.e. a list of {ChunkyPNG::Point} instances.
|
27
|
+
class Vector
|
28
|
+
|
29
|
+
include Enumerable
|
30
|
+
|
31
|
+
attr_reader :points
|
32
|
+
|
33
|
+
def initialize(points = [])
|
34
|
+
@points = points
|
35
|
+
end
|
36
|
+
|
37
|
+
def each_edge(close = true)
|
38
|
+
raise ChunkyPNG::ExpectationFailed, "Not enough points in this path to draw an edge!" if length < 2
|
39
|
+
points.each_cons(2) { |a, b| yield(a, b) }
|
40
|
+
yield(points.last, points.first) if close
|
41
|
+
end
|
42
|
+
|
43
|
+
def edges(close = true)
|
44
|
+
Enumerator.new(self, :each_edge, close)
|
45
|
+
end
|
46
|
+
|
47
|
+
def length
|
48
|
+
points.length
|
49
|
+
end
|
50
|
+
|
51
|
+
def each(&block)
|
52
|
+
points.each(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def eql?(other)
|
56
|
+
other.points == points
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :==, :eql?
|
60
|
+
|
61
|
+
def to_a
|
62
|
+
edges
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :to_ary, :to_a
|
66
|
+
|
67
|
+
def x_range
|
68
|
+
Range.new(*points.map { |p| p.x }.minmax)
|
69
|
+
end
|
70
|
+
|
71
|
+
def y_range
|
72
|
+
Range.new(*points.map { |p| p.y }.minmax)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.multiple_from_array(source)
|
76
|
+
return [] if source.empty?
|
77
|
+
if source.first.kind_of?(Numeric) || source.first =~ /^\d+$/
|
78
|
+
raise ChunkyPNG::ExpectationFailed, "The points array is expected to have an even number of items!" if source.length % 2 != 0
|
79
|
+
|
80
|
+
points = []
|
81
|
+
source.each_slice(2) { |x, y| points << ChunkyPNG::Point.new(x, y) }
|
82
|
+
return points
|
83
|
+
else
|
84
|
+
source.map { |p| ChunkyPNG::Point(p) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.multiple_from_string(source_str)
|
89
|
+
multiple_from_array(source_str.scan(/[\(\[\{]?(\d+)\s*[,x]?\s*(\d+)[\)\]\}]?/))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -3,36 +3,129 @@ require 'spec_helper'
|
|
3
3
|
describe ChunkyPNG::Canvas::Drawing do
|
4
4
|
|
5
5
|
describe '#point' do
|
6
|
+
subject { ChunkyPNG::Canvas.new(1, 1, ChunkyPNG::Color.rgb(200, 150, 100)) }
|
7
|
+
|
6
8
|
it "should compose colors correctly" do
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
subject.compose_pixel(0,0, ChunkyPNG::Color.rgba(100, 150, 200, 128))
|
10
|
+
subject['0,0'].should == ChunkyPNG::Color.rgb(150, 150, 150)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the composed color" do
|
14
|
+
subject.compose_pixel(0, 0, ChunkyPNG::Color.rgba(100, 150, 200, 128)).should == ChunkyPNG::Color.rgb(150, 150, 150)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should accept point-like arguments as well" do
|
18
|
+
lambda { subject.compose_pixel('0,0', ChunkyPNG::Color.rgba(100, 150, 200, 128)) }.should change { subject['0,0'] }
|
19
|
+
lambda { subject.compose_pixel({:x => 0, :y => 0}, ChunkyPNG::Color.rgba(100, 150, 200, 128)) }.should change { subject['0,0'] }
|
20
|
+
lambda { subject.compose_pixel(ChunkyPNG::Point.new(0, 0), ChunkyPNG::Color.rgba(100, 150, 200, 128)) } .should change { subject['0,0'] }
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should do nothing when the coordinates are out of bounds" do
|
24
|
+
subject.compose_pixel(1, -1, ChunkyPNG::Color::BLACK).should be_nil
|
25
|
+
lambda { subject.compose_pixel(1, -1, ChunkyPNG::Color::BLACK) }.should_not change { subject['0,0'] }
|
10
26
|
end
|
11
27
|
end
|
12
28
|
|
13
29
|
describe '#line' do
|
14
30
|
it "should draw lines correctly with anti-aliasing" do
|
15
|
-
canvas = ChunkyPNG::Canvas.new(32, 32, ChunkyPNG::Color::WHITE)
|
16
31
|
|
17
|
-
canvas.
|
18
|
-
canvas.line( 0, 31, 31, 0, ChunkyPNG::Color::BLACK)
|
19
|
-
canvas.line(15, 31, 15, 0, ChunkyPNG::Color.rgba(200, 0, 0, 128))
|
20
|
-
canvas.line( 0, 15, 31, 15, ChunkyPNG::Color.rgba(200, 0, 0, 128))
|
21
|
-
canvas.line( 0, 15, 31, 31, ChunkyPNG::Color.rgba( 0, 200, 0, 128))
|
22
|
-
canvas.line( 0, 15, 31, 0, ChunkyPNG::Color.rgba( 0, 200, 0, 128))
|
23
|
-
canvas.line(15, 0, 31, 31, ChunkyPNG::Color.rgba( 0, 0, 200, 128))
|
24
|
-
canvas.line(15, 0, 0, 31, ChunkyPNG::Color.rgba( 0, 0, 200, 128))
|
32
|
+
canvas = ChunkyPNG::Canvas.new(31, 31, ChunkyPNG::Color::WHITE)
|
25
33
|
|
34
|
+
canvas.line( 0, 0, 30, 30, ChunkyPNG::Color::BLACK)
|
35
|
+
canvas.line( 0, 30, 30, 0, ChunkyPNG::Color::BLACK)
|
36
|
+
canvas.line(15, 30, 15, 0, ChunkyPNG::Color.rgba(200, 0, 0, 128))
|
37
|
+
canvas.line( 0, 15, 30, 15, ChunkyPNG::Color.rgba(200, 0, 0, 128))
|
38
|
+
canvas.line(30, 30, 0, 15, ChunkyPNG::Color.rgba( 0, 200, 0, 128), false)
|
39
|
+
canvas.line( 0, 15, 30, 0, ChunkyPNG::Color.rgba( 0, 200, 0, 128))
|
40
|
+
canvas.line( 0, 30, 15, 0, ChunkyPNG::Color.rgba( 0, 0, 200, 128), false)
|
41
|
+
canvas.line(15, 0, 30, 30, ChunkyPNG::Color.rgba( 0, 0, 200, 128))
|
42
|
+
|
26
43
|
canvas.should == reference_canvas('lines')
|
27
44
|
end
|
45
|
+
|
46
|
+
it "should draw partial lines if the coordinates are partially out of bounds" do
|
47
|
+
canvas = ChunkyPNG::Canvas.new(1, 2, ChunkyPNG::Color::WHITE)
|
48
|
+
canvas.line(-5, -5, 0, 0, ChunkyPNG::Color::BLACK)
|
49
|
+
canvas.pixels.should == [ChunkyPNG::Color::BLACK, ChunkyPNG::Color::WHITE]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return itself to allow chaining" do
|
53
|
+
canvas = ChunkyPNG::Canvas.new(16, 16, ChunkyPNG::Color::WHITE)
|
54
|
+
canvas.line(1, 1, 10, 10, ChunkyPNG::Color::BLACK).should equal(canvas)
|
55
|
+
end
|
28
56
|
end
|
29
57
|
|
30
58
|
describe '#rect' do
|
31
59
|
it "should draw a rectangle with the correct colors" do
|
32
60
|
canvas = ChunkyPNG::Canvas.new(16, 16, ChunkyPNG::Color::WHITE)
|
33
|
-
canvas.rect(1, 1, 10, 10, ChunkyPNG::Color.
|
34
|
-
canvas.rect(5, 5, 14, 14, ChunkyPNG::Color.
|
61
|
+
canvas.rect(1, 1, 10, 10, ChunkyPNG::Color.rgba(0, 255, 0, 80), ChunkyPNG::Color.rgba(255, 0, 0, 100))
|
62
|
+
canvas.rect(5, 5, 14, 14, ChunkyPNG::Color.rgba(0, 0, 255, 160), ChunkyPNG::Color.rgba(255, 255, 0, 100))
|
35
63
|
canvas.should == reference_canvas('rect')
|
36
64
|
end
|
65
|
+
|
66
|
+
it "should return itself to allow chaining" do
|
67
|
+
canvas = ChunkyPNG::Canvas.new(16, 16, ChunkyPNG::Color::WHITE)
|
68
|
+
canvas.rect(1, 1, 10, 10).should equal(canvas)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should draw partial rectangles if the coordinates are partially out of bounds" do
|
72
|
+
canvas = ChunkyPNG::Canvas.new(1, 1)
|
73
|
+
canvas.rect(0, 0, 10, 10, ChunkyPNG::Color::BLACK, ChunkyPNG::Color::WHITE)
|
74
|
+
canvas[0, 0].should == ChunkyPNG::Color::BLACK
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should draw the rectangle fill only if the coordinates are fully out of bounds" do
|
78
|
+
canvas = ChunkyPNG::Canvas.new(1, 1)
|
79
|
+
canvas.rect(-10, -10, 10, 10, ChunkyPNG::Color::BLACK, ChunkyPNG::Color::WHITE)
|
80
|
+
canvas[0, 0].should == ChunkyPNG::Color::WHITE
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#circle' do
|
85
|
+
subject { ChunkyPNG::Canvas.new(32, 32, ChunkyPNG::Color.rgba(0, 0, 255, 128)) }
|
86
|
+
|
87
|
+
it "should draw circles" do
|
88
|
+
subject.circle(11, 11, 10, ChunkyPNG::Color('red @ 0.5'), ChunkyPNG::Color('white @ 0.2'))
|
89
|
+
subject.circle(21, 21, 10, ChunkyPNG::Color('green @ 0.5'))
|
90
|
+
subject.should == reference_canvas('circles')
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should draw partial circles when going of the canvas bounds" do
|
94
|
+
subject.circle(0, 0, 10, ChunkyPNG::Color(:red))
|
95
|
+
subject.circle(31, 16, 10, ChunkyPNG::Color(:black), ChunkyPNG::Color(:white, 0xaa))
|
96
|
+
subject.should == reference_canvas('partial_circles')
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should return itself to allow chaining" do
|
100
|
+
subject.circle(10, 10, 5).should equal(subject)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#polygon' do
|
105
|
+
subject { ChunkyPNG::Canvas.new(22, 22) }
|
106
|
+
|
107
|
+
it "should draw an filled triangle when using 3 control points" do
|
108
|
+
subject.polygon('(2,2) (20,5) (5,20)', ChunkyPNG::Color(:black, 0xaa), ChunkyPNG::Color(:red, 0x44))
|
109
|
+
subject.should == reference_canvas('polygon_triangle_filled')
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should draw a unfilled polygon with 6 control points" do
|
113
|
+
subject.polygon('(2,2) (12, 1) (20,5) (18,18) (5,20) (1,12)', ChunkyPNG::Color(:black))
|
114
|
+
subject.should == reference_canvas('polygon_unfilled')
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should draw a vertically crossed filled polygon with 4 control points" do
|
118
|
+
subject.polygon('(2,2) (21,2) (2,21) (21,21)', ChunkyPNG::Color(:black), ChunkyPNG::Color(:red))
|
119
|
+
subject.should == reference_canvas('polygon_filled_vertical')
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should draw a vertically crossed filled polygon with 4 control points" do
|
123
|
+
subject.polygon('(2,2) (2,21) (21,2) (21,21)', ChunkyPNG::Color(:black), ChunkyPNG::Color(:red))
|
124
|
+
subject.should == reference_canvas('polygon_filled_horizontal')
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return itself to allow chaining" do
|
128
|
+
subject.polygon('(2,2) (20,5) (5,20)').should equal(subject)
|
129
|
+
end
|
37
130
|
end
|
38
131
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ChunkyPNG::Canvas::Masking do
|
4
|
+
|
5
|
+
subject { reference_canvas('clock') }
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@theme_color = ChunkyPNG::Color('#e10f7a')
|
9
|
+
@new_color = ChunkyPNG::Color('#ff0000')
|
10
|
+
@background_color = ChunkyPNG::Color('white')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#change_theme_color!' do
|
14
|
+
it "should change the theme color correctly" do
|
15
|
+
subject.change_theme_color!(@theme_color, @new_color)
|
16
|
+
subject.should == reference_canvas('clock_updated')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#extract_mask' do
|
21
|
+
it "should create the correct base and mask image" do
|
22
|
+
base, mask = subject.extract_mask(@theme_color, @background_color)
|
23
|
+
base.should == reference_canvas('clock_base')
|
24
|
+
mask.should == reference_canvas('clock_mask')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should create a mask image with only one opaque color" do
|
28
|
+
base, mask = subject.extract_mask(@theme_color, @background_color)
|
29
|
+
mask.palette.opaque_palette.size.should == 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#change_mask_color!' do
|
34
|
+
before { @mask = reference_canvas('clock_mask') }
|
35
|
+
|
36
|
+
it "should replace the mask color correctly" do
|
37
|
+
@mask.change_mask_color!(@new_color)
|
38
|
+
@mask.should == reference_canvas('clock_mask_updated')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should still only have one opaque color" do
|
42
|
+
@mask.change_mask_color!(@new_color)
|
43
|
+
@mask.palette.opaque_palette.size.should == 1
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should raise an exception when the mask image has more than once color" do
|
47
|
+
not_a_mask = reference_canvas('operations')
|
48
|
+
lambda { not_a_mask.change_mask_color!(@new_color) }.should raise_error(ChunkyPNG::ExpectationFailed)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,173 +1,240 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ChunkyPNG::Canvas::Operations do
|
4
|
+
|
5
|
+
subject { reference_canvas('operations') }
|
6
|
+
|
4
7
|
describe '#crop' do
|
5
|
-
before { @canvas = reference_canvas('operations') }
|
6
|
-
|
7
8
|
it "should crop the right pixels from the original canvas" do
|
8
|
-
|
9
|
-
|
9
|
+
subject.crop(10, 5, 4, 8).should == reference_canvas('cropped')
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not return itself" do
|
13
|
+
subject.crop(10, 5, 4, 8).should_not equal(subject)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not adjust the currnet image" do
|
17
|
+
lambda { subject.crop(10, 5, 4, 8) }.should_not change(subject, :dimension)
|
10
18
|
end
|
11
19
|
|
12
20
|
it "should raise an exception when the cropped image falls outside the oiginal image" do
|
13
|
-
lambda {
|
21
|
+
lambda { subject.crop(16, 16, 2, 2) }.should raise_error(ChunkyPNG::OutOfBounds)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#crop!' do
|
26
|
+
it "should crop the right pixels from the original canvas" do
|
27
|
+
subject.crop!(10, 5, 4, 8)
|
28
|
+
subject.should == reference_canvas('cropped')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have a new width and height" do
|
32
|
+
lambda { subject.crop!(10, 5, 4, 8) }.should change(subject, :dimension).
|
33
|
+
from(ChunkyPNG::Dimension('16x16')).
|
34
|
+
to(ChunkyPNG::Dimension('4x8'))
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise an exception when the cropped image falls outside the oiginal image" do
|
38
|
+
lambda { subject.crop!(16, 16, 2, 2) }.should raise_error(ChunkyPNG::OutOfBounds)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return itself" do
|
42
|
+
subject.crop!(10, 5, 4, 8).should equal(subject)
|
14
43
|
end
|
15
44
|
end
|
16
45
|
|
17
46
|
describe '#compose' do
|
18
47
|
it "should compose pixels correctly" do
|
19
|
-
canvas = reference_canvas('operations')
|
20
48
|
subcanvas = ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75))
|
21
|
-
|
22
|
-
|
49
|
+
subject.compose(subcanvas, 8, 4).should == reference_canvas('composited')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should leave the original intact" do
|
53
|
+
subject.compose(ChunkyPNG::Canvas.new(1,1))
|
54
|
+
subject.should == reference_canvas('operations')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should not return itself" do
|
58
|
+
subject.compose(ChunkyPNG::Canvas.new(1,1)).should_not equal(subject)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should raise an exception when the pixels to compose fall outside the image" do
|
62
|
+
lambda { subject.compose(ChunkyPNG::Canvas.new(1,1), 16, 16) }.should raise_error(ChunkyPNG::OutOfBounds)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#compose!' do
|
67
|
+
it "should compose pixels correctly" do
|
68
|
+
subcanvas = ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75))
|
69
|
+
subject.compose!(subcanvas, 8, 4)
|
70
|
+
subject.should == reference_canvas('composited')
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return itself" do
|
74
|
+
subject.compose!(ChunkyPNG::Canvas.new(1,1)).should equal(subject)
|
23
75
|
end
|
24
76
|
|
25
77
|
it "should compose a base image and mask correctly" do
|
26
78
|
base = reference_canvas('clock_base')
|
27
79
|
mask = reference_canvas('clock_mask_updated')
|
28
|
-
base.compose(mask)
|
80
|
+
base.compose!(mask)
|
81
|
+
base.should == reference_canvas('clock_updated')
|
29
82
|
end
|
30
83
|
|
31
84
|
it "should raise an exception when the pixels to compose fall outside the image" do
|
32
|
-
lambda {
|
33
|
-
end
|
85
|
+
lambda { subject.compose!(ChunkyPNG::Canvas.new(1,1), 16, 16) }.should raise_error(ChunkyPNG::OutOfBounds)
|
86
|
+
end
|
34
87
|
end
|
35
88
|
|
36
89
|
describe '#replace' do
|
37
|
-
before { @canvas = reference_canvas('operations') }
|
38
|
-
|
39
90
|
it "should replace the correct pixels" do
|
40
91
|
subcanvas = ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0))
|
41
|
-
|
42
|
-
@canvas.should == reference_canvas('replaced')
|
92
|
+
subject.replace(subcanvas, 5, 4).should == reference_canvas('replaced')
|
43
93
|
end
|
44
94
|
|
45
|
-
it "should
|
46
|
-
|
95
|
+
it "should not return itself" do
|
96
|
+
subject.replace(ChunkyPNG::Canvas.new(1,1)).should_not equal(subject)
|
47
97
|
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe '#change_theme_color!' do
|
51
98
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@canvas = reference_canvas('clock')
|
99
|
+
it "should leave the original intact" do
|
100
|
+
subject.replace(ChunkyPNG::Canvas.new(1,1))
|
101
|
+
subject.should == reference_canvas('operations')
|
56
102
|
end
|
57
103
|
|
58
|
-
it "should
|
59
|
-
|
60
|
-
@canvas.should == reference_canvas('clock_updated')
|
104
|
+
it "should raise an exception when the pixels to replace fall outside the image" do
|
105
|
+
lambda { subject.replace(ChunkyPNG::Canvas.new(1,1), 16, 16) }.should raise_error(ChunkyPNG::OutOfBounds)
|
61
106
|
end
|
62
107
|
end
|
63
108
|
|
64
|
-
describe '#
|
65
|
-
|
66
|
-
|
67
|
-
|
109
|
+
describe '#replace!' do
|
110
|
+
it "should replace the correct pixels" do
|
111
|
+
subcanvas = ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0))
|
112
|
+
subject.replace!(subcanvas, 5, 4)
|
113
|
+
subject.should == reference_canvas('replaced')
|
68
114
|
end
|
69
115
|
|
70
|
-
it "should
|
71
|
-
|
72
|
-
base.should == reference_canvas('clock_base')
|
73
|
-
mask.should == reference_canvas('clock_mask')
|
116
|
+
it "should return itself" do
|
117
|
+
subject.replace!(ChunkyPNG::Canvas.new(1,1)).should equal(subject)
|
74
118
|
end
|
75
119
|
|
76
|
-
it "should
|
77
|
-
|
78
|
-
mask.palette.opaque_palette.size.should == 1
|
120
|
+
it "should raise an exception when the pixels to replace fall outside the image" do
|
121
|
+
lambda { subject.replace!(ChunkyPNG::Canvas.new(1,1), 16, 16) }.should raise_error(ChunkyPNG::OutOfBounds)
|
79
122
|
end
|
80
123
|
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe ChunkyPNG::Canvas::Operations do
|
81
127
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
128
|
+
subject { ChunkyPNG::Canvas.new(2, 3, [1, 2, 3, 4, 5, 6]) }
|
129
|
+
|
130
|
+
describe '#flip_horizontally!' do
|
131
|
+
it "should flip the pixels horizontally in place" do
|
132
|
+
subject.flip_horizontally!
|
133
|
+
subject.should == ChunkyPNG::Canvas.new(2, 3, [5, 6, 3, 4, 1, 2])
|
86
134
|
end
|
87
135
|
|
88
|
-
it "should
|
89
|
-
|
90
|
-
|
136
|
+
it "should return itself" do
|
137
|
+
subject.flip_horizontally!.should equal(subject)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#flip_horizontally' do
|
142
|
+
it "should flip the pixels horizontally" do
|
143
|
+
subject.flip_horizontally.should == ChunkyPNG::Canvas.new(2, 3, [5, 6, 3, 4, 1, 2])
|
91
144
|
end
|
92
145
|
|
93
|
-
it "should
|
94
|
-
|
95
|
-
@mask.palette.opaque_palette.size.should == 1
|
146
|
+
it "should not return itself" do
|
147
|
+
subject.flip_horizontally.should_not equal(subject)
|
96
148
|
end
|
97
149
|
|
98
|
-
it "should
|
99
|
-
|
100
|
-
lambda { not_a_mask.change_mask_color!(@new_color) }.should raise_error(ChunkyPNG::ExpectationFailed)
|
150
|
+
it "should return a copy of itself when applied twice" do
|
151
|
+
subject.flip_horizontally.flip_horizontally.should == subject
|
101
152
|
end
|
102
153
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
describe '#flip_horizontally' do
|
109
|
-
it "should flip the pixels horizontally" do
|
110
|
-
@stubbed.flip_horizontally.should == reference_canvas('clock_flip_horizontally')
|
154
|
+
|
155
|
+
describe '#flip_vertically!' do
|
156
|
+
it "should flip the pixels vertically" do
|
157
|
+
subject.flip_vertically!
|
158
|
+
subject.should == ChunkyPNG::Canvas.new(2, 3, [2, 1, 4, 3, 6, 5])
|
111
159
|
end
|
112
160
|
|
113
|
-
it "should return itself
|
114
|
-
|
161
|
+
it "should return itself" do
|
162
|
+
subject.flip_horizontally!.should equal(subject)
|
115
163
|
end
|
116
164
|
end
|
117
165
|
|
118
166
|
describe '#flip_vertically' do
|
119
167
|
it "should flip the pixels vertically" do
|
120
|
-
|
168
|
+
subject.flip_vertically.should == ChunkyPNG::Canvas.new(2, 3, [2, 1, 4, 3, 6, 5])
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should not return itself" do
|
172
|
+
subject.flip_horizontally.should_not equal(subject)
|
121
173
|
end
|
122
174
|
|
123
|
-
it "should return itself when applied twice" do
|
124
|
-
|
175
|
+
it "should return a copy of itself when applied twice" do
|
176
|
+
subject.flip_vertically.flip_vertically.should == subject
|
125
177
|
end
|
126
178
|
end
|
127
179
|
|
128
180
|
describe '#rotate_left' do
|
129
181
|
it "should rotate the pixels 90 degrees counter-clockwise" do
|
130
|
-
|
182
|
+
subject.rotate_left.should == ChunkyPNG::Canvas.new(3, 2, [2, 4, 6, 1, 3, 5] )
|
131
183
|
end
|
132
184
|
|
133
185
|
it "it should rotate 180 degrees when applied twice" do
|
134
|
-
|
186
|
+
subject.rotate_left.rotate_left.should == subject.rotate_180
|
135
187
|
end
|
136
188
|
|
137
189
|
it "it should rotate right when applied three times" do
|
138
|
-
|
190
|
+
subject.rotate_left.rotate_left.rotate_left.should == subject.rotate_right
|
139
191
|
end
|
140
192
|
|
141
193
|
it "should return itself when applied four times" do
|
142
|
-
|
194
|
+
subject.rotate_left.rotate_left.rotate_left.rotate_left.should == subject
|
143
195
|
end
|
144
196
|
end
|
145
197
|
|
146
198
|
describe '#rotate_right' do
|
147
199
|
it "should rotate the pixels 90 degrees clockwise" do
|
148
|
-
|
200
|
+
subject.rotate_right.should == ChunkyPNG::Canvas.new(3, 2, [5, 3, 1, 6, 4, 2] )
|
149
201
|
end
|
150
202
|
|
151
203
|
it "it should rotate 180 degrees when applied twice" do
|
152
|
-
|
204
|
+
subject.rotate_right.rotate_right.should == subject.rotate_180
|
153
205
|
end
|
154
206
|
|
155
207
|
it "it should rotate left when applied three times" do
|
156
|
-
|
208
|
+
subject.rotate_right.rotate_right.rotate_right.should == subject.rotate_left
|
157
209
|
end
|
158
210
|
|
159
211
|
it "should return itself when applied four times" do
|
160
|
-
|
212
|
+
subject.rotate_right.rotate_right.rotate_right.rotate_right.should == subject
|
161
213
|
end
|
162
214
|
end
|
163
215
|
|
164
216
|
describe '#rotate_180' do
|
165
217
|
it "should rotate the pixels 180 degrees" do
|
166
|
-
|
218
|
+
subject.rotate_180.should == ChunkyPNG::Canvas.new(2, 3, [6, 5, 4, 3, 2, 1])
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should return not itself" do
|
222
|
+
subject.rotate_180.should_not equal(subject)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should return a copy of itself when applied twice" do
|
226
|
+
subject.rotate_180.rotate_180.should == subject
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe '#rotate_180!' do
|
231
|
+
it "should rotate the pixels 180 degrees" do
|
232
|
+
subject.rotate_180!
|
233
|
+
subject.should == ChunkyPNG::Canvas.new(2, 3, [6, 5, 4, 3, 2, 1])
|
167
234
|
end
|
168
235
|
|
169
|
-
it "should return itself
|
170
|
-
|
236
|
+
it "should return itself" do
|
237
|
+
subject.rotate_180!.should equal(subject)
|
171
238
|
end
|
172
239
|
end
|
173
240
|
end
|