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,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChunkyPNG::Canvas::Resampling do
4
+
5
+ subject { reference_canvas('clock') }
6
+
7
+ describe '#resample_nearest_neighbor' do
8
+
9
+ it "should downscale from 2x2 to 1x1 correctly" do
10
+ canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4])
11
+ canvas.resample_nearest_neighbor(1, 1).should == ChunkyPNG::Canvas.new(1, 1, [1])
12
+ end
13
+
14
+ it "should downscale from 2x2 to 4x4 correctly" do
15
+ canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4])
16
+ canvas.resample_nearest_neighbor(4, 4).should == ChunkyPNG::Canvas.new(4, 4, [1,1,1,2,1,1,1,2,1,1,1,2,3,3,3,4])
17
+ end
18
+
19
+ it "should upscale both axis of the image" do
20
+ subject.resample_nearest_neighbor(45, 45).should == reference_canvas('clock_nn_xup_yup')
21
+ end
22
+
23
+ it "should downscale both axis of the image" do
24
+ subject.resample_nearest_neighbor(12, 12).should == reference_canvas('clock_nn_xdown_ydown')
25
+ end
26
+
27
+ it "should downscale the x-axis and upscale the y-axis of the image" do
28
+ subject.resample_nearest_neighbor(20, 50).should == reference_canvas('clock_nn_xdown_yup')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChunkyPNG::Canvas do
4
+
5
+ describe '#to_rgba_stream' do
6
+ before { File.open(resource_file('pixelstream.rgba'), 'rb') { |f| @reference_data = f.read } }
7
+
8
+ it "should load an image correctly from a datastream" do
9
+ reference_canvas('pixelstream_reference').to_rgba_stream.should == @reference_data
10
+ end
11
+ end
12
+
13
+ describe '#to_rgb_stream' do
14
+ before { File.open(resource_file('pixelstream.rgb'), 'rb') { |f| @reference_data = f.read } }
15
+
16
+ it "should load an image correctly from a datastream" do
17
+ reference_canvas('pixelstream_reference').to_rgb_stream.should == @reference_data
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChunkyPNG::Canvas do
4
+
5
+ describe '.from_rgb_stream' do
6
+ it "should load an image correctly from a datastream" do
7
+ File.open(resource_file('pixelstream.rgb')) do |stream|
8
+ matrix = ChunkyPNG::Canvas.from_rgb_stream(240, 180, stream)
9
+ matrix.should == reference_canvas('pixelstream_reference')
10
+ end
11
+ end
12
+ end
13
+
14
+ describe '.from_rgba_stream' do
15
+ it "should load an image correctly from a datastream" do
16
+ File.open(resource_file('pixelstream.rgba')) do |stream|
17
+ matrix = ChunkyPNG::Canvas.from_rgba_stream(240, 180, stream)
18
+ matrix.should == reference_canvas('pixelstream_reference')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,37 +2,150 @@ require 'spec_helper'
2
2
 
3
3
  describe ChunkyPNG::Canvas do
4
4
 
5
- describe '.from_rgb_stream' do
6
- it "should load an image correctly from a datastream" do
7
- File.open(resource_file('pixelstream.rgb')) do |stream|
8
- matrix = ChunkyPNG::Canvas.from_rgb_stream(240, 180, stream)
9
- matrix.should == reference_canvas('pixelstream_reference')
10
- end
5
+ subject { ChunkyPNG::Canvas.new(1, 1, ChunkyPNG::Color::WHITE) }
6
+
7
+ it { should respond_to(:width) }
8
+ it { should respond_to(:height) }
9
+ it { should respond_to(:pixels) }
10
+
11
+ describe '#dimension' do
12
+ it "should return the dimensions as a Dimension instance" do
13
+ subject.dimension.should == ChunkyPNG::Dimension('1x1')
14
+ end
15
+ end
16
+
17
+ describe '#area' do
18
+ it "should return the dimensions as two-item array" do
19
+ subject.area.should == ChunkyPNG::Dimension('1x1').area
11
20
  end
12
21
  end
13
22
 
