axon 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG.rdoc +9 -3
  2. data/README.rdoc +29 -36
  3. data/Rakefile +26 -21
  4. data/TODO.rdoc +1 -6
  5. data/ext/axon/axon.c +6 -15
  6. data/ext/axon/extconf.rb +19 -9
  7. data/ext/axon/interpolation.c +147 -0
  8. data/ext/axon/jpeg.c +1207 -0
  9. data/ext/axon/png.c +542 -0
  10. data/lib/axon.rb +235 -32
  11. data/lib/axon/cropper.rb +80 -18
  12. data/lib/axon/fit.rb +69 -19
  13. data/lib/axon/generators.rb +109 -0
  14. data/lib/axon/scalers.rb +160 -0
  15. data/test/helper.rb +151 -6
  16. data/test/reader_tests.rb +37 -82
  17. data/test/scaler_tests.rb +102 -0
  18. data/test/stress_helper.rb +58 -0
  19. data/test/stress_tests.rb +8 -5
  20. data/test/test_bilinear_scaler.rb +60 -2
  21. data/test/test_cropper.rb +68 -1
  22. data/test/test_fit.rb +35 -0
  23. data/test/test_generators.rb +21 -0
  24. data/test/test_image.rb +61 -0
  25. data/test/test_jpeg_reader.rb +96 -94
  26. data/test/test_jpeg_writer.rb +95 -8
  27. data/test/test_nearest_neighbor_scaler.rb +28 -4
  28. data/test/test_png_reader.rb +12 -8
  29. data/test/test_png_writer.rb +8 -6
  30. data/test/writer_tests.rb +129 -111
  31. metadata +71 -128
  32. data/.gemtest +0 -0
  33. data/ext/axon/bilinear_interpolation.c +0 -115
  34. data/ext/axon/interpolation.h +0 -7
  35. data/ext/axon/jpeg_common.c +0 -118
  36. data/ext/axon/jpeg_common.h +0 -37
  37. data/ext/axon/jpeg_native_writer.c +0 -248
  38. data/ext/axon/jpeg_reader.c +0 -774
  39. data/ext/axon/nearest_neighbor_interpolation.c +0 -50
  40. data/ext/axon/png_common.c +0 -21
  41. data/ext/axon/png_common.h +0 -18
  42. data/ext/axon/png_native_writer.c +0 -166
  43. data/ext/axon/png_reader.c +0 -381
  44. data/lib/axon/axon.so +0 -0
  45. data/lib/axon/bilinear_scaler.rb +0 -60
  46. data/lib/axon/jpeg_writer.rb +0 -41
  47. data/lib/axon/nearest_neighbor_scaler.rb +0 -39
  48. data/lib/axon/png_writer.rb +0 -35
  49. data/lib/axon/scaler.rb +0 -41
  50. data/lib/axon/solid.rb +0 -23
  51. data/test/_test_readme.rb +0 -34
  52. data/test/test_exif.rb +0 -39
  53. data/test/test_generator.rb +0 -10
  54. data/test/test_icc.rb +0 -18
  55. data/test/test_jpeg.rb +0 -9
  56. data/test/test_png.rb +0 -9
data/CHANGELOG.rdoc CHANGED
@@ -1,7 +1,13 @@
1
- === 0.0.1 / 2011-08-01
1
+ === 0.1.0 / 2012-1-5
2
2
 
3
- * Initial Release
3
+ * Make images and operations behave more like Ruby IO instead of enumerables.
4
+ * Full rdoc documentation.
5
+ * Rubinius and 1.8.7 support.
4
6
 
5
7
  === 0.0.2 / 2011-08-16
6
8
 
7
- * Fix a makefile bug that prevented axon from loading in 1.8.7
9
+ * Fix a makefile bug that prevented axon from loading in 1.8.7
10
+
11
+ === 0.0.1 / 2011-08-01
12
+
13
+ * Initial Release
data/README.rdoc CHANGED
@@ -1,72 +1,65 @@
1
- = Axon
1
+ = axon
2
2
 
3
3
  http://github.com/ender672/axon
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Axon is a library for streaming and manipulating JPEG and PNG images. It scales
8
- and crops images along the way.
7
+ axon reads, manipulates, and writes images.
9
8
 
10
- By limiting its functionality, Axon is able to depend on two ubiquitous
11
- libraries: libjpeg and libpng. Axon can be installed anywhere those libraries
12
- are available.
9
+ axon depends only on libjpeg and libpng.
13
10
 
14
- Axon never stores an entire image in memory. All images and operations are
15
- streamed from an input to an output. As a result, memory requirements and
16
- latency are low.
11
+ axon never stores an entire image in memory. All images and operations are
12
+ streamed. This keeps memory requirements and latency low.
17
13
 
18
14
  == FEATURES:
19
15
 
20
16
  * Read and Write JPEG and PNG images.
21
- * Scale images using bilinear (fast!) or nearest-neighbor (even faster!)
22
- interpolation.
23
- * Crop images.
17
+ * Scale images using bilinear or nearest-neighbor interpolation.
18
+ * Crop images and image regions.
19
+ * Scale images to fit into a box while preserving aspect ratio.
24
20
 
25
21
  == SYNOPSIS:
26
22
 
27
23
  # Short, chained example. Reads a JPEG from io_in and writes scaled png to
28
24
  # io_out.
29
- Axon.JPEG(io_in).fit(100, 100).write_png(io_out)
25
+ Axon.jpeg(io_in).fit(100, 100).png(io_out)
30
26
 
31
27
  # Longer example, reads the JPEG header, looks at properties and header
32
28
  # values, sets decompression options, scales the image, sets compression
33
29
  # options, and writes a JPEG.
34
- image = Axon.JPEG(io, [:APP2])
30
+ reader = Axon::JPEG::Reader(io, [:APP2])
35
31
 
36
- puts image.width
37
- puts image.height
38
- puts image[:APP2]
39
- image.scale_denom = 4
32
+ puts reader.width #=> Integer
33
+ puts reader.height #=> Integer
34
+ puts reader[:APP2] #=> Array of Strings
35
+ reader.scale_denom = 4 # Image will be scaled by 1/4 on read
40
36
 
41
- jpeg = image.fit(100, 100).to_jpeg
42
- jpeg.quality = 88
43
- jpeg.write(io)
37
+ fitted = Axon::Fit.new(reader, 100, 100) # Fits image in a 100x100 box
38
+ JPEG.write(fitted, io, :quality => 88)
44
39
 
45
40
  == BASIC API:
46
41
 
47
- There are three basic object types: Image, Reader and Writer.
42
+ All image objects are expected to respond to the following methods:
48
43
 
49
- Every Image object has the following methods:
50
-
51
- * Image#height, Image#width
52
- These are the output dimensions of the image.
44
+ * Image#height
45
+ Output height of the image.
46
+ * Image#width
47
+ Output width of the image.
53
48
  * Image#color_model
54
49
  Can be :GRAYSCALE or :RGB
55
50
  * Image#components
56
- An RGB image will have 3 components. A grayscale image with an alpha channel
57
- (transparency) will have 2 components: grayscale and transparency, etc.
58
- * Image#each(&block)
59
- Yields every line in the image as a binary ruby string. Image properties
60
- are not allowed to change between the first and last yield.
51
+ RGB image with alpha has 4 components.
52
+ RGB image has 3 components.
53
+ Grayscale image with alpha has 2 components.
54
+ Grayscale image has 1 component.
55
+ * Image#gets
56
+ Returns the next line in the image as a binary ruby string.
57
+ * Image#lineno
58
+ The line number of the next line that will be returned, starting at 0.
61
59
 
62
60
  A Reader object has the same methods as an Image object, with added methods that
63
61
  are specific to the decoding of the image format.
64
62
 
65
- A Writer has two methods:
66
-
67
- * Writer#write(io[, options])
68
- * Writer#data([options]) # returns image data as a string
69
-
70
63
  == REQUIREMENTS:
71
64
 
72
65
  IJG JPEG Library Version 6b or later.
data/Rakefile CHANGED
@@ -1,30 +1,35 @@
1
- require 'rubygems'
2
- require 'hoe'
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
3
 
