axon 0.0.2 → 0.1.0

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