14
- describe '.from_rgba_stream' do
15
- it "should load an image correctly from a datastream" do
16
- File.open(resource_file('pixelstream.rgba')) do |stream|
17
- matrix = ChunkyPNG::Canvas.from_rgba_stream(240, 180, stream)
18
- matrix.should == reference_canvas('pixelstream_reference')
19
- end
23
+ describe '#include?' do
24
+ it "should return true if the coordinates are within bounds, false otherwise" do
25
+ subject.include_xy?( 0, 0).should be_true
26
+
27
+ subject.include_xy?(-1, 0).should be_false
28
+ subject.include_xy?( 1, 0).should be_false
29
+ subject.include_xy?( 0, -1).should be_false
30
+ subject.include_xy?( 0, 1).should be_false
31
+ subject.include_xy?(-1, -1).should be_false
32
+ subject.include_xy?(-1, 1).should be_false
33
+ subject.include_xy?( 1, -1).should be_false
34
+ subject.include_xy?( 1, 1).should be_false
35
+ end
36
+
37
+ it "should accept strings, arrays, hashes and points as well" do
38
+ subject.should include('0, 0')
39
+ subject.should_not include('0, 1')
40
+ subject.should include([0, 0])
41
+ subject.should_not include([0, 1])
42
+ subject.should include(:y => 0, :x => 0)
43
+ subject.should_not include(:y => 1, :x => 0)
44
+ subject.should include(ChunkyPNG::Point.new(0, 0))
45
+ subject.should_not include(ChunkyPNG::Point.new(0, 1))
20
46
  end
21
47
  end
22
48
 
23
- describe '#to_rgba_stream' do
24
- before { File.open(resource_file('pixelstream.rgba'), 'rb') { |f| @reference_data = f.read } }
49
+ describe '#include_x?' do
50
+ it "should return true if the x-coordinate is within bounds, false otherwise" do
51
+ subject.include_x?( 0).should be_true
52
+ subject.include_x?(-1).should be_false
53
+ subject.include_x?( 1).should be_false
54
+ end
55
+ end
56
+
57
+ describe '#include_y?' do
58
+ it "should return true if the y-coordinate is within bounds, false otherwise" do
59
+ subject.include_y?( 0).should be_true
60
+ subject.include_y?(-1).should be_false
61
+ subject.include_y?( 1).should be_false
62
+ end
63
+ end
64
+
65
+ describe '#assert_xy!' do
66
+ it "should not raise an exception if the coordinates are within bounds" do
67
+ subject.should_receive(:include_xy?).with(0, 0).and_return(true)
68
+ lambda { subject.send(:assert_xy!, 0, 0) }.should_not raise_error
69
+ end
25
70
 
26
- it "should load an image correctly from a datastream" do
27
- reference_canvas('pixelstream_reference').to_rgba_stream.should == @reference_data
71
+ it "should raise an exception if the coordinates are out of bounds bounds" do
72
+ subject.should_receive(:include_xy?).with(0, -1).and_return(false)
73
+ lambda { subject.send(:assert_xy!, 0, -1) }.should raise_error(ChunkyPNG::OutOfBounds)
28
74
  end
29
75
  end
30
-
31
- describe '#to_rgb_stream' do
32
- before { File.open(resource_file('pixelstream.rgb'), 'rb') { |f| @reference_data = f.read } }
76
+
77
+ describe '#assert_x!' do
78
+ it "should not raise an exception if the x-coordinate is within bounds" do
79
+ subject.should_receive(:include_x?).with(0).and_return(true)
80
+ lambda { subject.send(:assert_x!, 0) }.should_not raise_error
81
+ end
82
+
83
+ it "should raise an exception if the x-coordinate is out of bounds bounds" do
84
+ subject.should_receive(:include_y?).with(-1).and_return(false)
85
+ lambda { subject.send(:assert_y!, -1) }.should raise_error(ChunkyPNG::OutOfBounds)
86
+ end
87
+ end
88
+
89
+ describe '#[]' do
90
+ it "should return the pixel value if the coordinates are within bounds" do
91
+ subject[0, 0].should == ChunkyPNG::Color::WHITE
92
+ end
93
+
94
+ it "should assert the coordinates to be within bounds" do
95
+ subject.should_receive(:assert_xy!).with(0, 0)
96
+ subject[0, 0]
97
+ end
98
+ end
99
+
100
+ describe '#get_pixel' do
101
+ it "should return the pixel value if the coordinates are within bounds" do
102
+ subject.get_pixel(0, 0).should == ChunkyPNG::Color::WHITE
103
+ end
33
104
 
