axon 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG.rdoc +9 -3
  2. data/README.rdoc +29 -36
  3. data/Rakefile +26 -21
  4. data/TODO.rdoc +1 -6
  5. data/ext/axon/axon.c +6 -15
  6. data/ext/axon/extconf.rb +19 -9
  7. data/ext/axon/interpolation.c +147 -0
  8. data/ext/axon/jpeg.c +1207 -0
  9. data/ext/axon/png.c +542 -0
  10. data/lib/axon.rb +235 -32
  11. data/lib/axon/cropper.rb +80 -18
  12. data/lib/axon/fit.rb +69 -19
  13. data/lib/axon/generators.rb +109 -0
  14. data/lib/axon/scalers.rb +160 -0
  15. data/test/helper.rb +151 -6
  16. data/test/reader_tests.rb +37 -82
  17. data/test/scaler_tests.rb +102 -0
  18. data/test/stress_helper.rb +58 -0
  19. data/test/stress_tests.rb +8 -5
  20. data/test/test_bilinear_scaler.rb +60 -2
  21. data/test/test_cropper.rb +68 -1
  22. data/test/test_fit.rb +35 -0
  23. data/test/test_generators.rb +21 -0
  24. data/test/test_image.rb +61 -0
  25. data/test/test_jpeg_reader.rb +96 -94
  26. data/test/test_jpeg_writer.rb +95 -8
  27. data/test/test_nearest_neighbor_scaler.rb +28 -4
  28. data/test/test_png_reader.rb +12 -8
  29. data/test/test_png_writer.rb +8 -6
  30. data/test/writer_tests.rb +129 -111
  31. metadata +71 -128
  32. data/.gemtest +0 -0
  33. data/ext/axon/bilinear_interpolation.c +0 -115
  34. data/ext/axon/interpolation.h +0 -7
  35. data/ext/axon/jpeg_common.c +0 -118
  36. data/ext/axon/jpeg_common.h +0 -37
  37. data/ext/axon/jpeg_native_writer.c +0 -248
  38. data/ext/axon/jpeg_reader.c +0 -774
  39. data/ext/axon/nearest_neighbor_interpolation.c +0 -50
  40. data/ext/axon/png_common.c +0 -21
  41. data/ext/axon/png_common.h +0 -18
  42. data/ext/axon/png_native_writer.c +0 -166
  43. data/ext/axon/png_reader.c +0 -381
  44. data/lib/axon/axon.so +0 -0
  45. data/lib/axon/bilinear_scaler.rb +0 -60
  46. data/lib/axon/jpeg_writer.rb +0 -41
  47. data/lib/axon/nearest_neighbor_scaler.rb +0 -39
  48. data/lib/axon/png_writer.rb +0 -35
  49. data/lib/axon/scaler.rb +0 -41
  50. data/lib/axon/solid.rb +0 -23
  51. data/test/_test_readme.rb +0 -34
  52. data/test/test_exif.rb +0 -39
  53. data/test/test_generator.rb +0 -10
  54. data/test/test_icc.rb +0 -18
  55. data/test/test_jpeg.rb +0 -9
  56. data/test/test_png.rb +0 -9
