axon 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
Binary file
@@ -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 && x_offset < 1 || y_offset && y_offset < 1
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 || 0
43
- @y_offset = y_offset || 0
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
@@ -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 : calculate_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 : calculate_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, width, height)
84
+ NearestNeighborScaler.new(@source, final_width, final_height)
89
85
  elsif r < 1
90
- if r <= 0.5 && @source.kind_of?(JPEG::Reader)
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
- private
90
+ def source_aspect_ratio
91
+ @source.width / @source.height.to_f
92
+ end
101
93
 
102
94
  def calc_fit_ratio
103
- width_ratio = @fit_width.to_f / @source.width
104
- height_ratio = @fit_height.to_f / @source.height
105
- [width_ratio, height_ratio].min
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
- def calc_jpeg_pre_shrink(r)
109
- case (1/r)
110
- when (0...2) then nil
111
- when (2...4) then 2
112
- when (4...8) then 4
113
- else 8
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
@@ -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, color_model=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
@@ -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 << line[-@source.components, @source.components]
157
- line
143
+ line + line[-cmp, cmp]
158
144
  end
159
145
  end
160
146
  end
@@ -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, 15, "\x0A\x14\x69"
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, :color_model
100
+ attr_reader :lineno, :components
98
101
 
99
- def initialize(ary, components=nil, color_model=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
@@ -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
@@ -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