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.
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