@@ -0,0 +1,109 @@
1
+ module Axon
2
+ # == A Noise Image Generator
3
+ #
4
+ # Axon::Noise will generate images with random pixel color values.
5
+ #
6
+ # == Example
7
+ #
8
+ # Axon::Noise.new(100, 200, :components => 1)
9
+ #
10
+ class Noise
11
+ # The width of the generated image.
12
+ attr_reader :width
13
+
14
+ # The height of the generated image.
15
+ attr_reader :height
16
+
17
+ # The color model of the generated image.
18
+ attr_reader :color_model
19
+
20
+ # The components in the generated image.
21
+ attr_reader :components
22
+
23
+ # The index of the next line that will be fetched by gets, starting at 0.
24
+ attr_reader :lineno
25
+
26
+ # :call-seq:
27
+ # Noise.new(width, height, options = {})
28
+ #
29
+ # Creates a new noise image object with dimensions +width+ x +height+.
30
+ #
31
+ # +options+ may contain the following optional hash key values:
32
+ #
33
+ # * :color_model -- The color model of the generated image.
34
+ # * :components -- The number of components in the generated image.
35
+ #
36
+ def initialize(width, height, options=nil)
37
+ options ||= {}
38
+
39
+ @width = width
40
+ @height = height
41
+ @color_model = options[:color_model] || :RGB
42
+ @components = options[:components] || 3
43
+ @lineno = 0
44
+ @empty_string = String.new
45
+ if @empty_string.respond_to? :force_encoding
46
+ @empty_string.force_encoding('BINARY')
47
+ end
48
+ end
49
+
50
+ # Gets the next scanline from the generated image.
51
+ #
52
+ def gets
53
+ return nil if @lineno >= @height
54
+ sl = @empty_string.dup
55
+ (@width * @components).times{ sl << rand(2**8) }
56
+ @lineno += 1
57
+ sl
58
+ end
59
+ end
60
+
61
+ # == A Solid Color Image Generator
62
+ #
63
+ # Axon::Solid will generate images with a solid color value.
64
+ #
65
+ # == Example
66
+ #
67
+ # Axon::Solid.new(100, 200, "\x0A\x14\x69")
68
+ #
69
+ class Solid
70
+ # The width of the generated image.
71
+ attr_reader :width
72
+
73
+ # The height of the generated image.
74
+ attr_reader :height
75
+
76
+ # The color model of the generated image.
77
+ attr_reader :color_model
78
+
79
+ # The components in the generated image.
80
+ attr_reader :components
81
+
82
+ # The index of the next line that will be fetched by gets, starting at 0.
83
+ attr_reader :lineno
84
+
85
+ # :call-seq:
86
+ # Solid.new(width, height, color = "\x00\x00\x00", color_model = :RGB)
87
+ #
88
+ # Creates a new solid color image object with dimensions +width+ x +height+.
89
+ #
90
+ # The optional argument +color+ is the binary value that will be assigned
91
+ # to each pixel.
92
+ #
93
+ def initialize(width, height, color=nil, color_model=nil)
94
+ @width, @height = width, height
95
+ @color = color || "\x00\x00\x00"
96
+ @color_model = color_model || :RGB
97
+ @components = @color.size
98
+ @lineno = 0
99
+ end
100
+
101
+ # Gets the next scanline from the generated image.
102
+ #
103
+ def gets
104
+ return nil if @lineno >= @height
105
+ @lineno += 1
106
+ @color * width
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,160 @@
1
+ module Axon
2
+ # == A Nearest-neighbor Image Scaler
3
+ #
4
+ # Axon::NearestNeighborScaler scales images quickly using the nearest-neighbor
5
+ # interpolation method.
6
+ #
7
+ # Nearest-neighbor interpolation selects the value of the nearest pixel when
8
+ # calculating colors in the scaled image.
9
+ #
10
+ # == Example
11
+ #
12
+ # n = Axon::NearestNeighborScaler.new(image_in, 50, 75)
13
+ # n.width # => 50
14
+ # n.height # => 75
15
+ # n.gets # => String
16
+ #
17
+ class NearestNeighborScaler
18
+ # The width of the generated image.
19
+ attr_reader :width
20
+
21
+ # The height of the generated image.
22
+ attr_reader :height
23
+
24
+ # The index of the next line that will be fetched by gets, starting at 0.
25
+ attr_reader :lineno
26
+
27
+ # :call-seq:
28
+ # NearestNeighborScaler.new(image_in, width, height)
29
+ #
30
+ # Scales +image_in+ to the size +width+ x +height+ using the
31
+ # nearest-neighbor interpolation method.
32
+ #
33
+ def initialize(source, width, height)
34
+ raise ArgumentError if width < 1 || height < 1
35
+ @width = width
36
+ @height = height
37
+ @source = source
38
+ @lineno = 0
39
+ @buf = nil
40
+ end
41
+
42
+ # Gets the components in the scaled image. Same as the components of the
43
+ # source image.
44
+ #
45
+ def components
46
+ @source.components
47
+ end
48
+
49
+ # Gets the color model of the scaled image. Same as the color model of the
50
+ # source image.
51
+ #
52
+ def color_model
53
+ @source.color_model
54
+ end
55
+
56
+ # Gets the next scanline from the cropped image.
57
+ #
58
+ def gets
59
+ return nil if @lineno >= @height
60
+ sample = (@lineno * @source.height / @height.to_f).floor
61
+ @lineno += 1
62
+ Interpolation.nearest(get_buf(sample), @width, components)
63
+ end
64
+
65
+ private
66
+
67
+ def get_buf(line)
68
+ @buf ||= @source.gets
69
+ (line + 1 - @source.lineno).times{ @buf = @source.gets }
70
+ @buf
71
+ end
72
+ end
73
+
74
+ # == A Bilinear Image Scaler
75
+ #
76
+ # Axon::BilinearScaler scales images using the bilinear interpolation method.
77
+ #
78
+ # Bilinear interpolation calculates the color values in the resulting image by
79
+ # looking at the four nearest pixels for each pixel in the resulting image.
80
+ #
81
+ # This gives a more accurate representation than nearest-neighbor
82
+ # interpolation, at the expense of slightly blurring the resulting image.
83
+ #
84
+ # == Example
85
+ #
86
+ # n = Axon::BilinearScaler.new(image_in, 50, 75)
87
+ # n.width # => 50
88
+ # n.height # => 75
89
+ # n.gets # => String
90
+ #
91
+ class BilinearScaler
92
+ # The width of the generated image.
93
+ attr_reader :width
94
+
95
+ # The height of the generated image.
96
+ attr_reader :height
97
+
98
+ # The index of the next line that will be fetched by gets, starting at 0.
99
+ attr_reader :lineno
100
+
101
+ # :call-seq:
102
+ # BilinearScaler.new(image_in, width, height)
103
+ #
104
+ # Scales +image_in+ to the size +width+ x +height+ using the bilinear
105
+ # interpolation method.
106
+ #
107
+ def initialize(source, width, height)
108
+ raise ArgumentError if width < 1 || height < 1
109
+ @width = width
110
+ @height = height
111
+ @source = source
112
+ @lineno = 0
113
+ @buf1 = nil
114
+ @buf2 = nil
115
+ end
116
+
117
+ # Gets the components in the scaled image. Same as the components of the
118
+ # source image.
119
+ #
120
+ def components
121
+ @source.components
122
+ end
123
+
124
+ # Gets the color model of the scaled image. Same as the color model of the
125
+ # source image.
126
+ #
127
+ def color_model
128
+ @source.color_model
129
+ end
130
+
131
+ # Gets the next scanline from the cropped image.
132
+ #
133
+ def gets
134
+ return nil if @lineno >= @height
135
+ sample = @lineno * @source.height / @height.to_f
136
+ sample_i = sample.to_i
137
+ ty = sample - sample_i
138
+ @lineno += 1
139
+ get_buf(sample_i)
140
+
141
+ Interpolation.bilinear(@buf1, @buf2, @width, ty, components)
142
+ end
143
+
144
+ private
145
+
146
+ def get_buf(line)
147
+ @buf1 = @buf2 = read_with_padding unless @buf2
148
+ (line + 2 - @source.lineno).times do
149
+ @buf1 = @buf2
150
+ @buf2 = read_with_padding if @source.lineno < @source.height
151
+ end
152
+ end
153
+
154
+ def read_with_padding
155
+ line = @source.gets
156
+ line << line[-@source.components, @source.components]
157
+ line
158
+ end
159
+ end
160
+ end
data/test/helper.rb CHANGED
@@ -1,17 +1,162 @@
1
+ require 'rubygems'
1
2
  require 'minitest/autorun'