34
- it "should load an image correctly from a datastream" do
35
- reference_canvas('pixelstream_reference').to_rgb_stream.should == @reference_data
105
+ it "should not assert nor check the coordinates" do
106
+ subject.should_not_receive(:assert_xy!)
107
+ subject.should_not_receive(:include_xy?)
108
+ subject.get_pixel(0, 0)
109
+ end
110
+ end
111
+
112
+ describe '#[]=' do
113
+ it "should change the pixel's color value" do
114
+ lambda { subject[0, 0] = ChunkyPNG::Color::BLACK }.should change { subject[0, 0] }.from(ChunkyPNG::Color::WHITE).to(ChunkyPNG::Color::BLACK)
115
+ end
116
+
117
+ it "should assert the bounds of the image" do
118
+ subject.should_receive(:assert_xy!).with(0, 0)
119
+ subject[0, 0] = ChunkyPNG::Color::BLACK
120
+ end
121
+ end
122
+
123
+ describe 'set_pixel' do
124
+ it "should change the pixel's color value" do
125
+ lambda { subject.set_pixel(0, 0, ChunkyPNG::Color::BLACK) }.should change { subject[0, 0] }.from(ChunkyPNG::Color::WHITE).to(ChunkyPNG::Color::BLACK)
126
+ end
127
+
128
+ it "should not assert or check the bounds of the image" do
129
+ subject.should_not_receive(:assert_xy!)
130
+ subject.should_not_receive(:include_xy?)
131
+ subject.set_pixel(0, 0, ChunkyPNG::Color::BLACK)
132
+ end
133
+ end
134
+
135
+ describe '#set_pixel_if_within_bounds' do
136
+ it "should change the pixel's color value" do
137
+ lambda { subject.set_pixel_if_within_bounds(0, 0, ChunkyPNG::Color::BLACK) }.should change { subject[0, 0] }.from(ChunkyPNG::Color::WHITE).to(ChunkyPNG::Color::BLACK)
138
+ end
139
+
140
+ it "should not assert, but only check the coordinates" do
141
+ subject.should_not_receive(:assert_xy!)
142
+ subject.should_receive(:include_xy?).with(0, 0)
143
+ subject.set_pixel_if_within_bounds(0, 0, ChunkyPNG::Color::BLACK)
144
+ end
145
+
146
+ it "should do nothing if the coordinates are out of bounds" do
147
+ subject.set_pixel_if_within_bounds(-1, 1, ChunkyPNG::Color::BLACK).should be_nil
148
+ subject[0, 0].should == ChunkyPNG::Color::WHITE
36
149
  end
37
150
  end
38
151
 
@@ -50,7 +163,7 @@ describe ChunkyPNG::Canvas do
50
163
  data.should == [65535, 268500991, 536936447, 805371903, 1073807359, 1342242815, 1610678271, 1879113727, 2147549183, 2415984639, 2684420095, 2952855551, 3221291007, 3489726463, 3758161919, 4026597375]
51
164
  end
52
165
  end
53
-
166
+
54
167
  describe '#column' do
55
168
  before { @canvas = reference_canvas('operations') }
56
169
 
@@ -65,4 +178,20 @@ describe ChunkyPNG::Canvas do
65
178
  data.should == [65535, 1114111, 2162687, 3211263, 4259839, 5308415, 6356991, 7405567, 8454143, 9502719, 10551295, 11599871, 12648447, 13697023, 14745599, 15794175]
66
179
  end
67
180
  end
181
+
182
+ describe '#replace_canvas' do
183
+ it "should change the dimension of the canvas" do
184
+ lambda { subject.send(:replace_canvas!, 2, 2, [1,2,3,4]) }.should change(subject, :dimension).
185
+ from(ChunkyPNG::Dimension('1x1')).to(ChunkyPNG::Dimension('2x2'))
186
+ end
187
+
188
+ it "should change the pixel array" do
189
+ lambda { subject.send(:replace_canvas!, 2, 2, [1,2,3,4]) }.should change(subject, :pixels).
190
+ from([ChunkyPNG::Color('white')]).to([1,2,3,4])
191
+ end
192
+
193
+ it "should return itself" do
194
+ subject.send(:replace_canvas!, 2, 2, [1,2,3,4]).should equal(subject)
195
+ end
196
+ end
68
197
  end
