axon 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +9 -3
- data/README.rdoc +29 -36
- data/Rakefile +26 -21
- data/TODO.rdoc +1 -6
- data/ext/axon/axon.c +6 -15
- data/ext/axon/extconf.rb +19 -9
- data/ext/axon/interpolation.c +147 -0
- data/ext/axon/jpeg.c +1207 -0
- data/ext/axon/png.c +542 -0
- data/lib/axon.rb +235 -32
- data/lib/axon/cropper.rb +80 -18
- data/lib/axon/fit.rb +69 -19
- data/lib/axon/generators.rb +109 -0
- data/lib/axon/scalers.rb +160 -0
- data/test/helper.rb +151 -6
- data/test/reader_tests.rb +37 -82
- data/test/scaler_tests.rb +102 -0
- data/test/stress_helper.rb +58 -0
- data/test/stress_tests.rb +8 -5
- data/test/test_bilinear_scaler.rb +60 -2
- data/test/test_cropper.rb +68 -1
- data/test/test_fit.rb +35 -0
- data/test/test_generators.rb +21 -0
- data/test/test_image.rb +61 -0
- data/test/test_jpeg_reader.rb +96 -94
- data/test/test_jpeg_writer.rb +95 -8
- data/test/test_nearest_neighbor_scaler.rb +28 -4
- data/test/test_png_reader.rb +12 -8
- data/test/test_png_writer.rb +8 -6
- data/test/writer_tests.rb +129 -111
- metadata +71 -128
- data/.gemtest +0 -0
- data/ext/axon/bilinear_interpolation.c +0 -115
- data/ext/axon/interpolation.h +0 -7
- data/ext/axon/jpeg_common.c +0 -118
- data/ext/axon/jpeg_common.h +0 -37
- data/ext/axon/jpeg_native_writer.c +0 -248
- data/ext/axon/jpeg_reader.c +0 -774
- data/ext/axon/nearest_neighbor_interpolation.c +0 -50
- data/ext/axon/png_common.c +0 -21
- data/ext/axon/png_common.h +0 -18
- data/ext/axon/png_native_writer.c +0 -166
- data/ext/axon/png_reader.c +0 -381
- data/lib/axon/axon.so +0 -0
- data/lib/axon/bilinear_scaler.rb +0 -60
- data/lib/axon/jpeg_writer.rb +0 -41
- data/lib/axon/nearest_neighbor_scaler.rb +0 -39
- data/lib/axon/png_writer.rb +0 -35
- data/lib/axon/scaler.rb +0 -41
- data/lib/axon/solid.rb +0 -23
- data/test/_test_readme.rb +0 -34
- data/test/test_exif.rb +0 -39
- data/test/test_generator.rb +0 -10
- data/test/test_icc.rb +0 -18
- data/test/test_jpeg.rb +0 -9
- data/test/test_png.rb +0 -9
@@ -0,0 +1,102 @@
|
|
1
|
+
module Axon
|
2
|
+
module ScalerTests
|
3
|
+
def test_dimensions
|
4
|
+
[
|
5
|
+
[7, 8],
|
6
|
+
[71, 82],
|
7
|
+
[@image.width, @image.height],
|
8
|
+
[1, 1]
|
9
|
+
].each do |dims|
|
10
|
+
im = Solid.new(100, 200)
|
11
|
+
|
12
|
+
w, h = dims[0], dims[1]
|
13
|
+
|
14
|
+
s = @scalerclass.new(im, w, h)
|
15
|
+
assert_image_dimensions(s, w, h)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_bad_dimensions
|
20
|
+
[ [0, 0], [0, 1], [1, 0], [-1, -3] ].each do |dims|
|
21
|
+
w, h = dims[0], dims[1]
|
22
|
+
|
23
|
+
assert_raises ArgumentError do
|
24
|
+
@scalerclass.new(@image, w, h).gets
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_scalar
|
30
|
+
[1, 2, 3, 1.1, 2.1, 0.9, 0.1].each do |scale|
|
31
|
+
im = Solid.new(100, 200)
|
32
|
+
width = (im.width * scale).to_i
|
33
|
+
height = (im.height * scale).to_i
|
34
|
+
|
35
|
+
s = @scalerclass.new(im, width, height)
|
36
|
+
assert_image_dimensions(s, width, height)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_bad_scalar
|
41
|
+
[0, 0.0, -1, -0.1].each do |scale|
|
42
|
+
assert_raises ArgumentError do
|
43
|
+
@scalerclass.new(@image, scale).gets
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def scale_test(*coords)
|
49
|
+
cache = Repeater.new(Noise.new 100, 200)
|
50
|
+
|
51
|
+
[1.0, 5.0, 0.3, 2.8].each do |scale|
|
52
|
+
width = (cache.width * scale).to_i
|
53
|
+
height = (cache.height * scale).to_i
|
54
|
+
|
55
|
+
cache.rewind
|
56
|
+
test_scaler = @scalertestclass.new(cache, width, height)
|
57
|
+
cache.rewind
|
58
|
+
s = @scalerclass.new(cache, width, height)
|
59
|
+
resized = []
|
60
|
+
s.height.times{ resized << s.gets }
|
61
|
+
|
62
|
+
right = width - 1
|
63
|
+
bottom = height - 1
|
64
|
+
|
65
|
+
coords.each do |coord|
|
66
|
+
x = coord[0]
|
67
|
+
y = coord[1]
|
68
|
+
|
69
|
+
x = right if x < 0
|
70
|
+
y = bottom if y < 0
|
71
|
+
|
72
|
+
result = resized[y][x * 3, 3].chars.map do |c|
|
73
|
+
c.respond_to?(:ord) ? c.ord : c[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
expected = test_scaler.calc(x, y)
|
77
|
+
3.times do |i|
|
78
|
+
assert_in_delta expected[i], result[i], 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_pixel_values
|
85
|
+
scale_test [10, 10], [29, 20], [9, 15]
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_edge_pixel_values
|
89
|
+
scale_test [0, 10], [29, 0], [-1, 15], [17, -1]
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_corner_pixel_values
|
93
|
+
scale_test [0, 0], [-1, 0], [0, -1], [-1, -1]
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_small_scaling
|
97
|
+
im = Solid.new(10, 20)
|
98
|
+
sc = @scalerclass.new(im, 2, 5)
|
99
|
+
assert_image_dimensions(sc, 2, 5)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
class MiniTest::Unit::TestCase
|
3
|
+
WARMUP_TESTS = 10
|
4
|
+
MAX_TEST_TIME = 3
|
5
|
+
MAX_TESTS = 3000
|
6
|
+
|
7
|
+
def vmsize
|
8
|
+
GC.start
|
9
|
+
GC.disable
|
10
|
+
vms = IO.read("/proc/#{$$}/status")[/^VmSize:\s+(\d+)/, 1].to_i
|
11
|
+
GC.enable
|
12
|
+
vms
|
13
|
+
end
|
14
|
+
|
15
|
+
def vmdelta
|
16
|
+
start_vmsize = vmsize
|
17
|
+
yield
|
18
|
+
vmsize - start_vmsize
|
19
|
+
end
|
20
|
+
|
21
|
+
def limited_loop
|
22
|
+
start_time = Time.now
|
23
|
+
test_counter = 0
|
24
|
+
|
25
|
+
until Time.now - start_time > MAX_TEST_TIME || test_counter > MAX_TESTS do
|
26
|
+
yield
|
27
|
+
test_counter += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
return test_counter
|
31
|
+
end
|
32
|
+
|
33
|
+
def mem_loop
|
34
|
+
result = yield
|
35
|
+
WARMUP_TESTS.times{ yield }
|
36
|
+
num_tests = 0
|
37
|
+
|
38
|
+
delta = vmdelta do
|
39
|
+
num_tests = limited_loop do
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# The idea here is that a legitimate memory leak will leak at least one
|
45
|
+
# byte per iteration.
|
46
|
+
if delta >= num_tests
|
47
|
+
puts "\n#{self.class} #{__name__}: #{delta}"
|
48
|
+
end
|
49
|
+
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
alias_method :old_run, :run
|
54
|
+
|
55
|
+
def run(*args)
|
56
|
+
mem_loop{ old_run(*args) }
|
57
|
+
end
|
58
|
+
end
|
data/test/stress_tests.rb
CHANGED
@@ -4,9 +4,12 @@ module Axon
|
|
4
4
|
class TestStressCases < AxonTestCase
|
5
5
|
def setup
|
6
6
|
@velvet = "\x0A\x14\x69"
|
7
|
-
@image =
|
8
|
-
|
9
|
-
|
7
|
+
@image = Solid.new 1000, 1500, @velvet
|
8
|
+
|
9
|
+
io = StringIO.new
|
10
|
+
JPEG.write(@image, io)
|
11
|
+
@jpeg_data = io.string
|
12
|
+
@readerclass = JPEG::Reader
|
10
13
|
end
|
11
14
|
|
12
15
|
class RandomRaiseIO
|
@@ -15,7 +18,7 @@ module Axon
|
|
15
18
|
@die_at = rand(source.size)
|
16
19
|
end
|
17
20
|
|
18
|
-
def read
|
21
|
+
def read(*args)
|
19
22
|
if @die_at < 2
|
20
23
|
raise 'random death!'
|
21
24
|
end
|
@@ -40,7 +43,7 @@ module Axon
|
|
40
43
|
@sequence = sequence
|
41
44
|
end
|
42
45
|
|
43
|
-
def read
|
46
|
+
def read(*args)
|
44
47
|
if !@sequence || @sequence.empty?
|
45
48
|
raise "random death!"
|
46
49
|
@sequence = nil
|
@@ -1,9 +1,67 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'scaler_tests'
|
2
3
|
|
3
4
|
module Axon
|
4
5
|
class TestBilinearScaler < AxonTestCase
|
5
|
-
|
6
|
-
|
6
|
+
include ScalerTests
|
7
|
+
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
@scalerclass = BilinearScaler
|
11
|
+
@scalertestclass = TestBilinearScaler
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestBilinearScaler
|
15
|
+
def initialize(original, width, height)
|
16
|
+
@original = original
|
17
|
+
@scale_x_inv = original.width / width.to_f
|
18
|
+
@scale_y_inv = original.height / height.to_f
|
19
|
+
|
20
|
+
# pad right and bottom with one pixel
|
21
|
+
@data = []
|
22
|
+
@original.height.times do
|
23
|
+
sl = @original.gets
|
24
|
+
@data << sl + sl[-@original.components, @original.components]
|
25
|
+
end
|
26
|
+
@data << @data.last
|
27
|
+
end
|
28
|
+
|
29
|
+
def calc(x, y)
|
30
|
+
sample_x = x * @scale_x_inv
|
31
|
+
sample_y = y * @scale_y_inv
|
32
|
+
|
33
|
+
sample_x_i = sample_x.floor
|
34
|
+
sample_y_i = sample_y.floor
|
35
|
+
|
36
|
+
tx = sample_x - sample_x_i
|
37
|
+
ty = sample_y - sample_y_i
|
38
|
+
|
39
|
+
ret = []
|
40
|
+
|
41
|
+
cmp = @original.components
|
42
|
+
|
43
|
+
cmp.times do |i|
|
44
|
+
smp_x = sample_x_i * @original.components + i
|
45
|
+
|
46
|
+
c00 = @data[sample_y_i][smp_x]
|
47
|
+
c10 = @data[sample_y_i][smp_x + cmp]
|
48
|
+
c01 = @data[sample_y_i + 1][smp_x]
|
49
|
+
c11 = @data[sample_y_i + 1][smp_x + cmp]
|
50
|
+
|
51
|
+
unless RUBY_VERSION == '1.8.7'
|
52
|
+
c00 = c00.ord
|
53
|
+
c10 = c10.ord
|
54
|
+
c01 = c01.ord
|
55
|
+
c11 = c11.ord
|
56
|
+
end
|
57
|
+
|
58
|
+
a = (1 - tx) * c00 + tx * c10
|
59
|
+
b = (1 - tx) * c01 + tx * c11
|
60
|
+
ret << ((1 - ty) * a + ty * b).floor
|
61
|
+
end
|
62
|
+
|
63
|
+
ret
|
64
|
+
end
|
7
65
|
end
|
8
66
|
end
|
9
67
|
end
|
data/test/test_cropper.rb
CHANGED
@@ -3,7 +3,74 @@ require 'helper'
|
|
3
3
|
module Axon
|
4
4
|
class TestCropper < AxonTestCase
|
5
5
|
def test_crop
|
6
|
-
|
6
|
+
i = Solid.new(200, 100)
|
7
|
+
c = Cropper.new(i, 4, 55)
|
8
|
+
|
9
|
+
assert_equal 4, c.width
|
10
|
+
assert_equal 55, c.height
|
11
|
+
|
12
|
+
55.times do |i|
|
13
|
+
assert_equal i, c.lineno
|
14
|
+
assert_equal 4 * c.components, c.gets.size
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal nil, c.gets
|
18
|
+
assert_equal 55, c.lineno
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_crop_too_tall
|
22
|
+
i = Solid.new(200, 100)
|
23
|
+
c = Cropper.new(i, 4, 133)
|
24
|
+
assert_image_dimensions(c, 4, 100)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_crop_too_wide
|
28
|
+
i = Solid.new(200, 100)
|
29
|
+
c = Cropper.new(i, 233, 33)
|
30
|
+
assert_image_dimensions(c, 200, 33)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_crop_negative_values
|
34
|
+
i = Solid.new(200, 100)
|
35
|
+
|
36
|
+
assert_raises(ArgumentError) do
|
37
|
+
Cropper.new(i, -233, 33)
|
38
|
+
end
|
39
|
+
|
40
|
+
assert_raises(ArgumentError) do
|
41
|
+
Cropper.new(i, 100, -33)
|
42
|
+
end
|
43
|
+
|
44
|
+
assert_raises(ArgumentError) do
|
45
|
+
Cropper.new(i, 0, 23)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_crop_with_offset
|
50
|
+
i = Noise.new(100, 200)
|
51
|
+
|
52
|
+
data = []
|
53
|
+
i.height.times{ data << i.gets }
|
54
|
+
c = Cropper.new(ArrayWrapper.new(data), 42, 51, 22, 31)
|
55
|
+
|
56
|
+
assert_equal 42, c.width
|
57
|
+
assert_equal 51, c.height
|
58
|
+
|
59
|
+
51.times do |i|
|
60
|
+
assert_equal i, c.lineno
|
61
|
+
sl = c.gets
|
62
|
+
assert_equal 42 * c.components, sl.size
|
63
|
+
assert_equal data[i + 31][22 * c.components], sl[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_equal nil, c.gets
|
67
|
+
assert_equal 51, c.lineno
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_crop_with_offset_oob
|
71
|
+
i = Solid.new(200, 100)
|
72
|
+
c = Cropper.new(i, 30, 40, 220, 10)
|
73
|
+
assert_nil c.gets
|
7
74
|
end
|
8
75
|
end
|
9
76
|
end
|
data/test/test_fit.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Axon
|
4
|
+
class TestFit < AxonTestCase
|
5
|
+
def test_width_determines_downscaling
|
6
|
+
im = Solid.new(10, 20)
|
7
|
+
r = Fit.new(im, 5, 20)
|
8
|
+
assert_image_dimensions(r, 5, 10)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_height_determines_downscaling
|
12
|
+
im = Solid.new(10, 20)
|
13
|
+
r = Fit.new(im, 20, 5)
|
14
|
+
assert_image_dimensions(r, 2, 5)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_width_determines_upscaling
|
18
|
+
im = Solid.new(10, 20)
|
19
|
+
r = Fit.new(im, 100, 900)
|
20
|
+
assert_image_dimensions(r, 100, 200)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_height_determines_upscaling
|
24
|
+
im = Solid.new(10, 20)
|
25
|
+
r = Fit.new(im, 1000, 200)
|
26
|
+
assert_image_dimensions(r, 100, 200)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_dimensions_unchanged
|
30
|
+
im = Solid.new(10, 20)
|
31
|
+
r = Fit.new(im, 10, 20)
|
32
|
+
assert_image_dimensions(r, 10, 20)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Axon
|
4
|
+
class TestGenerators < AxonTestCase
|
5
|
+
def test_solid_color
|
6
|
+
g = Solid.new 10, 15, "\x0A\x14\x69"
|
7
|
+
sl = g.gets
|
8
|
+
assert_equal "\x0A\x14\x69", sl[9, 3]
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_solid_dimensions
|
12
|
+
g = Solid.new(23, 33)
|
13
|
+
assert_image_dimensions(g, 23, 33)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_noise_dimensions
|
17
|
+
im = Noise.new(100, 200, :components => 1, :color_model => :GRAYSCALE)
|
18
|
+
assert_image_dimensions(im, 100, 200)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/test/test_image.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Axon
|
5
|
+
class TestImage < AxonTestCase
|
6
|
+
def setup
|
7
|
+
super
|
8
|
+
io = StringIO.new
|
9
|
+
JPEG.write(Solid.new(10, 20), io)
|
10
|
+
@jpeg_data = io.string
|
11
|
+
|
12
|
+
io = StringIO.new
|
13
|
+
PNG.write(Solid.new(10, 20), io)
|
14
|
+
@png_data = io.string
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_jpeg_helper_io
|
18
|
+
image = Axon.jpeg(StringIO.new(@jpeg_data))
|
19
|
+
assert_image_dimensions(image, 10, 20)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_jpeg_helper_stringdata
|
23
|
+
image = Axon.jpeg(@jpeg_data)
|
24
|
+
assert_image_dimensions(image, 10, 20)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_png_helper_io
|
28
|
+
image = Axon.png(StringIO.new(@png_data))
|
29
|
+
assert_image_dimensions(image, 10, 20)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_jpeg_helper_stringdata
|
33
|
+
image = Axon.png(@png_data)
|
34
|
+
assert_image_dimensions(image, 10, 20)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_bilinear
|
38
|
+
image = Axon.jpeg(@jpeg_data)
|
39
|
+
image.scale_bilinear(50, 75)
|
40
|
+
assert_image_dimensions(image, 50, 75)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_nearest
|
44
|
+
image = Axon.jpeg(@jpeg_data)
|
45
|
+
image.scale_nearest(50, 75)
|
46
|
+
assert_image_dimensions(image, 50, 75)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_fit
|
50
|
+
image = Axon.jpeg(@jpeg_data)
|
51
|
+
image.fit(50, 80)
|
52
|
+
assert_image_dimensions(image, 40, 80)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_crop
|
56
|
+
image = Axon.jpeg(@jpeg_data)
|
57
|
+
image.crop(5, 10, 6, 2)
|
58
|
+
assert_image_dimensions(image, 4, 10)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|