axon 0.1.1 → 0.2.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.
- data/CHANGELOG.rdoc +6 -0
- data/README.rdoc +23 -49
- data/Rakefile +33 -7
- data/TODO.rdoc +6 -1
- data/ext/axon/jpeg.c +32 -66
- data/ext/axon/png.c +46 -71
- data/ext/java/axon/AxonService.java +15 -0
- data/ext/java/axon/Interpolation.java +127 -0
- data/ext/java/axon/JPEG.java +119 -0
- data/ext/java/axon/JPEGReader.java +185 -0
- data/ext/java/axon/PNG.java +47 -0
- data/ext/java/axon/PNGReader.java +164 -0
- data/ext/java/axon/RubyImage.java +216 -0
- data/lib/axon.rb +93 -7
- data/lib/axon/alpha_stripper.rb +69 -0
- data/lib/axon/axon.jar +0 -0
- data/lib/axon/cropper.rb +6 -10
- data/lib/axon/fit.rb +53 -35
- data/lib/axon/generators.rb +1 -10
- data/lib/axon/scalers.rb +2 -16
- data/test/helper.rb +7 -9
- data/test/reader_tests.rb +0 -8
- data/test/test_alpha_stripper.rb +33 -0
- data/test/test_fit.rb +38 -0
- data/test/test_jpeg_reader.rb +49 -26
- data/test/test_jpeg_writer.rb +8 -0
- data/test/writer_tests.rb +51 -40
- metadata +96 -87
@@ -0,0 +1,69 @@
|
|
1
|
+
module Axon
|
2
|
+
|
3
|
+
# == Strips the Alpha Channel from an Image
|
4
|
+
#
|
5
|
+
# Axon::AlphaStripper Removes the Alpha Channel from an Image.
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
#
|
10
|
+
class AlphaStripper
|
11
|
+
|
12
|
+
# :call-seq:
|
13
|
+
# AlphaStripper.new(image_in)
|
14
|
+
#
|
15
|
+
# Removes the alpha channel from +image_in+.
|
16
|
+
#
|
17
|
+
def initialize(source)
|
18
|
+
@source = source
|
19
|
+
end
|
20
|
+
|
21
|
+
# Gets the height of the image. Same as the height of the source image.
|
22
|
+
#
|
23
|
+
def height
|
24
|
+
@source.height
|
25
|
+
end
|
26
|
+
|
27
|
+
# Gets the width of the image. Same as the width of the source image.
|
28
|
+
#
|
29
|
+
def width
|
30
|
+
@source.width
|
31
|
+
end
|
32
|
+
|
33
|
+
# Gets the components in the image.
|
34
|
+
#
|
35
|
+
def components
|
36
|
+
case @source.components
|
37
|
+
when 2 then 1
|
38
|
+
when 4 then 3
|
39
|
+
else @source.components
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gets the line number of the next scanline.
|
44
|
+
#
|
45
|
+
def lineno
|
46
|
+
@source.lineno
|
47
|
+
end
|
48
|
+
|
49
|
+
# Gets the next scanline from the image.
|
50
|
+
#
|
51
|
+
def gets
|
52
|
+
sl = @source.gets
|
53
|
+
return unless sl
|
54
|
+
|
55
|
+
# This should probably be done in C, but not seeing alpha stripping
|
56
|
+
# performance as terribly important right now.
|
57
|
+
case @source.components
|
58
|
+
when 2
|
59
|
+
(sl.size / 2).times{ |i| sl.slice!(1) }
|
60
|
+
sl
|
61
|
+
when 4
|
62
|
+
(sl.size / 4).times{ |i| sl.slice!(3) }
|
63
|
+
sl
|
64
|
+
else
|
65
|
+
sl
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/axon/axon.jar
ADDED
Binary file
|
data/lib/axon/cropper.rb
CHANGED
@@ -33,14 +33,17 @@ module Axon
|
|
33
33
|
# cropped image will be truncated at the boundary.
|
34
34
|
#
|
35
35
|
def initialize(source, width, height, x_offset=nil, y_offset=nil)
|
36
|
+
x_offset ||= 0
|
37
|
+
y_offset ||= 0
|
38
|
+
|
36
39
|
raise ArgumentError if width < 1 || height < 1
|
37
|
-
raise ArgumentError if x_offset
|
40
|
+
raise ArgumentError if x_offset < 0 || y_offset < 0
|
38
41
|
|
39
42
|
@source = source
|
40
43
|
@width = width
|
41
44
|
@height = height
|
42
|
-
@x_offset = x_offset
|
43
|
-
@y_offset = y_offset
|
45
|
+
@x_offset = x_offset
|
46
|
+
@y_offset = y_offset
|
44
47
|
@lineno = 0
|
45
48
|
end
|
46
49
|
|
@@ -71,13 +74,6 @@ module Axon
|
|
71
74
|
@source.components
|
72
75
|
end
|
73
76
|
|
74
|
-
# Gets the color model of the cropped image. Same as the color model of the
|
75
|
-
# source image.
|
76
|
-
#
|
77
|
-
def color_model
|
78
|
-
@source.color_model
|
79
|
-
end
|
80
|
-
|
81
77
|
# Gets the next scanline from the cropped image.
|
82
78
|
#
|
83
79
|
def gets
|
data/lib/axon/fit.rb
CHANGED
@@ -27,6 +27,7 @@ module Axon
|
|
27
27
|
#
|
28
28
|
def initialize(source, width, height)
|
29
29
|
@source, @fit_width, @fit_height = source, width, height
|
30
|
+
@aspect_ratio = width / height.to_f
|
30
31
|
@scaler = nil
|
31
32
|
end
|
32
33
|
|
@@ -37,24 +38,17 @@ module Axon
|
|
37
38
|
@source.components
|
38
39
|
end
|
39
40
|
|
40
|
-
# Gets the color model of the fitted image. Same as the color model of the
|
41
|
-
# source image.
|
42
|
-
#
|
43
|
-
def color_model
|
44
|
-
@source.color_model
|
45
|
-
end
|
46
|
-
|
47
41
|
# Gets the width of the fitted image. This will be the given width or less.
|
48
42
|
#
|
49
43
|
def width
|
50
|
-
@scaler ? @scaler.width :
|
44
|
+
@scaler ? @scaler.width : (@source.width * calc_fit_ratio).to_i
|
51
45
|
end
|
52
46
|
|
53
47
|
# Gets the height of the fitted image. This will be the given height or
|
54
48
|
# less.
|
55
49
|
#
|
56
50
|
def height
|
57
|
-
@scaler ? @scaler.height :
|
51
|
+
@scaler ? @scaler.height : (@source.height * calc_fit_ratio).to_i
|
58
52
|
end
|
59
53
|
|
60
54
|
# Gets the index of the next line that will be fetched by gets, starting at
|
@@ -73,44 +67,68 @@ module Axon
|
|
73
67
|
|
74
68
|
private
|
75
69
|
|
76
|
-
def calculate_width
|
77
|
-
(@source.width * calc_fit_ratio).to_i
|
78
|
-
end
|
79
|
-
|
80
|
-
def calculate_height
|
81
|
-
(@source.height * calc_fit_ratio).to_i
|
82
|
-
end
|
83
|
-
|
84
70
|
def get_scaler
|
85
71
|
r = calc_fit_ratio
|
72
|
+
return @source if r == 1
|
73
|
+
|
74
|
+
final_width = (r * @source.width).to_i
|
75
|
+
final_height = (r * @source.height).to_i
|
76
|
+
|
77
|
+
if @source.kind_of?(JPEG::Reader)
|
78
|
+
jpeg_scale_dct(r)
|
79
|
+
r = calc_fit_ratio
|
80
|
+
return @source if r == 1
|
81
|
+
end
|
86
82
|
|
87
83
|
if r > 1
|
88
|
-
NearestNeighborScaler.new(@source,
|
84
|
+
NearestNeighborScaler.new(@source, final_width, final_height)
|
89
85
|
elsif r < 1
|
90
|
-
|
91
|
-
@source.scale_denom = calc_jpeg_pre_shrink(r)
|
92
|
-
r = calc_fit_ratio
|
93
|
-
end
|
94
|
-
BilinearScaler.new(@source, width, height)
|
95
|
-
else
|
96
|
-
@source
|
86
|
+
BilinearScaler.new(@source, final_width, final_height)
|
97
87
|
end
|
98
88
|
end
|
99
89
|
|
100
|
-
|
90
|
+
def source_aspect_ratio
|
91
|
+
@source.width / @source.height.to_f
|
92
|
+
end
|
101
93
|
|
102
94
|
def calc_fit_ratio
|
103
|
-
|
104
|
-
|
105
|
-
|
95
|
+
if source_aspect_ratio > @aspect_ratio
|
96
|
+
@fit_width / @source.width.to_f
|
97
|
+
else
|
98
|
+
@fit_height / @source.height.to_f
|
99
|
+
end
|
106
100
|
end
|
107
101
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
102
|
+
# Some versions of libjpeg can perform DCT scaling during the jpeg decoding
|
103
|
+
# phase. This is fast and accurate scaling, so we want to take advantage of
|
104
|
+
# it if at all possible.
|
105
|
+
#
|
106
|
+
# Since this form of scaling only happens in increments, we probably won't
|
107
|
+
# be able to scale to the exact desired size, so our strategy is to scale to
|
108
|
+
# as close to the desired size as possible without scaling too much.
|
109
|
+
#
|
110
|
+
# This depends on our version of libjpeg:
|
111
|
+
# * libjpeg version 7 and greater can scale N/8 with all N from 1 to 16.
|
112
|
+
# * libjpeg version 6 and below can scale 1/N with all N from 1 to 8.
|
113
|
+
# * jruby doesn't do this at all
|
114
|
+
def jpeg_scale_dct(r)
|
115
|
+
return unless defined?(JPEG::LIB_VERSION)
|
116
|
+
if JPEG::LIB_VERSION >= 70
|
117
|
+
# when shrinking, we want scale_num to be the next highest integer
|
118
|
+
if r < 1
|
119
|
+
@source.scale_num = (r * 8).ceil
|
120
|
+
# when growing, we want scale_num to be the next lowest integer
|
121
|
+
else
|
122
|
+
@source.scale_num = (r * 8).to_i
|
123
|
+
end
|
124
|
+
else
|
125
|
+
if r <= 0.5
|
126
|
+
@source.scale_denom = case (1/r).to_i
|
127
|
+
when 2,3 then 2
|
128
|
+
when 4,5,6,7 then 4
|
129
|
+
else 8
|
130
|
+
end
|
131
|
+
end
|
114
132
|
end
|
115
133
|
end
|
116
134
|
end
|
data/lib/axon/generators.rb
CHANGED
@@ -14,9 +14,6 @@ module Axon
|
|
14
14
|
# The height of the generated image.
|
15
15
|
attr_reader :height
|
16
16
|
|
17
|
-
# The color model of the generated image.
|
18
|
-
attr_reader :color_model
|
19
|
-
|
20
17
|
# The components in the generated image.
|
21
18
|
attr_reader :components
|
22
19
|
|
@@ -30,7 +27,6 @@ module Axon
|
|
30
27
|
#
|
31
28
|
# +options+ may contain the following optional hash key values:
|
32
29
|
#
|
33
|
-
# * :color_model -- The color model of the generated image.
|
34
30
|
# * :components -- The number of components in the generated image.
|
35
31
|
#
|
36
32
|
def initialize(width, height, options=nil)
|
@@ -38,7 +34,6 @@ module Axon
|
|
38
34
|
|
39
35
|
@width = width
|
40
36
|
@height = height
|
41
|
-
@color_model = options[:color_model] || :RGB
|
42
37
|
@components = options[:components] || 3
|
43
38
|
@lineno = 0
|
44
39
|
@empty_string = String.new
|
@@ -73,9 +68,6 @@ module Axon
|
|
73
68
|
# The height of the generated image.
|
74
69
|
attr_reader :height
|
75
70
|
|
76
|
-
# The color model of the generated image.
|
77
|
-
attr_reader :color_model
|
78
|
-
|
79
71
|
# The components in the generated image.
|
80
72
|
attr_reader :components
|
81
73
|
|
@@ -90,10 +82,9 @@ module Axon
|
|
90
82
|
# The optional argument +color+ is the binary value that will be assigned
|
91
83
|
# to each pixel.
|
92
84
|
#
|
93
|
-
def initialize(width, height, color=nil
|
85
|
+
def initialize(width, height, color=nil)
|
94
86
|
@width, @height = width, height
|
95
87
|
@color = color || "\x00\x00\x00"
|
96
|
-
@color_model = color_model || :RGB
|
97
88
|
@components = @color.size
|
98
89
|
@lineno = 0
|
99
90
|
end
|
data/lib/axon/scalers.rb
CHANGED
@@ -46,13 +46,6 @@ module Axon
|
|
46
46
|
@source.components
|
47
47
|
end
|
48
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
49
|
# Gets the next scanline from the cropped image.
|
57
50
|
#
|
58
51
|
def gets
|
@@ -121,13 +114,6 @@ module Axon
|
|
121
114
|
@source.components
|
122
115
|
end
|
123
116
|
|
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
117
|
# Gets the next scanline from the cropped image.
|
132
118
|
#
|
133
119
|
def gets
|
@@ -152,9 +138,9 @@ module Axon
|
|
152
138
|
end
|
153
139
|
|
154
140
|
def read_with_padding
|
141
|
+
cmp = @source.components
|
155
142
|
line = @source.gets
|
156
|
-
line
|
157
|
-
line
|
143
|
+
line + line[-cmp, cmp]
|
158
144
|
end
|
159
145
|
end
|
160
146
|
end
|
data/test/helper.rb
CHANGED
@@ -8,7 +8,7 @@ module Axon
|
|
8
8
|
# Generate a solid velvet image
|
9
9
|
def setup
|
10
10
|
super
|
11
|
-
@image = Solid.new 10,
|
11
|
+
@image = Solid.new 10, 16, "\x0A\x14\x69"
|
12
12
|
@io_out = StringIO.new
|
13
13
|
@io_out.set_encoding 'ASCII-8BIT' if @io_out.respond_to?(:set_encoding)
|
14
14
|
end
|
@@ -28,6 +28,10 @@ module Axon
|
|
28
28
|
assert_nil image.gets, 'image.gets should be nil after reading height lines'
|
29
29
|
assert_equal height, image.lineno
|
30
30
|
end
|
31
|
+
|
32
|
+
def skip_symbol_fixnums
|
33
|
+
skip("ruby 1.8.7 treats symbols as fixnums") unless RUBY_VERSION >= "1.9"
|
34
|
+
end
|
31
35
|
end
|
32
36
|
|
33
37
|
class CustomError < RuntimeError; end
|
@@ -48,7 +52,6 @@ module Axon
|
|
48
52
|
|
49
53
|
def height; @parent.height; end
|
50
54
|
def width; @parent.width; end
|
51
|
-
def color_model; @parent.color_model; end
|
52
55
|
def components; @parent.components; end
|
53
56
|
def gets; @parent.gets; end
|
54
57
|
def lineno; @parent.lineno; end
|
@@ -94,12 +97,11 @@ module Axon
|
|
94
97
|
end
|
95
98
|
|
96
99
|
class ArrayWrapper
|
97
|
-
attr_reader :lineno, :components
|
100
|
+
attr_reader :lineno, :components
|
98
101
|
|
99
|
-
def initialize(ary, components=nil
|
102
|
+
def initialize(ary, components=nil)
|
100
103
|
@ary = ary
|
101
104
|
@components = components || 3
|
102
|
-
@color_model = color_model || :RGB
|
103
105
|
@lineno = 0
|
104
106
|
end
|
105
107
|
|
@@ -132,10 +134,6 @@ module Axon
|
|
132
134
|
@source.components
|
133
135
|
end
|
134
136
|
|
135
|
-
def color_model
|
136
|
-
@source.color_model
|
137
|
-
end
|
138
|
-
|
139
137
|
def height
|
140
138
|
@source.height
|
141
139
|
end
|
data/test/reader_tests.rb
CHANGED
@@ -9,10 +9,6 @@ module Axon
|
|
9
9
|
assert_equal @image.components, @reader.components
|
10
10
|
end
|
11
11
|
|
12
|
-
def test_header_color_model
|
13
|
-
assert_equal @image.color_model, @reader.color_model
|
14
|
-
end
|
15
|
-
|
16
12
|
def test_io_returns_too_much_data
|
17
13
|
io = CustomIO.new(Proc.new{ |io, *args| io.read(*args)[0..20] * 100 }, @data)
|
18
14
|
assert_raises(RuntimeError) { @readerclass.new io }
|
@@ -33,10 +29,6 @@ module Axon
|
|
33
29
|
assert_raises(CustomError) { @readerclass.new io }
|
34
30
|
end
|
35
31
|
|
36
|
-
def test_empty_string_io
|
37
|
-
assert_raises(RuntimeError) { @readerclass.new(CustomIO.new("")) }
|
38
|
-
end
|
39
|
-
|
40
32
|
def test_not_a_string_io
|
41
33
|
assert_raises(TypeError) { @readerclass.new(CustomIO.new(:foo)) }
|
42
34
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Axon
|
4
|
+
class TestAlphaStripper < AxonTestCase
|
5
|
+
def test_strip_grayscale_alpha
|
6
|
+
im = Solid.new(20, 30, "\x00\x00")
|
7
|
+
noalpha = AlphaStripper.new(im)
|
8
|
+
assert_equal 1, noalpha.components
|
9
|
+
assert_image_dimensions noalpha, 20, 30
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_strip_rgb_alpha
|
13
|
+
im = Solid.new(20, 30, "\x00\x00\x00\x00")
|
14
|
+
noalpha = AlphaStripper.new(im)
|
15
|
+
assert_equal 3, noalpha.components
|
16
|
+
assert_image_dimensions noalpha, 20, 30
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_leaves_grayscale_untouched
|
20
|
+
im = Solid.new(20, 30, "\x00")
|
21
|
+
noalpha = AlphaStripper.new(im)
|
22
|
+
assert_equal 1, noalpha.components
|
23
|
+
assert_image_dimensions noalpha, 20, 30
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_leaves_rgb_untouched
|
27
|
+
im = Solid.new(20, 30, "\x00\x00\x00")
|
28
|
+
noalpha = AlphaStripper.new(im)
|
29
|
+
assert_equal 3, noalpha.components
|
30
|
+
assert_image_dimensions noalpha, 20, 30
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/test/test_fit.rb
CHANGED
@@ -31,5 +31,43 @@ module Axon
|
|
31
31
|
r = Fit.new(im, 10, 20)
|
32
32
|
assert_image_dimensions(r, 10, 20)
|
33
33
|
end
|
34
|
+
|
35
|
+
def test_jpeg_pre_scale_one_half
|
36
|
+
skip "JRuby's JPEG decoder doesn't pre-scale" if(RUBY_PLATFORM =~ /java/)
|
37
|
+
io = StringIO.new
|
38
|
+
JPEG.write(Solid.new(10, 20), io)
|
39
|
+
io.rewind
|
40
|
+
im = JPEG::Reader.new(io)
|
41
|
+
width = im.width / 2
|
42
|
+
height = im.height / 2
|
43
|
+
|
44
|
+
r = Fit.new(im, width, height)
|
45
|
+
assert_image_dimensions(r, width, height)
|
46
|
+
|
47
|
+
if JPEG::LIB_VERSION >= 70
|
48
|
+
assert_equal(4, im.scale_num)
|
49
|
+
else
|
50
|
+
assert_equal(2, im.scale_denom)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_jpeg_pre_scale_two
|
55
|
+
skip "JRuby's JPEG decoder doesn't pre-scale" if(RUBY_PLATFORM =~ /java/)
|
56
|
+
io = StringIO.new
|
57
|
+
JPEG.write(Solid.new(10, 20), io)
|
58
|
+
io.rewind
|
59
|
+
im = JPEG::Reader.new(io)
|
60
|
+
width = im.width * 2
|
61
|
+
height = im.height * 2
|
62
|
+
|
63
|
+
r = Fit.new(im, width, height)
|
64
|
+
assert_image_dimensions(r, width, height)
|
65
|
+
|
66
|
+
if JPEG::LIB_VERSION >= 70
|
67
|
+
assert_equal(16, im.scale_num)
|
68
|
+
else
|
69
|
+
assert_equal(1, im.scale_denom)
|
70
|
+
end
|
71
|
+
end
|
34
72
|
end
|
35
73
|
end
|