2
3
  require 'axon'
3
4
  require 'stringio'
4
- require 'reader_tests'
5
- require 'writer_tests'
6
5
 
7
6
  module Axon
8
7
  class AxonTestCase < MiniTest::Unit::TestCase
9
-
10
- # Generate a solid velvet JPEG
8
+ # Generate a solid velvet image
11
9
  def setup
12
- @velvet = "\x0A\x14\x69"
13
- @image = Solid.new 10, 15, @velvet
10
+ super
11
+ @image = Solid.new 10, 15, "\x0A\x14\x69"
14
12
  @io_out = StringIO.new
13
+ @io_out.set_encoding 'ASCII-8BIT' if @io_out.respond_to?(:set_encoding)
14
+ end
15
+
16
+ def assert_image_dimensions(image, width, height)
17
+ cmp = image.components
18
+
19
+ assert_equal width, image.width
20
+ assert_equal height, image.height
21
+
22
+ height.times do |i|
23
+ assert_equal i, image.lineno
24
+ assert_equal width * cmp, image.gets.size, 'image.gets should return scanlines with length width'
25
+ end
26
+
27
+ assert_equal height, image.lineno
28
+ assert_nil image.gets, 'image.gets should be nil after reading height lines'
29
+ assert_equal height, image.lineno
30
+ end
31
+ end
32
+
33
+ class CustomError < RuntimeError; end
34
+
35
+ module CallOrReturnValue
36
+ def call_or_return(*args)
37
+ @custom.respond_to?(:call) ? @custom.call(@parent, *args) : @custom
38
+ end
39
+ end
40
+
41
+ class CustomImage
42
+ include CallOrReturnValue
43
+
44
+ def initialize(custom)
45
+ @parent = Solid.new 100, 200
46
+ @custom = custom
47
+ end
48
+
49
+ def height; @parent.height; end
50
+ def width; @parent.width; end
51
+ def color_model; @parent.color_model; end
52
+ def components; @parent.components; end
53
+ def gets; @parent.gets; end
54
+ def lineno; @parent.lineno; end
55
+ end
56
+
57
+ class CustomHeightImage < CustomImage
58
+ def height; call_or_return; end
59
+ end
60
+
61
+ class CustomWidthImage < CustomImage
62
+ def width; call_or_return; end
63
+ end
64
+
65
+ class CustomColorModelImage < CustomImage
66
+ def color_model; call_or_return; end
67
+ end
68
+
69
+ class CustomComponentsImage < CustomImage
70
+ def components; call_or_return; end
71
+ end
72
+
73
+ class CustomLinenoImage < CustomImage
74
+ def lineno; call_or_return; end
75
+ end
76
+
77
+ class CustomGetsImage < CustomImage
78
+ def gets; call_or_return; end
79
+ end
80
+
81
+ class CustomIO
82
+ include CallOrReturnValue
83
+
84
+ attr_accessor :custom
85
+
86
+ def initialize(custom, *args)
87
+ @parent = StringIO.new(*args)
88
+ @custom = custom
89
+ end
90
+
91
+ def write(*args); call_or_return(*args); end
92
+ def read(*args); call_or_return(*args); end
93
+ def string; @parent.string; end
94
+ end
95
+
96
+ class ArrayWrapper
97
+ attr_reader :lineno, :components, :color_model
98
+
99
+ def initialize(ary, components=nil, color_model=nil)
100
+ @ary = ary
101
+ @components = components || 3
102
+ @color_model = color_model || :RGB
103
+ @lineno = 0
104
+ end
105
+
106
+ def width
107
+ @ary[0].size
108
+ end
109
+
110
+ def height
111
+ @ary.size
112
+ end
113
+
114
+ def gets
115
+ return nil if @lineno > @ary.size
116
+ res = @ary[@lineno]
117
+ @lineno += 1
118
+ res
119
+ end
120
+ end
121
+
122
+ class Repeater
123
+ attr_reader :lineno
124
+
125
+ def initialize(source)
126
+ @source = source
127
+ @data = []
128
+ @lineno = 0
129
+ end
130
+
131
+ def components
132
+ @source.components
133
+ end
134
+
135
+ def color_model
136
+ @source.color_model
137
+ end
138
+
139
+ def height
140
+ @source.height
141
+ end
142
+
143
+ def width
144
+ @source.width
145
+ end
146
+
147
+ def gets
148
+ if @data.size <= @lineno
149
+ res = @source.gets.dup
150
+ return nil unless res
151
+ @data << res
152
+ end
153
+
154
+ @lineno += 1
155
+ @data[@lineno - 1].dup
156
+ end
157
+
158
+ def rewind
159
+ @lineno = 0
15
160
  end
