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.
@@ -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