4
- HOE = Hoe.spec 'axon' do
5
- developer('Timothy Elliott', 'tle@holymonkey.com')
6
- self.readme_file = 'README.rdoc'
7
- self.history_file = 'CHANGELOG.rdoc'
8
- self.extra_dev_deps << ['rake-compiler', '>= 0']
9
- self.spec_extras = { :extensions => ["ext/axon/extconf.rb"] }
4
+ file 'ext/axon/Makefile' do
5
+ cd 'ext/axon' do
6
+ ruby "extconf.rb #{ENV['EXTOPTS']}"
7
+ end
10
8
  end
11
9
 
12
- require "rake/extensiontask"
13
-
14
- Rake::ExtensionTask.new('axon', HOE.spec) do |ext|
15
- ext.lib_dir = File.join('lib', 'axon')
10
+ file 'ext/axon/axon.so' => FileList.new('ext/axon/Makefile', 'ext/axon/*{.c,.h}') do
11
+ cd 'ext/axon' do
12
+ sh 'make'
13
+ end
16
14
  end
17
15
 
18
- Rake::Task[:test].prerequisites << :compile
16
+ CLEAN.add('ext/axon/*{.o,.so,.log}', 'ext/axon/Makefile')
17
+ CLOBBER.add('*.gem')
19
18
 
20
- desc 'Run a test in looped mode so that you can look for memory leaks.'
21
- task 'test_loop' do
22
- code = %Q[require '#{$*[1]}'; loop{ MiniTest::Unit.new.run }]
23
- cmd = %Q[ruby -Ilib -Itest -e "#{ code }"]
24
- system cmd
19
+ desc 'Clean up Rubinius .rbc files.'
20
+ namespace :clean do
21
+ task :rbc do
22
+ system "find . -name *.rbc -delete"
23
+ end
25
24
  end
26
25
 
27
- desc 'Watch Memory use of a looping test'
28
- task 'test_loop_mem' do
29
- system 'watch "ps aux | grep Itest"'
26
+ Rake::TestTask.new do |t|
27
+ t.libs += ['test', 'ext']
28
+ t.test_files = FileList['test/test*.rb']
29
+ t.verbose = true
30
30
  end
31
+
32
+ desc 'Compile axon'
33
+ task :compile => 'ext/axon/axon.so'
34
+ task :test => :compile
35
+ task :default => :test
data/TODO.rdoc CHANGED
@@ -1,12 +1,7 @@
1
1
  = TODO
2
+ * get everything running on osx and windows
2
3
  * have a setting where decode / encode warnings will result in exceptions
3
-
4
- * Test scalers with images that lie about width & height. Never trust
5
- Image#width or Image#height -- instead always go by scanline size and how
6
- many times #each_scanline calls yield
7
-
8
4
  * add #initialize_copy
9
-
10
5
  * Check frozen status on setters, etc.
11
6
  * Check frozen status (and freeze) on all VALUEs that we stash for later use
12
7
  * Check trusted status (1.9.x only)
data/ext/axon/axon.c CHANGED
@@ -1,20 +1,11 @@
1
- #include <ruby.h>
2
-
3
- #include "jpeg_common.h"
4
- #include "png_common.h"
5
- #include "interpolation.h"
1
+ void Init_JPEG();
2
+ void Init_PNG();
3
+ void Init_Interpolation();
6
4
 
7
5
  void
8
6
  Init_axon()
9
7
  {
10
- Init_jpeg();
11
- Init_jpeg_reader();
12
- Init_jpeg_native_writer();
13
-
14
- Init_png();
15
- Init_png_reader();
16
- Init_png_native_writer();
17
-
18
- Init_bilinear_interpolation();
19
- Init_nearest_neighbor_interpolation();
8
+ Init_JPEG();
9
+ Init_PNG();
10
+ Init_Interpolation();
20
11
  }
data/ext/axon/extconf.rb CHANGED
@@ -1,21 +1,31 @@
1
1
  require 'mkmf'
2
2
 
3
- $CFLAGS += " -g -O0" if ENV['GCC_DEBUG']
3
+ # OSX by default keeps libpng in the X11 dirs
4
+ if RUBY_PLATFORM =~ /darwin/
5
+ png_idefault = '/usr/X11/include'
6
+ png_ldefault = '/usr/X11/lib'
7
+ else
8
+ png_idefault = nil
9
+ png_ldefault = nil
10
+ end
11
+
12
+ dir_config('jpeg')
13
+ dir_config('png', png_idefault, png_ldefault)
4
14
 
5
- unless find_header('jpeglib.h')
6
- abort "libjpeg headers are missing. Please install libjpeg headers."
15
+ unless have_header('jpeglib.h')
16
+ abort "libjpeg headers were not found."
7
17
  end
8
18
 
9
- unless have_library('jpeg', nil)
10
- abort "libjpeg is missing. Please install libjpeg."
19
+ unless have_library('jpeg')
20
+ abort "libjpeg was not found."
11
21
  end
12
22
 
13
- unless find_header('png.h')
14
- abort "libpng headers are missing. Please install libpng headers."
23
+ unless have_header('png.h')
24
+ abort "libpng headers were not found."
15
25
  end
16
26
 
17
- unless have_library('png', nil)
18
- abort "libpng is missing. Please install libpng."
27
+ unless have_library('png')
28
+ abort "libpng was not found."
19
29
  end
20
30
 
21
31
  create_makefile('axon/axon')