16
161
  end
17
162
  end
data/test/reader_tests.rb CHANGED
@@ -1,115 +1,70 @@
1
1
  module Axon
2
2
  module ReaderTests
3
- def test_read_image_from_io
4
- assert_equal 10, @reader.width
5
- assert_equal 15, @reader.height
3
+ def test_header_dimensions
4
+ assert_equal @image.width, @reader.width
5
+ assert_equal @image.height, @reader.height
6
6
  end
7
7
 
8
- def test_read_image_from_string
9
- assert_equal 10, @reader.width
10
- assert_equal 15, @reader.height
8
+ def test_header_components
9
+ assert_equal @image.components, @reader.components
11
10
  end
12
11
 
13
- def test_num_components
14
- assert_equal 3, @reader.components
12
+ def test_header_color_model
13
+ assert_equal @image.color_model, @reader.color_model
15
14
  end
16
15
 
17
- def test_color_model
18
- assert_equal :RGB, @reader.color_model
19
- end
20
-
21
- class DoubleIO < StringIO
22
- def read(*args)
23
- s = super
24
- s[0..20] * 100
25
- end
26
- end
27
-
28
- def test_io_returns_too_much
29
- f = DoubleIO.new @data
30
- assert_raises(RuntimeError) { @readerclass.new f }
31
- end
32
-
33
- class NilIO
34
- def read(*args); end
16
+ def test_io_returns_too_much_data
17
+ io = CustomIO.new(Proc.new{ |io, *args| io.read(*args)[0..20] * 100 }, @data)
18
+ assert_raises(RuntimeError) { @readerclass.new io }
35
19
  end
