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