@@ -0,0 +1,147 @@
1
+ #include <ruby.h>
2
+
3
+ /* c00 a c10
4
+ * --------------------------
5
+ * | | |
6
+ * | | |
7
+ * | ty| |
8
+ * | tx | |
9
+ * |------------------+-----|
10
+ * | /| |
11
+ * | sample| |
12
+ * | | |
13
+ * | | |
14
+ * | | |
15
+ * | | |
16
+ * --------------------------
17
+ * c01 b c11
18
+ *
19
+ * a = (1 - tx) * c00 + tx * c10
20
+ * b = (1 - tx) * c01 + tx * c11
21
+ * sample = (1 - ty) * a + ty * b
22
+ *
23
+ * sample = (1 - ty) * (1 - tx) * c00 +
24
+ * (1 - ty) * tx * c10 +
25
+ * ty * (1 - tx) * c01 +
26
+ * ty * tx * c11
27
+ */
28
+ static VALUE
29
+ bilinear2(size_t width, size_t src_width, size_t components, double ty,
30
+ char *scanline1, char *scanline2)
31
+ {
32
+ VALUE rb_dest_sl;
33
+ double width_ratio_inv, sample_x, tx, _tx, p00, p10, p01, p11;
34
+ unsigned char *c00, *c10, *c01, *c11, *dest_sl;
35
+ size_t sample_x_i, i, j;
36
+
37
+ rb_dest_sl = rb_str_new(0, width * components);
38
+ dest_sl = RSTRING_PTR(rb_dest_sl);
39
+
40
+ width_ratio_inv = (double)src_width / width;
41
+
42
+ for (i = 0; i < width; i++) {
43
+ sample_x = i * width_ratio_inv;
44
+ sample_x_i = (int)sample_x;
45
+
46
+ tx = sample_x - sample_x_i;
47
+ _tx = 1 - tx;
48
+
49
+ p11 = tx * ty;
50
+ p01 = _tx * ty;
51
+ p10 = tx - p11;
52
+ p00 = _tx - p01;
53
+
54
+ c00 = scanline1 + sample_x_i * components;
55
+ c10 = c00 + components;
56
+ c01 = scanline2 + sample_x_i * components;
57
+ c11 = c01 + components;
58
+
59
+ for (j = 0; j < components; j++)
60
+ *dest_sl++ = p00 * c00[j] + p10 * c10[j] + p01 * c01[j] +
61
+ p11 * c11[j];
62
+ }
63
+
64
+ return rb_dest_sl;
65
+ }
66
+
67
+ /* :nodoc: */
68
+
69
+ static VALUE
70
+ bilinear(VALUE self, VALUE rb_scanline1, VALUE rb_scanline2, VALUE rb_width,
71
+ VALUE rb_ty, VALUE rb_components)
72
+ {
73
+ double ty;
74
+ unsigned char *scanline1, *scanline2;
75
+ int src_line_size;
76
+ size_t width, components, src_width;
77
+
78
+ width = NUM2INT(rb_width);
79
+ components = NUM2INT(rb_components);
80
+ ty = NUM2DBL(rb_ty);
81
+
82
+ Check_Type(rb_scanline1, T_STRING);
83
+ Check_Type(rb_scanline2, T_STRING);
84
+
85
+ src_line_size = RSTRING_LEN(rb_scanline1);
86
+
87
+ if (RSTRING_LEN(rb_scanline2) != src_line_size)
88
+ rb_raise(rb_eArgError, "Scanlines don't have the same width.");
89
+
90
+ src_width = src_line_size / components - 1;
91
+ scanline1 = RSTRING_PTR(rb_scanline1);
92
+ scanline2 = RSTRING_PTR(rb_scanline2);
93
+
94
+ return bilinear2(width, src_width, components, ty, scanline1, scanline2);
95
+ }
96
+
97
+ static VALUE
98
+ nearest2(size_t width, size_t src_width, size_t components, char *scanline)
99
+ {
100
+ double inv_scale_x;
101
+ VALUE rb_dest_sl;
102
+ char *dest_sl, *xpos;
103
+ size_t i, j;
104
+
105
+ inv_scale_x = (double)src_width / width;
106
+
107
+ rb_dest_sl = rb_str_new(0, width * components);
108
+ dest_sl = RSTRING_PTR(rb_dest_sl);
109
+
110
+ for (i = 0; i < width; i++) {
111
+ xpos = scanline + (int)(i * inv_scale_x) * components;
112
+ for (j = 0; j < components; j++)
113
+ *dest_sl++ = *xpos++;
114
+ }
115
+
116
+ return rb_dest_sl;
117
+ }
118
+
119
+ /* :nodoc: */
120
+
121
+ static VALUE
122
+ nearest(VALUE self, VALUE rb_scanline, VALUE rb_width, VALUE rb_components)
123
+ {
124
+ unsigned char *scanline;
125
+ size_t width, src_width, src_line_size, components;
126
+
127
+ width = NUM2INT(rb_width);
128
+ components = NUM2INT(rb_components);
129
+
130
+ Check_Type(rb_scanline, T_STRING);
131
+ src_line_size = RSTRING_LEN(rb_scanline);
132
+ scanline = RSTRING_PTR(rb_scanline);
133
+
134
+ src_width = src_line_size / components;
135
+ return nearest2(width, src_width, components, scanline);
136
+ }
137
+
138
+
139
+ void
140
+ Init_Interpolation()
141
+ {
142
+ VALUE mAxon = rb_define_module("Axon");
143
+ /* :nodoc: */
144
+ VALUE mInterpolation = rb_define_module_under(mAxon, "Interpolation");
145
+ rb_define_singleton_method(mInterpolation, "bilinear", bilinear, 5);
146
+ rb_define_singleton_method(mInterpolation, "nearest", nearest, 3);
147
+ }
data/ext/axon/jpeg.c ADDED
@@ -0,0 +1,1207 @@
1
+ #include <ruby.h>
2
+ #include <jpeglib.h>
3
+ #include "iccjpeg.h"
4
+
5
+ /*
6
+ * Marker size is defined by two bytes, so the maximum is 65,535 bytes.
7
+ * Two of those bytes are used by the size indicator bytes themselves, leaving
8
+ * 65,533 bytes.
9
+ */
10
+
11
+ #define MAX_MARKER_LEN 65533
12
+
13
+ /*
14
+ * The Exif marker eats 4 bytes for "Exif", and 2 bytes for NULL terminators.
15
+ */
16
+
17
+ #define EXIF_OVERHEAD_LEN 6
18
+ #define MAX_EXIF_DATA_LEN MAX_MARKER_LEN - EXIF_OVERHEAD_LEN
19
+ #define EXIF_MARKER (JPEG_APP0 + 1)
20
+
21
+ #define WRITE_BUFSIZE 1024
22
+ #define READ_SIZE 2048
23
+
24
+ static ID id_ISLOW, id_IFAST, id_FLOAT, id_DEFAULT, id_FASTEST;
25
+ static ID id_GRAYSCALE, id_RGB, id_YCbCr, id_CMYK, id_YCCK, id_UNKNOWN;
26
+ static ID id_APP0, id_APP1, id_APP2, id_APP3, id_APP4, id_APP5, id_APP6,
27
+ id_APP7, id_APP8, id_APP9, id_APP10, id_APP11, id_APP12, id_APP13,
28
+ id_APP14, id_APP15, id_COM;
29
+ static ID id_write, id_gets, id_width, id_height, id_color_model, id_read,
30
+ id_components;
31
+ static VALUE sym_icc_profile, sym_exif, sym_quality, sym_bufsize;
32
+
33
+ static struct jpeg_error_mgr jerr;
34
+
35
+ struct buf_dest_mgr {
36
+ struct jpeg_destination_mgr pub;
37
+ VALUE io;
38
+ JOCTET *buffer;
39
+ size_t alloc;
40
+ size_t total;
41
+ };
42
+
43
+ struct readerdata {
44
+ struct jpeg_decompress_struct cinfo;
45
+ struct jpeg_source_mgr mgr;
46
+
47
+ int header_read;
48
+ int locked;
49
+
50
+ VALUE source_io;
51
+ VALUE buffer;
52
+ };
53
+
54
+ static ID
55
+ j_dct_method_to_id(J_DCT_METHOD dct_method)
56
+ {
57
+ switch (dct_method) {
58
+ case JDCT_ISLOW: return id_ISLOW;
59
+ case JDCT_IFAST: return id_IFAST;
60
+ case JDCT_FLOAT: return id_FLOAT;
61
+ }
62
+
63
+ return Qnil;
64
+ }
65
+
66
+ static J_DCT_METHOD
67
+ id_to_j_dct_method(ID rb)
68
+ {
69
+ if (rb == id_ISLOW) return JDCT_ISLOW;
70
+ else if (rb == id_IFAST) return JDCT_IFAST;
71
+ else if (rb == id_FLOAT) return JDCT_FLOAT;
72
+
73
+ return (J_DCT_METHOD)NULL;
74
+ }
75
+
76
+ static ID
77
+ j_color_space_to_id(J_COLOR_SPACE cs)
78
+ {
79
+ switch (cs) {
80
+ case JCS_GRAYSCALE: return id_GRAYSCALE;
81
+ case JCS_RGB: return id_RGB;
82
+ case JCS_YCbCr: return id_YCbCr;
83
+ case JCS_CMYK: return id_CMYK;
84
+ case JCS_YCCK: return id_YCCK;
85
+ }
86
+
87
+ return id_UNKNOWN;
88
+ }
89
+
90
+ static J_COLOR_SPACE
91
+ id_to_j_color_space(ID rb)
92
+ {
93
+ if (rb == id_GRAYSCALE) return JCS_GRAYSCALE;
94
+ else if (rb == id_RGB) return JCS_RGB;
95
+ else if (rb == id_YCbCr) return JCS_YCbCr;
96
+ else if (rb == id_CMYK) return JCS_CMYK;
97
+ else if (rb == id_YCCK) return JCS_YCCK;
98
+ else if (rb == id_UNKNOWN) return JCS_UNKNOWN;
99
+
100
+ rb_raise(rb_eRuntimeError, "Color Space not recognized.");
101
+ }
102
+
103
+ static int
104
+ sym_to_marker_code(VALUE sym)
105
+ {
106
+ ID rb = SYM2ID(sym);
107
+
108
+ if (rb == id_APP0) return JPEG_APP0;
109
+ else if (rb == id_APP1) return JPEG_APP0 + 1;
110
+ else if (rb == id_APP2) return JPEG_APP0 + 2;
111
+ else if (rb == id_APP3) return JPEG_APP0 + 3;
112
+ else if (rb == id_APP4) return JPEG_APP0 + 4;
113
+ else if (rb == id_APP5) return JPEG_APP0 + 5;
114
+ else if (rb == id_APP6) return JPEG_APP0 + 6;
115
+ else if (rb == id_APP7) return JPEG_APP0 + 7;
116
+ else if (rb == id_APP8) return JPEG_APP0 + 8;
117
+ else if (rb == id_APP9) return JPEG_APP0 + 9;
118
+ else if (rb == id_APP10) return JPEG_APP0 + 10;
119
+ else if (rb == id_APP11) return JPEG_APP0 + 11;
120
+ else if (rb == id_APP12) return JPEG_APP0 + 12;
121
+ else if (rb == id_APP13) return JPEG_APP0 + 13;
122
+ else if (rb == id_APP14) return JPEG_APP0 + 14;
123
+ else if (rb == id_APP15) return JPEG_APP0 + 15;
124
+ else if (rb == id_COM) return JPEG_COM;
125
+
126
+ rb_raise(rb_eRuntimeError, "Marker code not recognized.");
127
+ }
128
+
129
+ static void
130
+ reset_buffer(struct buf_dest_mgr *dest)
131
+ {
132
+ dest->pub.next_output_byte = dest->buffer;
133
+ dest->pub.free_in_buffer = dest->alloc;
134
+ }
135
+
136
+ static void
137
+ init_destination(j_compress_ptr cinfo)
138
+ {
139
+ struct buf_dest_mgr *dest = (struct buf_dest_mgr *) cinfo->dest;
140
+ size_t size = dest->alloc * sizeof(JOCTET);
141
+
142
+ /* Allocate the output buffer --- it will be released when done */
143
+ dest->buffer = (JOCTET *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
144
+ JPOOL_IMAGE, size);
145
+ dest->total = 0;
146
+ reset_buffer(dest);
147
+ }
148
+
149
+ static boolean
150
+ empty_output_buffer(j_compress_ptr cinfo)
151
+ {
152
+ VALUE str, write_len;
153
+ struct buf_dest_mgr *dest = (struct buf_dest_mgr *) cinfo->dest;
154
+ size_t write_len_i;
155
+
156
+ str = rb_str_new(dest->buffer, dest->alloc);
157
+
158
+ write_len = rb_funcall(dest->io, id_write, 1, str);
159
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
160
+ if (SYMBOL_P(write_len))
161
+ rb_raise(rb_eTypeError, "write returned a symbol.");
162
+
163
+ write_len_i = (size_t)NUM2INT(write_len);
164
+ dest->total += write_len_i;
165
+ if (write_len_i != dest->alloc)
166
+ rb_raise(rb_eRuntimeError, "Write Error. Wrote %d instead of %d bytes.",
167
+ (int)write_len_i, (int)dest->alloc);
168
+
169
+ reset_buffer(dest);
170
+
171
+ return TRUE;
172
+ }
173
+
174
+ static void
175
+ term_destination(j_compress_ptr cinfo)
176
+ {
177
+ struct buf_dest_mgr *dest = (struct buf_dest_mgr *) cinfo->dest;
178
+ size_t write_len_i, len = dest->alloc - dest->pub.free_in_buffer;
179
+ VALUE str, write_len;
180
+
181
+ if (len > 0) {
182
+ str = rb_str_new(dest->buffer, len);
183
+ write_len = rb_funcall(dest->io, id_write, 1, str);
184
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
185
+ if (SYMBOL_P(write_len))
186
+ rb_raise(rb_eTypeError, "write returned a symbol.");
187
+ write_len_i = (size_t)NUM2INT(write_len);
188
+ dest->total += write_len_i;
189
+ if (write_len_i != len)
190
+ rb_raise(rb_eRuntimeError, "Write Error. Wrote %d instead of %d bytes.",
191
+ (int)write_len_i, (int)len);
192
+ }
193
+ }
194
+
195
+ static VALUE
196
+ write_scanline(VALUE scan_line, j_compress_ptr cinfo)
197
+ {
198
+ JSAMPROW row_pointer[1];
199
+
200
+ if (TYPE(scan_line) != T_STRING)
201
+ scan_line = rb_obj_as_string(scan_line);
202
+
203
+ if (RSTRING_LEN(scan_line) != cinfo->image_width * cinfo->num_components)
204
+ rb_raise(rb_eRuntimeError, "Scanline has a bad size. Expected %d but got %d.",
205
+ (int)(cinfo->image_width * cinfo->num_components),
206
+ (int)RSTRING_LEN(scan_line));
207
+
208
+ row_pointer[0] = (JSAMPLE *)RSTRING_PTR(scan_line);
209
+ jpeg_write_scanlines(cinfo, row_pointer, 1);
210
+ return Qnil;
211
+ }
212
+
213
+ static int
214
+ write_exif(j_compress_ptr cinfo, char *str, int len)
215
+ {
216
+ if (len > MAX_EXIF_DATA_LEN) {
217
+ rb_raise(rb_eRuntimeError, "Exif data is too large. Max is %d.",
218
+ MAX_EXIF_DATA_LEN);
219
+ }
220
+
221
+ jpeg_write_m_header(cinfo, EXIF_MARKER,
222
+ (unsigned int) (len + EXIF_OVERHEAD_LEN));
223
+
224
+ jpeg_write_m_byte(cinfo, 0x45);
225
+ jpeg_write_m_byte(cinfo, 0x78);
226
+ jpeg_write_m_byte(cinfo, 0x69);
227
+ jpeg_write_m_byte(cinfo, 0x66);
228
+ jpeg_write_m_byte(cinfo, 0x0);
229
+ jpeg_write_m_byte(cinfo, 0x0);
230
+
231
+ /* Add the profile data */
232
+ while (len--) {
233
+ jpeg_write_m_byte(cinfo, *str);
234
+ str++;
235
+ }
236
+ }
237
+
238
+ static void
239
+ write_configure(j_compress_ptr cinfo, VALUE image_in, VALUE quality)
240
+ {
241
+ VALUE color_model, width, components, rb_height;
242
+ int height;
243
+
244
+ width = rb_funcall(image_in, id_width, 0);
245
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
246
+ if (SYMBOL_P(width))
247
+ rb_raise(rb_eTypeError, "source image has a symbol for width.");
248
+ cinfo->image_width = NUM2INT(width);
249
+
250
+ rb_height = rb_funcall(image_in, id_height, 0);
251
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
252
+ if (SYMBOL_P(rb_height))
253
+ rb_raise(rb_eTypeError, "source image has a symbol for height.");
254
+
255
+ height = NUM2INT(rb_height);
256
+ if (height < 1)
257
+ rb_raise(rb_eRuntimeError, "Source image gave an invalid height.");
258
+ cinfo->image_height = height;
259
+
260
+ components = rb_funcall(image_in, id_components, 0);
261
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
262
+ if (SYMBOL_P(components))
263
+ rb_raise(rb_eTypeError, "source image has a symbol for components.");
264
+ cinfo->input_components = NUM2INT(components);
265
+
266
+ color_model = rb_funcall(image_in, id_color_model, 0);
267
+ if (SYMBOL_P(color_model))
268
+ cinfo->in_color_space = id_to_j_color_space(SYM2ID(color_model));
269
+ else
270
+ rb_raise(rb_eTypeError, "source image has a non symbol color space");
271
+
272
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
273
+ if (SYMBOL_P(quality))
274
+ rb_raise(rb_eTypeError, "symbol for quality.");
275
+
276
+
277
+ jpeg_set_defaults(cinfo);
278
+
279
+ if(!NIL_P(quality))
280
+ jpeg_set_quality(cinfo, NUM2INT(quality), TRUE);
281
+ }
282
+
283
+ static void
284
+ write_header(j_compress_ptr cinfo, VALUE icc, VALUE exif)
285
+ {
286
+ if(!NIL_P(icc)) {
287
+ StringValue(icc);
288
+ write_icc_profile(cinfo, RSTRING_PTR(icc), RSTRING_LEN(icc));
289
+ }
290
+
291
+ if (!NIL_P(exif)) {
292
+ StringValue(exif);
293
+ write_exif(cinfo, RSTRING_PTR(exif), RSTRING_LEN(exif));
294
+ }
295
+ }
296
+
297
+ static VALUE
298
+ write_jpeg3(VALUE *args)
299
+ {
300
+ VALUE image_in, scanline, quality, icc_profile, exif;
301
+ j_compress_ptr cinfo;
302
+ struct buf_dest_mgr *mgr;
303
+ size_t i;
304
+
305
+ cinfo = (j_compress_ptr) args[0];
306
+ image_in = args[1];
307
+ quality = args[2];
308
+ icc_profile = args[3];
309
+ exif = args[4];
310
+
311
+ write_configure(cinfo, image_in, quality);
312
+
313
+ jpeg_start_compress(cinfo, TRUE);
314
+
315
+ write_header(cinfo, icc_profile, exif);
316
+
317
+ for (i = 0; i < cinfo->image_height; i++) {
318
+ scanline = rb_funcall(image_in, id_gets, 0);
319
+ write_scanline(scanline, cinfo);
320
+ }
321
+
322
+ jpeg_finish_compress(cinfo);
323
+
324
+ mgr = (struct buf_dest_mgr *)(cinfo->dest);
325
+ return INT2FIX(mgr->total);
326
+ }
327
+
328
+ static VALUE
329
+ write_jpeg3_ensure(VALUE *args)
330
+ {
331
+ jpeg_destroy_compress((j_compress_ptr) args[0]);
332
+ return INT2FIX(0);
333
+ }
334
+
335
+ static VALUE
336
+ write_jpeg2(VALUE image_in, VALUE io_out, size_t bufsize, VALUE icc_profile,
337
+ VALUE exif, VALUE quality)
338
+ {
339
+ struct jpeg_compress_struct cinfo;
340
+ struct buf_dest_mgr mgr;
341
+ VALUE ensure_args[5];
342
+
343
+ cinfo.err = &jerr;
344
+
345
+ jpeg_create_compress(&cinfo);
346
+
347
+ mgr.pub.init_destination = init_destination;
348
+ mgr.pub.empty_output_buffer = empty_output_buffer;
349
+ mgr.pub.term_destination = term_destination;
350
+ mgr.alloc = 1024;
351
+ mgr.io = io_out;
352
+ cinfo.dest = (struct jpeg_destination_mgr *)&mgr;
353
+
354
+ ensure_args[0] = (VALUE)&cinfo;
355
+ ensure_args[1] = image_in;
356
+ ensure_args[2] = quality;
357
+ ensure_args[3] = icc_profile;
358
+ ensure_args[4] = exif;
359
+
360
+ return rb_ensure(write_jpeg3, (VALUE)ensure_args, write_jpeg3_ensure,
361
+ (VALUE)ensure_args);
362
+ }
363
+
364
+ /*
365
+ * call-seq:
366
+ * write(image_in, io_out [, options]) -> integer
367
+ *
368
+ * Writes the given image +image_in+ to +io_out+ as compressed JPEG data.
369
+ * Returns the number of bytes written.
370
+ *
371
+ * +options+ may contain the following symbols:
372
+ *
373
+ * * :bufsize - the size in bytes of the writes that will be made to
374
+ * +io_out+.
375
+ * * :quality - the JPEG quality on a 0..100 scale.
376
+ * * :exif - raw exif data that will be saved in the header.
377
+ * * :icc_profile - raw icc profile that will be saved in the header.
378
+ *
379
+ * Example:
380
+ * image = Axon::Solid.new(200, 300)
381
+ * io = File.open("test.jpg", "w")
382
+ * Axon::JPEG.write(image, io) #=> 1234
383
+ */
384
+
385
+ static VALUE
386
+ write_jpeg(int argc, VALUE *argv, VALUE self)
387
+ {
388
+ VALUE image_in, io_out, rb_bufsize, icc_profile, exif, quality, options;
389
+ int bufsize;
390
+
391
+ rb_scan_args(argc, argv, "21", &image_in, &io_out, &options);
392
+
393
+ bufsize = WRITE_BUFSIZE;
394
+
395
+ if (!NIL_P(options) && TYPE(options) == T_HASH) {
396
+ rb_bufsize = rb_hash_aref(options, sym_bufsize);
397
+ /* in 1.8.7, NUM2INT gives funny numbers for Symbols */
398
+ if (SYMBOL_P(rb_bufsize))
399
+ rb_raise(rb_eTypeError, "symbol for bufsize.");
400
+
401
+ if (!NIL_P(rb_bufsize)) {
402
+ bufsize = NUM2INT(rb_bufsize);
403
+ if (bufsize < 1)
404
+ rb_raise(rb_eRuntimeError, "Buffer size must be greater than zero");
405
+ }
406
+
407
+ icc_profile = rb_hash_aref(options, sym_icc_profile);
408
+ exif = rb_hash_aref(options, sym_exif);
409
+ quality = rb_hash_aref(options, sym_quality);
410
+ } else {
411
+ icc_profile = Qnil;
412
+ exif = Qnil;
413
+ quality = Qnil;
414
+ }
415
+
416
+ return write_jpeg2(image_in, io_out, bufsize, icc_profile, exif, quality);
417
+ }
418
+
419
+ static void
420
+ error_exit(j_common_ptr cinfo)
421
+ {
422
+ char buffer[JMSG_LENGTH_MAX];
423
+ (*cinfo->err->format_message) (cinfo, buffer);
424
+ rb_raise(rb_eRuntimeError, "jpeglib: %s", buffer);
425
+ }
426
+
427
+ static void
428
+ output_message(j_common_ptr cinfo)
429
+ {
430
+ /* do nothing */
431
+ }
432
+
433
+ static void
434
+ init_jerror(struct jpeg_error_mgr * err)
435
+ {
436
+ jpeg_std_error(err);
437
+ err->error_exit = error_exit;
438
+ err->output_message = output_message;
439
+ }
440
+
441
+ static void
442
+ raise_if_locked(struct readerdata *reader)
443
+ {
444
+ if (reader->locked)
445
+ rb_raise(rb_eRuntimeError, "Can't modify a locked Reader");
446
+ }
447
+
448
+ static void
449
+ deallocate(struct readerdata *reader)
450
+ {
451
+ jpeg_abort_decompress(&reader->cinfo);
452
+ jpeg_destroy_decompress(&reader->cinfo);
453
+ free(reader);
454
+ }
455
+
456
+ static void
457
+ mark(struct readerdata *reader)
458
+ {
459
+ if (!NIL_P(reader->source_io))
460
+ rb_gc_mark(reader->source_io);
461
+
462
+ if (!NIL_P(reader->buffer))
463
+ rb_gc_mark(reader->buffer);
464
+ }
465
+
466
+ /* Data Source Callbacks */
467
+
468
+ static void
469
+ init_source(j_decompress_ptr cinfo)
470
+ {
471
+ /* do nothing */
472
+ }
473
+
474
+ static void
475
+ term_source(j_decompress_ptr cinfo)
476
+ {
477
+ /* do nothing */
478
+ }
479
+
480
+ static void
481
+ set_input_buffer(struct readerdata *reader, VALUE string)
482
+ {
483
+ size_t nbytes = 0;
484
+ JOCTET *buffer;
485
+
486
+ if (!NIL_P(string)) {
487
+ StringValue(string);
488
+ OBJ_FREEZE(string);
489
+ nbytes = (size_t)RSTRING_LEN(string);
490
+ buffer = (JOCTET *)RSTRING_PTR(string);
491
+ reader->buffer = string;
492
+ }
493
+
494
+ if (!nbytes) {
495
+ nbytes = 2;
496
+ reader->buffer = rb_str_new(0, 2);
497
+
498
+ buffer = (JOCTET *)RSTRING_PTR(reader->buffer);
499
+ buffer[0] = (JOCTET) 0xFF;
500
+ buffer[1] = (JOCTET) JPEG_EOI;
501
+ }
502
+
503
+ reader->mgr.next_input_byte = buffer;
504
+ reader->mgr.bytes_in_buffer = nbytes;
505
+ }
506
+
507
+ static boolean
508
+ fill_input_buffer(j_decompress_ptr cinfo)
509
+ {
510
+ VALUE string;
511
+ struct readerdata *reader;
512
+
513
+ reader = (struct readerdata *)cinfo;
514
+ string = rb_funcall(reader->source_io, id_read, 1, INT2FIX(READ_SIZE));
515
+ set_input_buffer(reader, string);
516
+
517
+ return TRUE;
518
+ }
519
+
520
+ static void
521
+ skip_input_data(j_decompress_ptr cinfo, long num_bytes)
522
+ {
523
+ struct jpeg_source_mgr * src = cinfo->src;
524
+
525
+ if (num_bytes > 0) {
526
+ while (num_bytes > (long) src->bytes_in_buffer) {
527
+ num_bytes -= (long) src->bytes_in_buffer;
528
+ (void) (*src->fill_input_buffer) (cinfo);
529
+ }
530
+ src->next_input_byte += (size_t) num_bytes;
531
+ src->bytes_in_buffer -= (size_t) num_bytes;
532
+ }
533
+ }
534
+
535
+ static VALUE
536
+ allocate(VALUE klass)
537
+ {
538
+ struct readerdata *reader;
539
+ VALUE self;
540
+
541
+ self = Data_Make_Struct(klass, struct readerdata, mark, deallocate, reader);
542
+
543
+ reader->cinfo.err = &jerr;
544
+ jpeg_create_decompress(&reader->cinfo);
545
+
546
+ reader->cinfo.src = &reader->mgr;
547
+ reader->mgr.init_source = init_source;
548
+ reader->mgr.fill_input_buffer = fill_input_buffer;
549
+ reader->mgr.skip_input_data = skip_input_data;
550
+ reader->mgr.resync_to_restart = jpeg_resync_to_restart;
551
+ reader->mgr.term_source = term_source;
552
+
553
+ return self;
554
+ }
555
+
556
+ static VALUE
557
+ read_header2(VALUE arg)
558
+ {
559
+ struct readerdata *reader;
560
+
561
+ reader = (struct readerdata *)arg;
562
+ jpeg_read_header(&reader->cinfo, TRUE);
563
+ reader->header_read = 1;
564
+
565
+ return Qnil;
566
+ }
567
+
568
+ static void
569
+ read_header(struct readerdata *reader, VALUE markers)
570
+ {
571
+ int i, marker_code, state;
572
+ j_decompress_ptr cinfo;
573
+
574
+ cinfo = &reader->cinfo;
575
+
576
+ if(NIL_P(markers)) {
577
+ jpeg_save_markers(cinfo, JPEG_COM, 0xFFFF);
578
+
579
+ for (i = 0; i < 16; i++)
580
+ jpeg_save_markers(cinfo, JPEG_APP0 + i, 0xFFFF);
581
+ } else {
582
+ Check_Type(markers, T_ARRAY);
583
+ for (i = 0; i < RARRAY_LEN(markers); i++) {
584
+ marker_code = sym_to_marker_code(RARRAY_PTR(markers)[i]);
585
+ jpeg_save_markers(cinfo, marker_code, 0xFFFF);
586
+ }
587
+ }
588
+
589
+ rb_protect(read_header2, (VALUE)reader, &state);
590
+
591
+ if(state) {
592
+ jpeg_abort_decompress(&reader->cinfo);
593
+ rb_jump_tag(state);
594
+ }
595
+
596
+ jpeg_calc_output_dimensions(cinfo);
597
+ }
598
+
599
+ /*
600
+ * call-seq:
601
+ * Reader.new(io_in [, markers]) -> reader
602
+ *
603
+ * Creates a new JPEG Reader. +io_in+ must be an IO-like object that responds
604
+ * to read(size).
605
+ *
606
+ * +markers+ should be an array of valid JPEG header marker symbols. Valid
607
+ * symbols are :APP0 through :APP15 and :COM.
608
+ *
609
+ * If performance is important, you can avoid reading any header markers by
610
+ * supplying an empty array, [].
611
+ *
612
+ * When markers are not specified, we read all known JPEG markers.
613
+ *
614
+ * io = File.open("image.jpg", "r")
615
+ * reader = Axon::JPEG::Reader.new(io)
616
+ *
617
+ * io = File.open("image.jpg", "r")
618
+ * reader = Axon::JPEG::Reader.new(io, [:APP4, :APP5])
619
+ */
620
+
621
+ static VALUE
622
+ initialize(int argc, VALUE *argv, VALUE self)
623
+ {
624
+ struct readerdata *reader;
625
+ j_decompress_ptr cinfo;
626
+ VALUE io, markers;
627
+ int i;
628
+
629
+ Data_Get_Struct(self, struct readerdata, reader);
630
+ raise_if_locked(reader);
631
+ cinfo = &reader->cinfo;
632
+
633
+ rb_scan_args(argc, argv, "11", &io, &markers);
634
+
635
+ reader->source_io = io;
636
+ reader->mgr.bytes_in_buffer = 0;
637
+
638
+ read_header(reader, markers);
639
+
640
+ return self;
641
+ }
642
+
643
+ /*
644
+ * call-seq:
645
+ * reader.components -> number
646
+ *
647
+ * Retrieve the number of components as stored in the JPEG image.
648
+ */
649
+
650
+ static VALUE
651
+ components(VALUE self)
652
+ {
653
+ struct jpeg_decompress_struct * cinfo;
654
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
655
+ return INT2FIX(cinfo->num_components);
656
+ }
657
+
658
+ /*
659
+ * call-seq:
660
+ * reader.in_color_model -> symbol
661
+ *
662
+ * Returns a symbol representing the color model in which the JPEG is stored.
663
+ *
664
+ * This does not have to be set explicitly and can be relied upon when the file
665
+ * conforms to JFIF or Adobe conventions. Otherwise it is guessed.
666
+ *
667
+ * Possible color models are: :GRAYSCALE, :RGB, :YCbCr, :CMYK, and :YCCK. This
668
+ * method will return nil if the color model is not recognized.
669
+ */
670
+
671
+ static VALUE
672
+ in_color_model(VALUE self)
673
+ {
674
+ struct jpeg_decompress_struct * cinfo;
675
+ ID id;
676
+
677
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
678
+ id = j_color_space_to_id(cinfo->jpeg_color_space);
679
+
680
+ return ID2SYM(id);
681
+ }
682
+
683
+ /*
684
+ * call-seq:
685
+ * reader.in_color_model = symbol
686
+ *
687
+ * Explicitly sets the color model the JPEG will be read in. This will override
688
+ * the guessed color model.
689
+ */
690
+
691
+ static VALUE
692
+ set_in_color_model(VALUE self, VALUE cs)
693
+ {
694
+ struct readerdata *reader;
695
+
696
+ Data_Get_Struct(self, struct readerdata, reader);
697
+ raise_if_locked(reader);
698
+ reader->cinfo.jpeg_color_space = id_to_j_color_space(SYM2ID(cs));
699
+
700
+ return cs;
701
+ }
702
+
703
+ /*
704
+ * call-seq:
705
+ * reader.color_model -> symbol
706
+ *
707
+ * Returns a symbol representing the color model into which the JPEG will be
708
+ * transformed as it is read.
709
+ *
710
+ * Possible color models are: :GRAYSCALE, :RGB, :YCbCr, :CMYK, and :YCCK. This
711
+ * method will return nil if the color model is not recognized.
712
+ */
713
+
714
+ static VALUE
715
+ color_model(VALUE self)
716
+ {
717
+ ID id;
718
+ struct jpeg_decompress_struct * cinfo;
719
+
720
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
721
+
722
+ id = j_color_space_to_id(cinfo->out_color_space);
723
+ return ID2SYM(id);
724
+ }
725
+
726
+ /*
727
+ * call-seq:
728
+ * reader.color_model = symbol
729
+ *
730
+ * Explicitly sets the color model to which the JPEG will be transformed as it
731
+ * is read.
732
+ *
733
+ * Legal transformations are:
734
+ * :YCbCr to :GRAYSCALE, :YCbCr to :RGB, :GRAYSCALE to :RGB, and :YCCK to :CMYK
735
+ */
736
+
737
+ static VALUE
738
+ set_color_model(VALUE self, VALUE cs)
739
+ {
740
+ struct readerdata *reader;
741
+
742
+ Data_Get_Struct(self, struct readerdata, reader);
743
+ raise_if_locked(reader);
744
+
745
+ reader->cinfo.out_color_space = id_to_j_color_space(SYM2ID(cs));
746
+ return cs;
747
+ }
748
+
749
+ /*
750
+ * call-seq:
751
+ * reader.scale_num -> number
752
+ *
753
+ * Retrieve the numerator of the fraction by which the JPEG will be scaled as
754
+ * it is read. This is always 1 for libjpeg version 6b. In version 8b this can
755
+ * be 1 to 16.
756
+ */
757
+
758
+ static VALUE
759
+ scale_num(VALUE self)
760
+ {
761
+ struct jpeg_decompress_struct * cinfo;
762
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
763
+ return INT2FIX(cinfo->scale_num);
764
+ }
765
+
766
+ /*
767
+ * call-seq:
768
+ * reader.scale_num = number
769
+ *
770
+ * Set the numerator of the fraction by which the JPEG will be scaled as it is
771
+ * read. This must always be 1 for libjpeg version 6b. In version 8b this can
772
+ * be set to 1 through 16.
773
+ *
774
+ * Prior to version 1.2, libjpeg-turbo will not scale down images on
775
+ * decompression, and this option will do nothing.
776
+ */
777
+
778
+ static VALUE
779
+ set_scale_num(VALUE self, VALUE scale_num)
780
+ {
781
+ struct readerdata *reader;
782
+
783
+ Data_Get_Struct(self, struct readerdata, reader);
784
+ raise_if_locked(reader);
785
+
786
+ reader->cinfo.scale_num = NUM2INT(scale_num);
787
+ jpeg_calc_output_dimensions(&reader->cinfo);
788
+ return scale_num;
789
+ }
790
+
791
+ /*
792
+ * call-seq:
793
+ * reader.scale_denom -> number
794
+ *
795
+ * Retrieve the denominator of the fraction by which the JPEG will be scaled as
796
+ * it is read. This is 1, 2, 4, or 8 for libjpeg version 6b. In version 8b this
797
+ * is always the source DCT size, which is 8 for baseline JPEG.
798
+ */
799
+
800
+ static VALUE
801
+ scale_denom(VALUE self)
802
+ {
803
+ struct jpeg_decompress_struct * cinfo;
804
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
805
+ return INT2FIX(cinfo->scale_denom);
806
+ }
807
+
808
+ /*
809
+ * call-seq:
810
+ * reader.scale_denom = number
811
+ *
812
+ * Set the denominator of the fraction by which the JPEG will be scaled as it
813
+ * is read. This can be set to 1, 2, 4, or 8 for libjpeg version 6b. In version
814
+ * 8b this must always be the source DCT size, which is 8 for baseline JPEG.
815
+ *
816
+ * Prior to version 1.2, libjpeg-turbo will not scale down images on
817
+ * decompression, and this option will do nothing.
818
+ */
819
+
820
+ static VALUE
821
+ set_scale_denom(VALUE self, VALUE scale_denom)
822
+ {
823
+ struct readerdata *reader;
824
+
825
+ Data_Get_Struct(self, struct readerdata, reader);
826
+ raise_if_locked(reader);
827
+
828
+ reader->cinfo.scale_denom = NUM2INT(scale_denom);
829
+ jpeg_calc_output_dimensions(&reader->cinfo);
830
+ return scale_denom;
831
+ }
832
+
833
+ /*
834
+ * call-seq:
835
+ * reader.dct_method -> symbol
836
+ *
837
+ * Returns a symbol representing the algorithm used for the DCT (discrete
838
+ * cosine transform) step in JPEG encoding.
839
+ *
840
+ * Possible DCT algorithms are: :ISLOW, :IFAST, and :IFLOAT.
841
+ */
842
+
843
+ static VALUE
844
+ dct_method(VALUE self)
845
+ {
846
+ struct jpeg_decompress_struct * cinfo;
847
+ ID id;
848
+
849
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
850
+
851
+ id = j_dct_method_to_id(cinfo->dct_method);
852
+
853
+ if (NIL_P(id))
854
+ return Qnil;
855
+ else
856
+ return ID2SYM(id);
857
+ }
858
+
859
+ /*
860
+ * call-seq:
861
+ * reader.dct_method = symbol
862
+ *
863
+ * Sets the algorithm used for the DCT step in JPEG encoding.
864
+ *
865
+ * Possible DCT algorithms are: :ISLOW, :IFAST, and :IFLOAT.
866
+ */
867
+
868
+ static VALUE
869
+ set_dct_method(VALUE self, VALUE dct_method)
870
+ {
871
+ struct readerdata *reader;
872
+ J_DCT_METHOD val;
873
+
874
+ Data_Get_Struct(self, struct readerdata, reader);
875
+ raise_if_locked(reader);
876
+
877
+ val = id_to_j_dct_method(SYM2ID(dct_method));
878
+ if (val == (J_DCT_METHOD)NULL) {
879
+ return Qnil;
880
+ } else {
881
+ reader->cinfo.dct_method = val;
882
+ return dct_method;
883
+ }
884
+ }
885
+
886
+ static VALUE
887
+ j_gets2(struct readerdata *reader)
888
+ {
889
+ struct jpeg_decompress_struct * cinfo;
890
+ VALUE sl;
891
+ int width, components, sl_width, ret;
892
+ JSAMPROW ijg_buffer;
893
+
894
+ cinfo = &reader->cinfo;
895
+
896
+ width = cinfo->output_width;
897
+ components = cinfo->output_components;
898
+
899
+ sl_width = width * components;
900
+
901
+ sl = rb_str_new(0, sl_width);
902
+ ijg_buffer = (JSAMPROW)RSTRING_PTR(sl);
903
+ ret = jpeg_read_scanlines(cinfo, &ijg_buffer, 1);
904
+
905
+ return ret == 0 ? Qnil : sl;
906
+ }
907
+
908
+ /*
909
+ * call-seq:
910
+ * gets -> string or nil
911
+ *
912
+ * Reads the next scanline of data from the image. Once the first scanline has
913
+ * been read you can no longer change read options for this reader.
914
+ *
915
+ * If the end of the image has been reached, this will return nil.
916
+ */
917
+
918
+ static VALUE
919
+ j_gets(VALUE self)
920
+ {
921
+ struct jpeg_decompress_struct * cinfo;
922
+ struct readerdata *reader;
923
+
924
+ Data_Get_Struct(self, struct readerdata, reader);
925
+
926
+ if (!reader->header_read)
927
+ read_header(reader, Qnil);
928
+
929
+ if (reader->locked == 0) {
930
+ reader->locked = 1;
931
+ jpeg_start_decompress(&reader->cinfo);
932
+ }
933
+
934
+ return j_gets2(reader);
935
+ }
936
+
937
+ /*
938
+ * call-seq:
939
+ * reader.width -> number
940
+ *
941
+ * Retrieve the width of the image as it will be written out. This can be
942
+ * affected by scale_num and scale_denom if they are set.
943
+ */
944
+
945
+ static VALUE
946
+ width(VALUE self)
947
+ {
948
+ struct jpeg_decompress_struct * cinfo;
949
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
950
+ return INT2FIX(cinfo->output_width);
951
+ }
952
+
953
+ /*
954
+ * call-seq:
955
+ * reader.height -> number
956
+ *
957
+ * Retrieve the height of the image as it will be written out. This can be
958
+ * affected by scale_num and scale_denom if they are set.
959
+ */
960
+
961
+ static VALUE
962
+ height(VALUE self)
963
+ {
964
+ struct jpeg_decompress_struct * cinfo;
965
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
966
+ return INT2FIX(cinfo->output_height);
967
+ }
968
+
969
+ /*
970
+ * call-seq:
971
+ * reader.icc_profile -> string
972
+ *
973
+ * Read the raw icc_profile from the JPEG. This requires that the APP2 segment
974
+ * has been selected by initialize (this is the default behaviour).
975
+ */
976
+
977
+ static VALUE
978
+ icc_profile(VALUE self)
979
+ {
980
+ struct jpeg_decompress_struct * cinfo;
981
+ JOCTET *icc_embed_buffer;
982
+ unsigned int icc_embed_len;
983
+ VALUE str;
984
+
985
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
986
+ read_icc_profile(cinfo, &icc_embed_buffer, &icc_embed_len);
987
+
988
+ if (icc_embed_len <= 0) {
989
+ return Qnil;
990
+ } else {
991
+ str = rb_str_new(icc_embed_buffer, icc_embed_len);
992
+ free(icc_embed_buffer);
993
+ return str;
994
+ }
995
+ }
996
+
997
+ static boolean
998
+ marker_is_exif(jpeg_saved_marker_ptr marker)
999
+ {
1000
+ return marker->marker == EXIF_MARKER &&
1001
+ marker->data_length >= EXIF_OVERHEAD_LEN &&
1002
+ /* verify the identifying string */
1003
+ GETJOCTET(marker->data[0]) == 0x45 &&
1004
+ GETJOCTET(marker->data[1]) == 0x78 &&
1005
+ GETJOCTET(marker->data[2]) == 0x69 &&
1006
+ GETJOCTET(marker->data[3]) == 0x66 &&
1007
+ GETJOCTET(marker->data[4]) == 0x0 &&
1008
+ GETJOCTET(marker->data[5]) == 0x0;
1009
+ }
1010
+
1011
+ /*
1012
+ * call-seq:
1013
+ * reader.exif -> string
1014
+ *
1015
+ * Get the raw Exif data from the JPEG. This requires that the APP1 segment
1016
+ * has been selected by initialize (this is the default behavior).
1017
+ */
1018
+
1019
+ static VALUE
1020
+ exif(VALUE self)
1021
+ {
1022
+ struct jpeg_decompress_struct * cinfo;
1023
+ jpeg_saved_marker_ptr marker;
1024
+ int len;
1025
+
1026
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
1027
+
1028
+ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
1029
+ if (marker_is_exif(marker)) {
1030
+ len = marker->data_length - EXIF_OVERHEAD_LEN;
1031
+ return rb_str_new(marker->data + EXIF_OVERHEAD_LEN, len);
1032
+ }
1033
+ }
1034
+
1035
+ return Qnil;
1036
+ }
1037
+
1038
+ /*
1039
+ * call-seq:
1040
+ * reader[marker] -> array
1041
+ *
1042
+ * Read raw data from the given JPEG header marker. Note that the marker must
1043
+ * have been specified by initialize.
1044
+ *
1045
+ * The return from this method is an array, since there may be multiple
1046
+ * instances of a single marker in a JPEG header.
1047
+ */
1048
+
1049
+ static VALUE
1050
+ aref(VALUE self, VALUE marker_sym)
1051
+ {
1052
+ struct jpeg_decompress_struct * cinfo;
1053
+ jpeg_saved_marker_ptr marker;
1054
+ VALUE ary = rb_ary_new();
1055
+ int marker_i = sym_to_marker_code(marker_sym);
1056
+
1057
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
1058
+
1059
+ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next)
1060
+ if (marker->marker == marker_i)
1061
+ rb_ary_push(ary, rb_str_new(marker->data, marker->data_length));
1062
+
1063
+ return ary;
1064
+ }
1065
+
1066
+ /*
1067
+ * call-seq:
1068
+ * reader.saw_jfif_marker -> boolean
1069
+ *
1070
+ * Indicates whether a JFIF marker was found in the header.
1071
+ */
1072
+
1073
+ static VALUE
1074
+ saw_jfif_marker(VALUE self)
1075
+ {
1076
+ struct jpeg_decompress_struct * cinfo;
1077
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
1078
+ return cinfo->saw_JFIF_marker ? Qtrue : Qfalse;
1079
+ }
1080
+
1081
+ /*
1082
+ * call-seq:
1083
+ * reader.saw_adobe_marker -> boolean
1084
+ *
1085
+ * Indicates whether an Adobe marker was found in the header.
1086
+ */
1087
+
1088
+ static VALUE
1089
+ saw_adobe_marker(VALUE self)
1090
+ {
1091
+ struct jpeg_decompress_struct * cinfo;
1092
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
1093
+ return cinfo->saw_Adobe_marker ? Qtrue : Qfalse;
1094
+ }
1095
+
1096
+ /*
1097
+ * call-seq:
1098
+ * reader.lineno -> number
1099
+ *
1100
+ * Returns the number of the next line to be read from the image, starting at
1101
+ * 0.
1102
+ */
1103
+
1104
+ static VALUE
1105
+ lineno(VALUE self)
1106
+ {
1107
+ struct jpeg_decompress_struct * cinfo;
1108
+ Data_Get_Struct(self, struct jpeg_decompress_struct, cinfo);
1109
+ return INT2FIX(cinfo->output_scanline);
1110
+ }
1111
+
1112
+ /*
1113
+ * Document-class: Axon::JPEG::Reader
1114
+ *
1115
+ * Read compressed JPEG images from an IO.
1116
+ */
1117
+
1118
+ void
1119
+ Init_JPEG()
1120
+ {
1121
+ VALUE mAxon, mJPEG, cJPEGReader;
1122
+
1123
+ init_jerror(&jerr);
1124
+
1125
+ mAxon = rb_define_module("Axon");
1126
+ mJPEG = rb_define_module_under(mAxon, "JPEG");
1127
+ rb_const_set(mJPEG, rb_intern("LIB_VERSION"), INT2FIX(JPEG_LIB_VERSION));
1128
+ #ifdef JCS_EXTENSIONS
1129
+ rb_const_set(mJPEG, rb_intern("LIB_TURBO"), Qtrue);
1130
+ #else
1131
+ rb_const_set(mJPEG, rb_intern("LIB_TURBO"), Qfalse);
1132
+ #endif
1133
+ rb_define_singleton_method(mJPEG, "write", write_jpeg, -1);
1134
+
1135
+ cJPEGReader = rb_define_class_under(mJPEG, "Reader", rb_cObject);
1136
+ rb_define_alloc_func(cJPEGReader, allocate);
1137
+ rb_define_method(cJPEGReader, "initialize", initialize, -1);
1138
+ rb_define_method(cJPEGReader, "icc_profile", icc_profile, 0);
1139
+ rb_define_method(cJPEGReader, "exif", exif, 0);
1140
+ rb_define_method(cJPEGReader, "saw_jfif_marker", saw_jfif_marker, 0);
1141
+ rb_define_method(cJPEGReader, "saw_adobe_marker", saw_adobe_marker, 0);
1142
+ rb_define_method(cJPEGReader, "[]", aref, 1);
1143
+ rb_define_method(cJPEGReader, "in_color_model", in_color_model, 0);
1144
+ rb_define_method(cJPEGReader, "in_color_model=", set_in_color_model, 1);
1145
+ rb_define_method(cJPEGReader, "color_model", color_model, 0);
1146
+ rb_define_method(cJPEGReader, "color_model=", set_color_model, 1);
1147
+ rb_define_method(cJPEGReader, "components", components, 0);
1148
+ rb_define_method(cJPEGReader, "scale_num", scale_num, 0);
1149
+ rb_define_method(cJPEGReader, "scale_num=", set_scale_num, 1);
1150
+ rb_define_method(cJPEGReader, "scale_denom", scale_denom, 0);
1151
+ rb_define_method(cJPEGReader, "scale_denom=", set_scale_denom, 1);
1152
+ rb_define_method(cJPEGReader, "dct_method", dct_method, 0);
1153
+ rb_define_method(cJPEGReader, "dct_method=", set_dct_method, 1);
1154
+ rb_define_method(cJPEGReader, "width", width, 0);
1155
+ rb_define_method(cJPEGReader, "height", height, 0);
1156
+ rb_define_method(cJPEGReader, "lineno", lineno, 0);
1157
+ rb_define_method(cJPEGReader, "gets", j_gets, 0);
1158
+
1159
+ id_IFAST = rb_intern("IFAST");
1160
+ id_ISLOW = rb_intern("ISLOW");
1161
+ id_FLOAT = rb_intern("FLOAT");
1162
+ id_DEFAULT = rb_intern("DEFAULT");
1163
+ id_FASTEST = rb_intern("FASTEST");
1164
+
1165
+ id_GRAYSCALE = rb_intern("GRAYSCALE");
1166
+ id_RGB = rb_intern("RGB");
1167
+ id_YCbCr = rb_intern("YCbCr");
1168
+ id_CMYK = rb_intern("CMYK");
1169
+ id_YCCK = rb_intern("YCCK");
1170
+ id_UNKNOWN = rb_intern("UNKNOWN");
1171
+
1172
+ id_APP0 = rb_intern("APP0");
1173
+ id_APP1 = rb_intern("APP1");
1174
+ id_APP2 = rb_intern("APP2");
1175
+ id_APP3 = rb_intern("APP3");
1176
+ id_APP4 = rb_intern("APP4");
1177
+ id_APP5 = rb_intern("APP5");
1178
+ id_APP6 = rb_intern("APP6");
1179
+ id_APP7 = rb_intern("APP7");
1180
+ id_APP8 = rb_intern("APP8");
1181
+ id_APP9 = rb_intern("APP9");
1182
+ id_APP10 = rb_intern("APP10");
1183
+ id_APP11 = rb_intern("APP11");
1184
+ id_APP12 = rb_intern("APP12");
1185
+ id_APP13 = rb_intern("APP13");
1186
+ id_APP14 = rb_intern("APP14");
1187
+ id_APP15 = rb_intern("APP15");
1188
+ id_COM = rb_intern("COM");
1189
+
1190
+ id_read = rb_intern("read");
1191
+ id_write = rb_intern("write");
1192
+ id_width = rb_intern("width");
1193
+ id_height = rb_intern("height");
1194
+ id_color_model = rb_intern("color_model");
1195
+ id_components = rb_intern("components");
1196
+ id_gets = rb_intern("gets");
1197
+
1198
+ sym_icc_profile = ID2SYM(rb_intern("icc_profile"));
1199
+ sym_exif = ID2SYM(rb_intern("exif"));
1200
+ sym_quality = ID2SYM(rb_intern("quality"));
1201
+ sym_bufsize = ID2SYM(rb_intern("bufsize"));
1202
+
1203
+ rb_const_set(cJPEGReader, rb_intern("DEFAULT_DCT"),
1204
+ ID2SYM(j_dct_method_to_id(JDCT_DEFAULT)));
1205
+ rb_const_set(cJPEGReader, rb_intern("FASTEST_DCT"),
1206
+ ID2SYM(j_dct_method_to_id(JDCT_FASTEST)));
1207
+ }