@@ -10,6 +10,27 @@ describe ChunkyPNG::Color do
10
10
  @non_opaque = 0x0a649664
11
11
  @fully_transparent = 0x0a649600
12
12
  end
13
+
14
+ it "should interpret 4 arguments as RGBA values" do
15
+ ChunkyPNG::Color(1, 2, 3, 4).should == ChunkyPNG::Color.rgba(1, 2, 3, 4)
16
+ end
17
+
18
+ it "should interpret 3 arguments as RGBA values" do
19
+ ChunkyPNG::Color(1, 2, 3).should == ChunkyPNG::Color.rgb(1, 2, 3)
20
+ end
21
+
22
+ it "should interpret a hex string correctly" do
23
+ ChunkyPNG::Color('0x0a649664').should == ChunkyPNG::Color.from_hex('#0a649664')
24
+ ChunkyPNG::Color('0x0a649664', 0xff).should == ChunkyPNG::Color.from_hex('#0a6496', 0xff)
25
+ end
26
+
27
+ it "should interpret a color name correctly" do
28
+ ChunkyPNG::Color(:spring_green).should == 0x00ff7fff
29
+ ChunkyPNG::Color('spring green').should == 0x00ff7fff
30
+ ChunkyPNG::Color('spring green @ 0.6666').should == 0x00ff7faa
31
+ ChunkyPNG::Color('spring green', 0xaa).should == 0x00ff7faa
32
+ ChunkyPNG::Color('spring green @ 0.6666', 0xff).should == 0x00ff7fff
33
+ end
13
34
 
14
35
  describe '#pixel_bytesize' do
15
36
  it "should return the normal amount of bytes with a bit depth of 8" do
@@ -59,6 +80,38 @@ describe ChunkyPNG::Color do
59
80
  from_hex('#0a6496').should == @opaque
60
81
  from_hex('0x0a6496').should == @opaque
61
82
  end
83
+
84
+ it "should allow setting opacity explicitely" do
85
+ from_hex('0x0a6496', 0x64).should == @non_opaque
86
+ from_hex('#0a6496', 0x64).should == @non_opaque
87
+ end
88
+ end
89
+
90
+ describe '#html_color' do
91
+ it "should find the correct color value" do
92
+ html_color(:springgreen).should == 0x00ff7fff
93
+ html_color(:spring_green).should == 0x00ff7fff
94
+ html_color('springgreen').should == 0x00ff7fff
95
+ html_color('spring green').should == 0x00ff7fff
96
+ html_color('SpringGreen').should == 0x00ff7fff
97
+ html_color('SPRING_GREEN').should == 0x00ff7fff
98
+ end
99
+
100
+ it "should set the opacity level explicitely" do
101
+ html_color(:springgreen, 0xff).should == 0x00ff7fff
102
+ html_color(:springgreen, 0xaa).should == 0x00ff7faa
103
+ html_color(:springgreen, 0x00).should == 0x00ff7f00
104
+ end
105
+
106
+ it "should set opacity levels from the color name" do
107
+ html_color('Spring green @ 1.0').should == 0x00ff7fff
108
+ html_color('Spring green @ 0.666').should == 0x00ff7faa
109
+ html_color('Spring green @ 0.0').should == 0x00ff7f00
110
+ end
111
+
112
+ it "should raise for an unkown color name" do
113
+ lambda { html_color(:nonsense) }.should raise_error(ChunkyPNG::Exception)
114
+ end
62
115
  end
63
116
 
