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.
Files changed (44) hide show
  1. data/.infinity_test +1 -1
  2. data/README.rdoc +2 -2
  3. data/chunky_png.gemspec +4 -4
  4. data/lib/chunky_png.rb +14 -8
  5. data/lib/chunky_png/canvas.rb +110 -38
  6. data/lib/chunky_png/canvas/drawing.rb +175 -34
  7. data/lib/chunky_png/canvas/masking.rb +91 -0
  8. data/lib/chunky_png/canvas/operations.rb +141 -112
  9. data/lib/chunky_png/canvas/png_decoding.rb +13 -13
  10. data/lib/chunky_png/canvas/resampling.rb +42 -0
  11. data/lib/chunky_png/color.rb +252 -11
  12. data/lib/chunky_png/compatibility.rb +5 -5
  13. data/lib/chunky_png/dimension.rb +106 -0
  14. data/lib/chunky_png/point.rb +110 -0
  15. data/lib/chunky_png/vector.rb +92 -0
  16. data/spec/chunky_png/canvas/drawing_spec.rb +107 -14
  17. data/spec/chunky_png/canvas/masking_spec.rb +51 -0
  18. data/spec/chunky_png/canvas/operations_spec.rb +142 -75
  19. data/spec/chunky_png/canvas/resampling_spec.rb +31 -0
  20. data/spec/chunky_png/canvas/stream_exporting_spec.rb +20 -0
  21. data/spec/chunky_png/canvas/stream_importing_spec.rb +22 -0
  22. data/spec/chunky_png/canvas_spec.rb +151 -22
  23. data/spec/chunky_png/color_spec.rb +53 -0
  24. data/spec/chunky_png/dimension_spec.rb +43 -0
  25. data/spec/chunky_png/point_spec.rb +71 -0
  26. data/spec/chunky_png/vector_spec.rb +58 -0
  27. data/spec/resources/circles.png +0 -0
  28. data/spec/resources/clock_nn_xdown_ydown.png +0 -0
  29. data/spec/resources/clock_nn_xdown_yup.png +0 -0
  30. data/spec/resources/clock_nn_xup_yup.png +0 -0
  31. data/spec/resources/lines.png +0 -0
  32. data/spec/resources/partial_circles.png +0 -0
  33. data/spec/resources/polygon_filled_horizontal.png +0 -0
  34. data/spec/resources/polygon_filled_vertical.png +0 -0
  35. data/spec/resources/polygon_triangle_filled.png +0 -0
  36. data/spec/resources/polygon_unfilled.png +0 -0
  37. data/spec/resources/rect.png +0 -0
  38. metadata +31 -9
  39. data/spec/resources/clock_flip_horizontally.png +0 -0
  40. data/spec/resources/clock_flip_vertically.png +0 -0
  41. data/spec/resources/clock_rotate_180.png +0 -0
  42. data/spec/resources/clock_rotate_left.png +0 -0
  43. data/spec/resources/clock_rotate_right.png +0 -0
  44. 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
- canvas = ChunkyPNG::Canvas.new(1, 1, ChunkyPNG::Color.rgb(200, 150, 100))
8
- canvas.point(0,0, ChunkyPNG::Color.rgba(100, 150, 200, 128))
9
- canvas[0,0].should == ChunkyPNG::Color.rgb(150, 150, 150)
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.line( 0, 0, 31, 31, ChunkyPNG::Color::BLACK)
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.rgb(0, 255, 0), ChunkyPNG::Color.rgba(255, 0, 0, 100))
34
- canvas.rect(5, 5, 14, 14, ChunkyPNG::Color.rgb(0, 0, 255), ChunkyPNG::Color.rgba(255, 255, 0, 100))
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
- cropped = @canvas.crop(10, 5, 4, 8)
9
- cropped.should == reference_canvas('cropped')
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 { @canvas.crop(16, 16, 2, 2) }.should raise_error(ChunkyPNG::OutOfBounds)
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
- canvas.compose(subcanvas, 8, 4)
22
- canvas.should == reference_canvas('composited')
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).should == reference_canvas('clock_updated')
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 { reference_canvas('operations').compose(ChunkyPNG::Canvas.new(1,1), 16, 16) }.should raise_error(ChunkyPNG::OutOfBounds)
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
- @canvas.replace(subcanvas, 5, 4)
42
- @canvas.should == reference_canvas('replaced')
92
+ subject.replace(subcanvas, 5, 4).should == reference_canvas('replaced')
43
93
  end
44
94
 
45
- it "should raise an exception when the pixels to replace fall outside the image" do
46
- lambda { @canvas.replace(ChunkyPNG::Canvas.new(1,1), 16, 16) }.should raise_error(ChunkyPNG::OutOfBounds)
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
- before(:each) do
53
- @theme_color = ChunkyPNG::Color.from_hex('#e10f7a')
54
- @new_color = ChunkyPNG::Color.from_hex('#ff0000')
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 change the theme color correctly" do
59
- @canvas.change_theme_color!(@theme_color, @new_color)
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 '#extract_mask' do
65
- before(:each) do
66
- @mask_color = ChunkyPNG::Color.from_hex('#e10f7a')
67
- @canvas = reference_canvas('clock')
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 create the correct base and mask image" do
71
- base, mask = @canvas.extract_mask(@mask_color, ChunkyPNG::Color::WHITE)
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 create a mask image with only one opaque color" do
77
- base, mask = @canvas.extract_mask(@mask_color, ChunkyPNG::Color::WHITE)
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
- describe '#change_mask_color!' do
83
- before(:each) do
84
- @new_color = ChunkyPNG::Color.from_hex('#ff0000')
85
- @mask = reference_canvas('clock_mask')
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 replace the mask color correctly" do
89
- @mask.change_mask_color!(@new_color)
90
- @mask.should == reference_canvas('clock_mask_updated')
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 still only have one opaque color" do
94
- @mask.change_mask_color!(@new_color)
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 raise an exception when the mask image has more than once color" do
99
- not_a_mask = reference_canvas('operations')
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
- end
104
-
105
- describe ChunkyPNG::Canvas::Operations do
106
- before { @stubbed = reference_canvas('clock_stubbed') }
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 when applied twice" do
114
- @stubbed.flip_horizontally.flip_horizontally.should == @stubbed
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
- @stubbed.flip_vertically.should == reference_canvas('clock_flip_vertically')
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
- @stubbed.flip_vertically.flip_vertically.should == @stubbed
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
- @stubbed.rotate_left.should == reference_canvas('clock_rotate_left')
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
- @stubbed.rotate_left.rotate_left.should == reference_canvas('clock_rotate_180')
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
- @stubbed.rotate_left.rotate_left.rotate_left.should == reference_canvas('clock_rotate_right')
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
- @stubbed.rotate_left.rotate_left.rotate_left.rotate_left.should == @stubbed
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
- @stubbed.rotate_right.should == reference_canvas('clock_rotate_right')
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
- @stubbed.rotate_right.rotate_right.should == reference_canvas('clock_rotate_180')
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
- @stubbed.rotate_right.rotate_right.rotate_right.should == reference_canvas('clock_rotate_left')
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
- @stubbed.rotate_right.rotate_right.rotate_right.rotate_right.should == @stubbed
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
- @stubbed.rotate_180.should == reference_canvas('clock_rotate_180')
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 when applied twice" do
170
- @stubbed.rotate_180.rotate_180.should == @stubbed
236
+ it "should return itself" do
237
+ subject.rotate_180!.should equal(subject)
171
238
  end
172
239
  end
173
240
  end