axon 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|