36
20
 
37
21
  def test_io_returns_nil
38
- assert_raises(RuntimeError) { @readerclass.new NilIO.new }
22
+ assert_raises(RuntimeError) { @readerclass.new(CustomIO.new(nil)) }
39
23
  end
40
-
41
- class RaiseIO
42
- def read(*args)
43
- raise 'heck'
44
- end
45
- end
46
-
47
- def test_io_raises_exception
48
- assert_raises(RuntimeError) { @readerclass.new RaiseIO.new }
24
+
25
+ def test_io_returns_one_byte_at_a_time
26
+ io = CustomIO.new(Proc.new{ |io, len| io.read(len || 1) }, @data)
27
+ r = @readerclass.new(io)
28
+ r.gets
49
29
  end
50
30
 
51
- class EmptyStringIO
52
- def read(*args)
53
- ""
54
- end
31
+ def test_io_raises_exception
32
+ io = CustomIO.new(Proc.new{ raise CustomError }, @data)
33
+ assert_raises(CustomError) { @readerclass.new io }
55
34
  end
56
35
 
57
36
  def test_empty_string_io
58
- assert_raises(RuntimeError) { @readerclass.new EmptyStringIO.new }
59
- end
60
-
61
- class ThatsNotAStringIO
62
- def read(*args)
63
- :this_should_be_a_string
64
- end
37
+ assert_raises(RuntimeError) { @readerclass.new(CustomIO.new("")) }
65
38
  end
66
39
 
67
40
  def test_not_a_string_io
68
- assert_raises(TypeError) { @readerclass.new ThatsNotAStringIO.new }
41
+ assert_raises(TypeError) { @readerclass.new(CustomIO.new(:foo)) }
69
42
  end
70
-
71
- def test_each
72
- size = @reader.width * @reader.components
73
43
 
74
- @reader.each do |scan_line|
75
- assert_equal size, scan_line.size
44
+ def test_lineno
45
+ assert_equal 0, @reader.lineno
46
+ @reader.height.times do |i|
47
+ assert_equal i, @reader.lineno
48
+ @reader.gets
76
49
  end
50
+ assert_equal nil, @reader.gets
51
+ assert_equal @reader.height, @reader.lineno
77
52
  end
78
53
 
79
- def test_no_shenanigans_during_each
80
- @reader.each do |sl|
81
- assert_raises(RuntimeError) { @reader.each{} }
82
- break
83
- end
84
- end
85
-
86
- class OneExceptionIO < StringIO
87
- def initialize(*args)
88
- @raised_exception = false
89
- super
90
- end
91
-
92
- def read(*args)
93
- unless @raised_exception
94
- @raised_exception = true
95
- raise 'heck'
96
- end
97
- super
54
+ def test_gets
55
+ size = @reader.width * @reader.components
56
+ @reader.height.times do
57
+ assert_equal size, @reader.gets.size
98
58
  end
59
+ assert_equal nil, @reader.gets
99
60
  end
100
61
 
101
62
  def test_recovers_from_initial_io_exception
102
- io = OneExceptionIO.new @data
63
+ ex_io = CustomIO.new(Proc.new{ raise CustomError }, @data)
103
64
  r = @readerclass.allocate
104
- assert_raises(RuntimeError) { r.send(:initialize, io) }
105
- r.send(:initialize, io)
106
- r.each{ }
107
- end
108
-
109
- def test_multiple_each_calls
110
- @reader.each{ }
111
- @io_in.rewind
112
- @reader.each{ }
65
+ assert_raises(CustomError) { r.send(:initialize, ex_io) }
66
+ r.send(:initialize, StringIO.new(@data))
67
+ r.gets
113
68
  end
114
69
  end
115
70
  end