64
117
  describe '#opaque?' do
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChunkyPNG::Dimension do
4
+ subject { ChunkyPNG::Dimension.new(2, 3) }
5
+
6
+ it { should respond_to(:width) }
7
+ it { should respond_to(:height) }
8
+
9
+ describe '#area' do
10
+ it "should calculate the area correctly" do
11
+ subject.area.should == 6
12
+ end
13
+ end
14
+ end
15
+
16
+ describe 'ChunkyPNG.Dimension' do
17
+ subject { ChunkyPNG::Dimension.new(1, 2) }
18
+
19
+ it "should create a point from a 2-item array" do
20
+ ChunkyPNG::Dimension([1, 2]).should == subject
21
+ ChunkyPNG::Dimension(['1', '2']).should == subject
22
+ end
23
+
24
+ it "should create a point from a hash with x and y keys" do
25
+ ChunkyPNG::Dimension(:width => 1, :height => 2).should == subject
26
+ ChunkyPNG::Dimension('width' => '1', 'height' => '2').should == subject
27
+ end
28
+
29
+ it "should create a point from a point-like string" do
30
+ [
31
+ ChunkyPNG::Dimension('1,2'),
32
+ ChunkyPNG::Dimension('1 2'),
33
+ ChunkyPNG::Dimension('(1 , 2)'),
34
+ ChunkyPNG::Dimension("{1x2}"),
35
+ ChunkyPNG::Dimension("[1\t2}"),
36
+ ].all? { |point| point == subject }
37
+ end
38
+
39
+ it "should raise an exception if the input is not understood" do
40
+ lambda { ChunkyPNG::Dimension(Object.new) }.should raise_error(ChunkyPNG::ExpectationFailed)
41
+ lambda { ChunkyPNG::Dimension(1, 2, 3) }.should raise_error(ChunkyPNG::ExpectationFailed)
42
+ end
43
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChunkyPNG::Point do
4
+
5
+ subject { ChunkyPNG::Point.new(1, 2) }
6
+
7
+ it { should respond_to(:x) }
8
+ it { should respond_to(:y) }
9
+
10
+ describe '#within_bounds?' do
11
+ it { should be_within_bounds(2, 3) }
12
+ it { should_not be_within_bounds('1x3') }
13
+ it { should_not be_within_bounds(2, 2) }
14
+ it { should_not be_within_bounds('[1 2]') }
15
+ end
16
+
17
+ describe '#<=>' do
18
+ it "should return 0 if the coordinates are identical" do
19
+ (subject <=> ChunkyPNG::Point.new(1, 2)).should == 0
20
+ end
21
+
22
+ it "should return -1 if the y coordinate is smaller than the other one" do
23
+ (subject <=> ChunkyPNG::Point.new(1, 3)).should == -1
24
+ (subject <=> ChunkyPNG::Point.new(0, 3)).should == -1 # x doesn't matter
25
+ (subject <=> ChunkyPNG::Point.new(2, 3)).should == -1 # x doesn't matter
26
+ end
27
+
28
+ it "should return 1 if the y coordinate is larger than the other one" do
29
+ (subject <=> ChunkyPNG::Point.new(1, 0)).should == 1
30
+ (subject <=> ChunkyPNG::Point.new(0, 0)).should == 1 # x doesn't matter
31
+ (subject <=> ChunkyPNG::Point.new(2, 0)).should == 1 # x doesn't matter
32
+ end
33
+
34
+ it "should return -1 if the x coordinate is smaller and y is the same" do
35
+ (subject <=> ChunkyPNG::Point.new(2, 2)).should == -1
36
+ end
37
+
38
+ it "should return 1 if the x coordinate is larger and y is the same" do
39
+ (subject <=> ChunkyPNG::Point.new(0, 2)).should == 1
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'ChunkyPNG.Point' do
45
+ subject { ChunkyPNG::Point.new(1, 2) }
46
+
47
+ it "should create a point from a 2-item array" do
48
+ ChunkyPNG::Point([1, 2]).should == subject
49
+ ChunkyPNG::Point(['1', '2']).should == subject
50
+ end
51
+
52
+ it "should create a point from a hash with x and y keys" do
53
+ ChunkyPNG::Point(:x => 1, :y => 2).should == subject
54
+ ChunkyPNG::Point('x' => '1', 'y' => '2').should == subject
55
+ end
56
+
57
+ it "should create a point from a point-like string" do
58
+ [
59
+ ChunkyPNG::Point('1,2'),
60
+ ChunkyPNG::Point('1 2'),
61
+ ChunkyPNG::Point('(1 , 2)'),
62
+ ChunkyPNG::Point("{1,\t2}"),
63
+ ChunkyPNG::Point("[1 2}"),
64
+ ].all? { |point| point == subject }
65
+ end
66
+
67
+ it "should raise an exception if the input is not understood" do
68
+ lambda { ChunkyPNG::Point(Object.new) }.should raise_error(ChunkyPNG::ExpectationFailed)
69
+ lambda { ChunkyPNG::Point(1, 2, 3) }.should raise_error(ChunkyPNG::ExpectationFailed)
70
+ end
71
+ end