chunky_png 1.0.0.beta2 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|