oil 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4036eeaab5f839b40269e35db9d1c5e74f905a2d
4
+ data.tar.gz: c369f27885d7edc5353b0012973075b7b1969aac
5
+ SHA512:
6
+ metadata.gz: ff2f9e88da8fd2c1845e63ceffc1fd460d58a1ede777734c812eaad8a195bc7007575aa1221cd80c529a4f2ce0ddabaa23b900dd996c489c8dbe10fb07555025
7
+ data.tar.gz: ad0f4b2dfee53093fa6daf65a3d7deba426799e2921f0d4d6e76e6742efc423ad5f9a4429addf1f9b702eab2d6f0927d3f75c9d9014dd070b34414a2b55142d8
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Timothy Elliott
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -4,8 +4,8 @@ http://github.com/ender672/oil
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Oil resizes JPEG and PNG images. It aims for fast performance and low memory
8
- use.
7
+ Oil is a Ruby extension for resizing JPEG and PNG images. It aims for fast
8
+ performance and low memory use.
9
9
 
10
10
  == INSTALLATION:
11
11
 
@@ -14,24 +14,23 @@ use.
14
14
  == SYNOPSIS:
15
15
 
16
16
  require 'oil'
17
- io = File.open('image.jpg', 'rb')
18
17
 
19
- # prepare to resize JPEG to fit in a 200x300 box, preserving aspect ratio
20
- jpeg = Oil::JPEG.new(io, 200, 300)
18
+ # Oil uses IO objects for input & output.
19
+ io_in = File.open('image.jpg', 'rb')
20
+ io_out = File.open('image_resized.jpg', 'w')
21
21
 
22
- # yield successive parts of the resized and compressed JPEG file
23
- jpeg.each { |data| }
22
+ # Read the source image header and prepare to fit it into a 200x300 box.
23
+ img = Oil.new(io_in, 200, 300)
24
+
25
+ # Write the resized image to disk
26
+ img.each { |data| io_out << data }
24
27
 
25
28
  == REQUIREMENTS:
26
29
 
27
30
  These requirements do not apply to the JRuby gem.
28
31
 
29
- * IJG JPEG Library or libjpeg-turbo.
30
- * libpng
31
-
32
- Installing libjpeg headers (OSX):
33
-
34
- $ brew install libjpeg # requires homebrew (http://mxcl.github.com/homebrew/)
32
+ * libjpeg-turbo
33
+ * libpng
35
34
 
36
35
  Installing libjpeg and libpng headers (Debian/Ubuntu):
37
36
 
@@ -47,43 +46,12 @@ Compile & run unit tests. Should show no warnings and no failing tests:
47
46
  Valgrind should not complain (ruby-1.9.3p125, compiled with -O3):
48
47
 
49
48
  $ valgrind /path/to/ruby -Iext:test test/test_jpeg.rb
50
- $ valgrind --suppressions=test/valgrind_suppressions.txt /path/to/ruby -Iext:test test/test_png.rb
49
+ $ valgrind /path/to/ruby -Iext:test test/test_png.rb
51
50
 
52
51
  Tests should not leak memory:
53
52
 
54
- $ ruby -Iext:test -e "require 'test_jpeg.rb'; require 'test_png.rb'; loop{ MiniTest::Unit.new.run }"
53
+ $ ruby -Iext:test -e "require 'test_jpeg.rb'; require 'test_png.rb'; loop{ MiniTest.run }"
55
54
 
56
55
  Changes to the interpolator should be analyzed using ResampleScope:
57
56
 
58
57
  https://github.com/jsummers/resamplescope
59
-
60
- == TODO:
61
-
62
- * Windows
63
-
64
- == LICENSE:
65
-
66
- (The MIT License)
67
-
68
- Copyright (c) 2012
69
-
70
- * {Timothy Elliott}[http://holymonkey.com]
71
-
72
- Permission is hereby granted, free of charge, to any person obtaining
73
- a copy of this software and associated documentation files (the
74
- 'Software'), to deal in the Software without restriction, including
75
- without limitation the rights to use, copy, modify, merge, publish,
76
- distribute, sublicense, and/or sell copies of the Software, and to
77
- permit persons to whom the Software is furnished to do so, subject to
78
- the following conditions:
79
-
80
- The above copyright notice and this permission notice shall be
81
- included in all copies or substantial portions of the Software.
82
-
83
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
84
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
85
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
86
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
87
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
88
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
89
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,41 +1,45 @@
1
- require 'rake/clean'
1
+ require 'rake/extensiontask'
2
+ require 'rubygems/package_task'
2
3
  require 'rake/testtask'
3
4
 
4
- file 'ext/oil.jar' => FileList['ext/*.java'] do
5
- cd 'ext' do
6
- sh "javac -g -cp #{Config::CONFIG['prefix']}/lib/jruby.jar #{FileList['*.java']}"
7
- quoted_files = (FileList.new('*.class').to_a.map { |f| "'#{f}'" }).join(' ')
8
- sh "jar cf oil.jar #{quoted_files}"
9
- end
5
+ Rake::ExtensionTask.new('oil') do |ext|
6
+ ext.lib_dir = 'lib/oil'
10
7
  end
11
8
 
12
- file 'ext/Makefile' do
13
- cd 'ext' do
14
- ruby "extconf.rb #{ENV['EXTOPTS']}"
15
- end
9
+ s = Gem::Specification.new('oil', '0.1.0') do |s|
10
+ s.license = 'MIT'
11
+ s.summary = 'Resize JPEG and PNG images.'
12
+ s.description = 'Resize JPEG and PNG images, aiming for fast performance and low memory use.'
13
+ s.authors = ['Timothy Elliott']
14
+ s.email = 'tle@holymonkey.com'
15
+ s.files = %w{
16
+ Rakefile
17
+ README.rdoc
18
+ MIT-LICENSE
19
+ lib/oil.rb
20
+ ext/oil/resample.c
21
+ ext/oil/resample.h
22
+ ext/oil/yscaler.c
23
+ ext/oil/yscaler.h
24
+ ext/oil/jpeg.c
25
+ ext/oil/png.c
26
+ ext/oil/oil.c
27
+ ext/oil/extconf.rb
28
+ test/helper.rb
29
+ test/test_jpeg.rb
30
+ test/test_png.rb
31
+ }
32
+ s.homepage = 'http://github.com/ender672/oil'
33
+ s.extensions << 'ext/oil/extconf.rb'
34
+ s.extra_rdoc_files = ['README.rdoc']
16
35
  end
17
36
 
18
- file 'ext/oil.so' => FileList.new('ext/Makefile', 'ext/oil.c') do
19
- cd 'ext' do
20
- sh 'make'
21
- end
22
- end
37
+ Gem::PackageTask.new(s){}
23
38
 
24
39
  Rake::TestTask.new do |t|
25
- t.libs = ['ext', 'test']
26
- t.test_files = FileList['test/test_*.rb']
40
+ t.libs = ['lib', 'test']
41
+ t.test_files = FileList['test/test_jpeg.rb', 'test/test_png.rb']
27
42
  end
28
43
 
29
- CLEAN.add('ext/*{.o,.so,.log,.class,.jar}', 'ext/Makefile')
30
- CLOBBER.add('*.gem')
31
-
32
- desc 'Build the gem and include the java library'
33
- task :gem => "ext/oil.jar" do
34
- system "gem build oil.gemspec"
35
- end
36
-
37
- desc 'Compile the extension'
38
- task :compile => "ext/oil.#{RUBY_PLATFORM =~ /java/ ? 'jar' : 'so'}"
39
-
40
- task :test => :compile
41
- task :default => :test
44
+ task test: :compile
45
+ task default: :test
@@ -1,16 +1,6 @@
1
1
  require 'mkmf'
2
2
 
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)
3
+ $CFLAGS += " -O3 -ffast-math -march=native"
14
4
 
15
5
  unless have_header('jpeglib.h')
16
6
  abort "libjpeg headers were not found."
@@ -28,4 +18,4 @@ unless have_library('png')
28
18
  abort "libpng was not found."
29
19
  end
30
20
 
31
- create_makefile('oil')
21
+ create_makefile('oil/oil')
@@ -0,0 +1,861 @@
1
+ #include <ruby.h>
2
+ #include <jpeglib.h>
3
+ #include "resample.h"
4
+ #include "yscaler.h"
5
+
6
+ #define READ_SIZE 1024
7
+ #define WRITE_SIZE 1024
8
+
9
+ static ID id_GRAYSCALE, id_RGB, id_YCbCr, id_CMYK, id_YCCK, id_UNKNOWN;
10
+ static ID id_APP0, id_APP1, id_APP2, id_APP3, id_APP4, id_APP5, id_APP6,
11
+ id_APP7, id_APP8, id_APP9, id_APP10, id_APP11, id_APP12, id_APP13,
12
+ id_APP14, id_APP15, id_COM;
13
+ static ID id_read;
14
+
15
+ static VALUE sym_quality, sym_markers;
16
+
17
+ /* Color Space Conversion Helpers. */
18
+
19
+ static ID j_color_space_to_id(J_COLOR_SPACE cs)
20
+ {
21
+ switch (cs) {
22
+ case JCS_GRAYSCALE:
23
+ return id_GRAYSCALE;
24
+ case JCS_RGB:
25
+ return id_RGB;
26
+ case JCS_YCbCr:
27
+ return id_YCbCr;
28
+ case JCS_CMYK:
29
+ return id_CMYK;
30
+ case JCS_YCCK:
31
+ return id_YCCK;
32
+ default:
33
+ return id_UNKNOWN;
34
+ }
35
+ }
36
+
37
+ static J_COLOR_SPACE sym_to_j_color_space(VALUE sym)
38
+ {
39
+ ID rb = SYM2ID(sym);
40
+
41
+ if (rb == id_GRAYSCALE) {
42
+ return JCS_GRAYSCALE;
43
+ } else if (rb == id_RGB) {
44
+ return JCS_RGB;
45
+ } else if (rb == id_YCbCr) {
46
+ return JCS_YCbCr;
47
+ } else if (rb == id_CMYK) {
48
+ return JCS_CMYK;
49
+ } else if (rb == id_YCCK) {
50
+ return JCS_YCCK;
51
+ }
52
+ rb_raise(rb_eRuntimeError, "Color space not recognized.");
53
+ }
54
+
55
+ static int sym_to_marker_code(VALUE sym)
56
+ {
57
+ ID rb = SYM2ID(sym);
58
+
59
+ if (rb == id_COM) {
60
+ return JPEG_COM;
61
+ } else if (rb == id_APP0) {
62
+ return JPEG_APP0;
63
+ } else if (rb == id_APP1) {
64
+ return JPEG_APP0 + 1;
65
+ } else if (rb == id_APP2) {
66
+ return JPEG_APP0 + 2;
67
+ } else if (rb == id_APP3) {
68
+ return JPEG_APP0 + 3;
69
+ } else if (rb == id_APP4) {
70
+ return JPEG_APP0 + 4;
71
+ } else if (rb == id_APP5) {
72
+ return JPEG_APP0 + 5;
73
+ } else if (rb == id_APP6) {
74
+ return JPEG_APP0 + 6;
75
+ } else if (rb == id_APP7) {
76
+ return JPEG_APP0 + 7;
77
+ } else if (rb == id_APP8) {
78
+ return JPEG_APP0 + 8;
79
+ } else if (rb == id_APP9) {
80
+ return JPEG_APP0 + 9;
81
+ } else if (rb == id_APP10) {
82
+ return JPEG_APP0 + 10;
83
+ } else if (rb == id_APP11) {
84
+ return JPEG_APP0 + 11;
85
+ } else if (rb == id_APP12) {
86
+ return JPEG_APP0 + 12;
87
+ } else if (rb == id_APP13) {
88
+ return JPEG_APP0 + 13;
89
+ } else if (rb == id_APP14) {
90
+ return JPEG_APP0 + 14;
91
+ } else if (rb == id_APP15) {
92
+ return JPEG_APP0 + 15;
93
+ }
94
+ rb_raise(rb_eRuntimeError, "Marker code not recognized.");
95
+ }
96
+
97
+ static VALUE marker_code_to_sym(int marker_code)
98
+ {
99
+ switch(marker_code) {
100
+ case JPEG_COM:
101
+ return ID2SYM(id_COM);
102
+ case JPEG_APP0:
103
+ return ID2SYM(id_APP0);
104
+ case JPEG_APP0 + 1:
105
+ return ID2SYM(id_APP1);
106
+ case JPEG_APP0 + 2:
107
+ return ID2SYM(id_APP2);
108
+ case JPEG_APP0 + 3:
109
+ return ID2SYM(id_APP3);
110
+ case JPEG_APP0 + 4:
111
+ return ID2SYM(id_APP4);
112
+ case JPEG_APP0 + 5:
113
+ return ID2SYM(id_APP5);
114
+ case JPEG_APP0 + 6:
115
+ return ID2SYM(id_APP6);
116
+ case JPEG_APP0 + 7:
117
+ return ID2SYM(id_APP7);
118
+ case JPEG_APP0 + 8:
119
+ return ID2SYM(id_APP8);
120
+ case JPEG_APP0 + 9:
121
+ return ID2SYM(id_APP9);
122
+ case JPEG_APP0 + 10:
123
+ return ID2SYM(id_APP10);
124
+ case JPEG_APP0 + 11:
125
+ return ID2SYM(id_APP11);
126
+ case JPEG_APP0 + 12:
127
+ return ID2SYM(id_APP12);
128
+ case JPEG_APP0 + 13:
129
+ return ID2SYM(id_APP13);
130
+ case JPEG_APP0 + 14:
131
+ return ID2SYM(id_APP14);
132
+ case JPEG_APP0 + 15:
133
+ return ID2SYM(id_APP15);
134
+ }
135
+ rb_raise(rb_eRuntimeError, "Marker code not recognized.");
136
+ }
137
+
138
+ /* JPEG Error Handler -- raise a ruby exception. */
139
+
140
+ void output_message(j_common_ptr cinfo)
141
+ {
142
+ char buffer[JMSG_LENGTH_MAX];
143
+ cinfo->err->format_message(cinfo, buffer);
144
+ rb_warning("jpeglib: %s", buffer);
145
+ }
146
+
147
+ static void error_exit(j_common_ptr dinfo)
148
+ {
149
+ char buffer[JMSG_LENGTH_MAX];
150
+ (*dinfo->err->format_message) (dinfo, buffer);
151
+ rb_raise(rb_eRuntimeError, "jpeglib: %s", buffer);
152
+ }
153
+
154
+ /* JPEG Data Source */
155
+
156
+ struct readerdata {
157
+ struct jpeg_decompress_struct dinfo;
158
+ struct jpeg_source_mgr mgr;
159
+ struct jpeg_error_mgr jerr;
160
+ int locked;
161
+ VALUE source_io;
162
+ VALUE buffer;
163
+ uint32_t scale_width;
164
+ uint32_t scale_height;
165
+ };
166
+
167
+ static void null_jdecompress(j_decompress_ptr dinfo) {}
168
+
169
+ static boolean fill_input_buffer(j_decompress_ptr dinfo)
170
+ {
171
+ VALUE string;
172
+ long strl;
173
+ struct readerdata *reader;
174
+
175
+ reader = (struct readerdata *)dinfo;
176
+
177
+ string = rb_funcall(reader->source_io, id_read, 1, INT2FIX(READ_SIZE));
178
+ Check_Type(string, T_STRING);
179
+
180
+ strl = RSTRING_LEN(string);
181
+ if (strl > READ_SIZE) {
182
+ rb_raise(rb_eRuntimeError, "IO returned too much data.");
183
+ }
184
+
185
+ if (!strl) {
186
+ string = rb_str_new2("\xFF\xD9");
187
+ strl = 2;
188
+ }
189
+
190
+ reader->buffer = string;
191
+ reader->mgr.bytes_in_buffer = strl;
192
+ reader->mgr.next_input_byte = (unsigned char *)RSTRING_PTR(string);
193
+
194
+ return TRUE;
195
+ }
196
+
197
+ static void skip_input_data(j_decompress_ptr dinfo, long num_bytes)
198
+ {
199
+ struct jpeg_source_mgr * src = dinfo->src;
200
+
201
+ if (num_bytes > 0) {
202
+ while (num_bytes > (long) src->bytes_in_buffer) {
203
+ num_bytes -= (long) src->bytes_in_buffer;
204
+ (void) (*src->fill_input_buffer) (dinfo);
205
+ }
206
+ src->next_input_byte += (size_t) num_bytes;
207
+ src->bytes_in_buffer -= (size_t) num_bytes;
208
+ }
209
+ }
210
+
211
+ /* Ruby GC */
212
+
213
+ static void deallocate(struct readerdata *reader)
214
+ {
215
+ jpeg_destroy_decompress(&reader->dinfo);
216
+ free(reader);
217
+ }
218
+
219
+ static void mark(struct readerdata *reader)
220
+ {
221
+ if (!NIL_P(reader->source_io)) {
222
+ rb_gc_mark(reader->source_io);
223
+ }
224
+
225
+ if (!NIL_P(reader->buffer)) {
226
+ rb_gc_mark(reader->buffer);
227
+ }
228
+ }
229
+
230
+ static VALUE allocate(VALUE klass)
231
+ {
232
+ struct readerdata *reader;
233
+ VALUE self;
234
+ self = Data_Make_Struct(klass, struct readerdata, mark, deallocate, reader);
235
+
236
+ jpeg_std_error(&reader->jerr);
237
+ reader->jerr.error_exit = error_exit;
238
+ reader->jerr.output_message = output_message;
239
+ reader->dinfo.err = &reader->jerr;
240
+ reader->mgr.init_source = null_jdecompress;
241
+ reader->mgr.fill_input_buffer = fill_input_buffer;
242
+ reader->mgr.skip_input_data = skip_input_data;
243
+ reader->mgr.resync_to_restart = jpeg_resync_to_restart;
244
+ reader->mgr.term_source = null_jdecompress;
245
+ return self;
246
+ }
247
+
248
+ /* Helper that raises an exception if the reader is locked. */
249
+
250
+ static void raise_if_locked(struct readerdata *reader)
251
+ {
252
+ if (reader->locked) {
253
+ rb_raise(rb_eRuntimeError, "Can't modify a Reader after decompress started.");
254
+ }
255
+ }
256
+
257
+ /*
258
+ * call-seq:
259
+ * Reader.new(io_in [, markers]) -> reader
260
+ *
261
+ * Creates a new JPEG Reader. +io_in+ must be an IO-like object that responds
262
+ * to read(size).
263
+ *
264
+ * +markers+ should be an array of valid JPEG header marker symbols. Valid
265
+ * symbols are :APP0 through :APP15 and :COM.
266
+ *
267
+ * If performance is important, you can avoid reading any header markers by
268
+ * supplying an empty array, [].
269
+ *
270
+ * When markers are not specified, we read all known JPEG markers.
271
+ *
272
+ * io = File.open("image.jpg", "r")
273
+ * reader = Oil::JPEGReader.new(io)
274
+ *
275
+ * io = File.open("image.jpg", "r")
276
+ * reader = Oil::JPEGReader.new(io, [:APP1, :APP2])
277
+ */
278
+
279
+ static VALUE initialize(int argc, VALUE *argv, VALUE self)
280
+ {
281
+ struct readerdata *reader;
282
+ VALUE io, markers;
283
+ struct jpeg_decompress_struct *dinfo;
284
+ int i, marker_code;
285
+
286
+ Data_Get_Struct(self, struct readerdata, reader);
287
+ dinfo = &reader->dinfo;
288
+
289
+ /* If source_io has already been set, then this is a re-used jpeg reader
290
+ * object. This means we need to abort the previous decompress to
291
+ * prevent memory leaks.
292
+ */
293
+ if (reader->source_io) {
294
+ jpeg_abort_decompress(dinfo);
295
+ }
296
+
297
+ jpeg_create_decompress(&reader->dinfo);
298
+ reader->dinfo.src = &reader->mgr;
299
+
300
+ rb_scan_args(argc, argv, "11", &io, &markers);
301
+ reader->source_io = io;
302
+ reader->mgr.bytes_in_buffer = 0;
303
+
304
+ if(!NIL_P(markers)) {
305
+ Check_Type(markers, T_ARRAY);
306
+ for (i=0; i<RARRAY_LEN(markers); i++) {
307
+ marker_code = sym_to_marker_code(RARRAY_PTR(markers)[i]);
308
+ jpeg_save_markers(dinfo, marker_code, 0xFFFF);
309
+ }
310
+ }
311
+
312
+ /* Be warned that this can raise a ruby exception and longjmp away. */
313
+ jpeg_read_header(&reader->dinfo, TRUE);
314
+
315
+ jpeg_calc_output_dimensions(dinfo);
316
+
317
+ return self;
318
+ }
319
+
320
+ /*
321
+ * call-seq:
322
+ * reader.components -> number
323
+ *
324
+ * Retrieve the number of components as stored in the JPEG image.
325
+ */
326
+
327
+ static VALUE components(VALUE self)
328
+ {
329
+ struct jpeg_decompress_struct * dinfo;
330
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
331
+ return INT2FIX(dinfo->num_components);
332
+ }
333
+
334
+ /*
335
+ * call-seq:
336
+ * reader.color_space -> symbol
337
+ *
338
+ * Returns a symbol representing the color model in which the JPEG is stored.
339
+ *
340
+ * This does not have to be set explicitly and can be relied upon when the file
341
+ * conforms to JFIF or Adobe conventions. Otherwise it is guessed.
342
+ *
343
+ * Possible color models are: :GRAYSCALE, :RGB, :YCbCr, :CMYK, and :YCCK. This
344
+ * method will return :UNKNOWN if the color model is not recognized.
345
+ */
346
+
347
+ static VALUE color_space(VALUE self)
348
+ {
349
+ struct jpeg_decompress_struct * dinfo;
350
+ ID id;
351
+
352
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
353
+ id = j_color_space_to_id(dinfo->jpeg_color_space);
354
+
355
+ return ID2SYM(id);
356
+ }
357
+
358
+ /*
359
+ * call-seq:
360
+ * reader.out_color_space -> symbol
361
+ *
362
+ * Returns a symbol representing the color model to which the image will be
363
+ * converted on decompress.
364
+ */
365
+
366
+ static VALUE out_color_space(VALUE self)
367
+ {
368
+ struct jpeg_decompress_struct * dinfo;
369
+ ID id;
370
+
371
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
372
+ id = j_color_space_to_id(dinfo->jpeg_color_space);
373
+
374
+ return ID2SYM(id);
375
+ }
376
+
377
+ /*
378
+ * call-seq:
379
+ * reader.out_color_space = symbol
380
+ *
381
+ * Set the color model to which teh image will be converted on decompress.
382
+ */
383
+
384
+ static VALUE set_out_color_space(VALUE self, VALUE cs)
385
+ {
386
+ struct readerdata *reader;
387
+
388
+ Data_Get_Struct(self, struct readerdata, reader);
389
+ raise_if_locked(reader);
390
+
391
+ reader->dinfo.jpeg_color_space = sym_to_j_color_space(cs);
392
+ jpeg_calc_output_dimensions(&reader->dinfo);
393
+ return cs;
394
+ }
395
+
396
+ /*
397
+ * call-seq:
398
+ * reader.width -> number
399
+ *
400
+ * Retrieve the width of the image.
401
+ */
402
+
403
+ static VALUE width(VALUE self)
404
+ {
405
+ struct jpeg_decompress_struct * dinfo;
406
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
407
+ return INT2FIX(dinfo->image_width);
408
+ }
409
+
410
+ /*
411
+ * call-seq:
412
+ * reader.height -> number
413
+ *
414
+ * Retrieve the height of the image.
415
+ */
416
+
417
+ static VALUE height(VALUE self)
418
+ {
419
+ struct jpeg_decompress_struct * dinfo;
420
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
421
+ return INT2FIX(dinfo->image_height);
422
+ }
423
+
424
+ /*
425
+ * call-seq:
426
+ * reader.markers -> hash
427
+ *
428
+ * Get a hash of raw marker data from the JPEG.
429
+ *
430
+ * The keys in the hash are the marker codes as symbols. The values are arrays.
431
+ *
432
+ * Arrays since there may be multiple instances of a single marker in a JPEG
433
+ * marker.
434
+ */
435
+
436
+ static VALUE markers(VALUE self)
437
+ {
438
+ struct jpeg_decompress_struct *dinfo;
439
+ jpeg_saved_marker_ptr marker;
440
+ VALUE hash, ary, key, val;
441
+
442
+ hash = rb_hash_new();
443
+
444
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
445
+
446
+ for (marker=dinfo->marker_list; marker; marker=marker->next) {
447
+ key = marker_code_to_sym(marker->marker);
448
+ ary = rb_hash_aref(hash, key);
449
+ if (NIL_P(ary)) {
450
+ ary = rb_ary_new();
451
+ rb_hash_aset(hash, key, ary);
452
+ }
453
+ val = rb_str_new((char *)marker->data, marker->data_length);
454
+ rb_ary_push(ary, val);
455
+ }
456
+
457
+ return hash;
458
+ }
459
+
460
+ /*
461
+ * call-seq:
462
+ * reader.scale_num -> number
463
+ *
464
+ * Retrieve the numerator of the fraction by which the JPEG will be scaled as
465
+ * it is read. This is always 1 for libjpeg version 6b. In version 8b this can
466
+ * be 1 to 16.
467
+ */
468
+
469
+ static VALUE scale_num(VALUE self)
470
+ {
471
+ struct jpeg_decompress_struct *dinfo;
472
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
473
+ return INT2FIX(dinfo->scale_num);
474
+ }
475
+
476
+ /*
477
+ * call-seq:
478
+ * reader.scale_num = number
479
+ *
480
+ * Set the numerator of the fraction by which the JPEG will be scaled as it is
481
+ * read. This must always be 1 for libjpeg version 6b. In version 8b this can
482
+ * be set to 1 through 16.
483
+ */
484
+
485
+ static VALUE set_scale_num(VALUE self, VALUE scale_num)
486
+ {
487
+ struct readerdata *reader;
488
+
489
+ Data_Get_Struct(self, struct readerdata, reader);
490
+ raise_if_locked(reader);
491
+
492
+ reader->dinfo.scale_num = NUM2INT(scale_num);
493
+ jpeg_calc_output_dimensions(&reader->dinfo);
494
+ return scale_num;
495
+ }
496
+
497
+ /*
498
+ * call-seq:
499
+ * reader.scale_denom -> number
500
+ *
501
+ * Retrieve the denominator of the fraction by which the JPEG will be scaled as
502
+ * it is read. This is 1, 2, 4, or 8 for libjpeg version 6b. In version 8b this
503
+ * is always the source DCT size, which is 8 for baseline JPEG.
504
+ */
505
+
506
+ static VALUE scale_denom(VALUE self)
507
+ {
508
+ struct jpeg_decompress_struct *dinfo;
509
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
510
+ return INT2FIX(dinfo->scale_denom);
511
+ }
512
+
513
+ /*
514
+ * call-seq:
515
+ * reader.scale_denom = number
516
+ *
517
+ * Set the denominator of the fraction by which the JPEG will be scaled as it
518
+ * is read. This can be set to 1, 2, 4, or 8 for libjpeg version 6b. In version
519
+ * 8b this must always be the source DCT size, which is 8 for baseline JPEG.
520
+ *
521
+ * Prior to version 1.2, libjpeg-turbo will not scale down images on
522
+ * decompression, and this option will do nothing.
523
+ */
524
+
525
+ static VALUE set_scale_denom(VALUE self, VALUE scale_denom)
526
+ {
527
+ struct readerdata *reader;
528
+
529
+ Data_Get_Struct(self, struct readerdata, reader);
530
+ raise_if_locked(reader);
531
+
532
+ reader->dinfo.scale_denom = NUM2INT(scale_denom);
533
+ jpeg_calc_output_dimensions(&reader->dinfo);
534
+ return scale_denom;
535
+ }
536
+
537
+ /*
538
+ * call-seq:
539
+ * reader.scale_width -> number
540
+ *
541
+ * Retrieve the width to which the image will be resized after decompression. A
542
+ * width of 0 means the image will remain at original width.
543
+ */
544
+
545
+ static VALUE scale_width(VALUE self)
546
+ {
547
+ struct readerdata *reader;
548
+ Data_Get_Struct(self, struct readerdata, reader);
549
+ return INT2FIX(reader->scale_width);
550
+ }
551
+
552
+ /*
553
+ * call-seq:
554
+ * reader.scale_width = number
555
+ *
556
+ * Set the width to which the image will be resized after decompression. A
557
+ * width of 0 means the image will remain at original width.
558
+ */
559
+
560
+ static VALUE set_scale_width(VALUE self, VALUE scale_width)
561
+ {
562
+ struct readerdata *reader;
563
+ Data_Get_Struct(self, struct readerdata, reader);
564
+ raise_if_locked(reader);
565
+ reader->scale_width = NUM2INT(scale_width);
566
+ return scale_width;
567
+ }
568
+
569
+ /*
570
+ * call-seq:
571
+ * reader.scale_height -> number
572
+ *
573
+ * Retrieve the height to which the image will be resized after decompression. A
574
+ * height of 0 means the image will remain at original height.
575
+ */
576
+
577
+ static VALUE scale_height(VALUE self)
578
+ {
579
+ struct readerdata *reader;
580
+ Data_Get_Struct(self, struct readerdata, reader);
581
+ return INT2FIX(reader->scale_height);
582
+ }
583
+
584
+ /*
585
+ * call-seq:
586
+ * reader.scale_height = number
587
+ *
588
+ * Set the height to which the image will be resized after decompression. A
589
+ * height of 0 means the image will remain at original height.
590
+ */
591
+
592
+ static VALUE set_scale_height(VALUE self, VALUE scale_height)
593
+ {
594
+ struct readerdata *reader;
595
+ Data_Get_Struct(self, struct readerdata, reader);
596
+ raise_if_locked(reader);
597
+ reader->scale_height = NUM2INT(scale_height);
598
+ return scale_height;
599
+ }
600
+
601
+ /* JPEG Data Destination */
602
+
603
+ struct writerdata {
604
+ struct jpeg_compress_struct cinfo;
605
+ struct jpeg_destination_mgr mgr;
606
+ VALUE buffer;
607
+ };
608
+
609
+ static void init_destination(j_compress_ptr cinfo)
610
+ {
611
+ struct writerdata *writer;
612
+
613
+ writer = (struct writerdata *)cinfo;
614
+ writer->buffer = rb_str_new(NULL, WRITE_SIZE);
615
+ writer->mgr.next_output_byte = (JOCTET *)RSTRING_PTR(writer->buffer);
616
+ writer->mgr.free_in_buffer = WRITE_SIZE;
617
+ }
618
+
619
+ static boolean empty_output_buffer(j_compress_ptr cinfo)
620
+ {
621
+ struct writerdata *writer;
622
+
623
+ writer = (struct writerdata *)cinfo;
624
+ rb_yield(writer->buffer);
625
+ init_destination(cinfo);
626
+ return TRUE;
627
+ }
628
+
629
+ static void term_destination(j_compress_ptr cinfo)
630
+ {
631
+ struct writerdata *writer;
632
+ size_t datacount;
633
+
634
+ writer = (struct writerdata *)cinfo;
635
+ datacount = WRITE_SIZE - writer->mgr.free_in_buffer;
636
+
637
+ if (datacount > 0) {
638
+ rb_str_set_len(writer->buffer, datacount);
639
+ rb_yield(writer->buffer);
640
+ }
641
+ }
642
+
643
+ static int markerhash_each(VALUE marker_code_v, VALUE marker_ary, VALUE cinfo_v)
644
+ {
645
+ struct jpeg_compress_struct *cinfo;
646
+ int i, marker_code;
647
+ size_t strl;
648
+ VALUE marker;
649
+
650
+ cinfo = (struct jpeg_compress_struct *)cinfo_v;
651
+ marker_code = sym_to_marker_code(marker_code_v);
652
+
653
+ Check_Type(marker_ary, T_ARRAY);
654
+ for (i=0; i<RARRAY_LEN(marker_ary); i++) {
655
+ marker = rb_ary_entry(marker_ary, i);
656
+ Check_Type(marker, T_STRING);
657
+ strl = RSTRING_LEN(marker);
658
+ jpeg_write_marker(cinfo, marker_code, (JOCTET *)RSTRING_PTR(marker), strl);
659
+ }
660
+
661
+ return ST_CONTINUE;
662
+ }
663
+
664
+ struct write_jpeg_args {
665
+ VALUE opts;
666
+ struct readerdata *reader;
667
+ struct writerdata *writer;
668
+ unsigned char *inwidthbuf;
669
+ unsigned char *outwidthbuf;
670
+ struct yscaler *ys;
671
+ };
672
+
673
+ static VALUE each2(struct write_jpeg_args *args)
674
+ {
675
+ struct writerdata *writer;
676
+ struct jpeg_decompress_struct *dinfo;
677
+ struct jpeg_compress_struct *cinfo;
678
+ unsigned char *inwidthbuf, *outwidthbuf, *yinbuf;
679
+ struct yscaler *ys;
680
+ uint32_t i, scalex, scaley;
681
+ VALUE quality, markers;
682
+ int cmp, opts;
683
+
684
+ writer = args->writer;
685
+ inwidthbuf = args->inwidthbuf;
686
+ outwidthbuf = args->outwidthbuf;
687
+ ys = args->ys;
688
+ dinfo = &args->reader->dinfo;
689
+ cinfo = &writer->cinfo;
690
+ scalex = args->reader->scale_width;
691
+ scaley = args->reader->scale_height;
692
+
693
+ cmp = dinfo->output_components;
694
+ opts = dinfo->out_color_space == JCS_EXT_RGBX ? OIL_FILLER : 0;
695
+
696
+ writer->mgr.init_destination = init_destination;
697
+ writer->mgr.empty_output_buffer = empty_output_buffer;
698
+ writer->mgr.term_destination = term_destination;
699
+ writer->cinfo.dest = &writer->mgr;
700
+ writer->cinfo.image_width = scalex;
701
+ writer->cinfo.image_height = scaley;
702
+ writer->cinfo.in_color_space = dinfo->out_color_space;
703
+ writer->cinfo.input_components = cmp;
704
+
705
+ jpeg_set_defaults(cinfo);
706
+
707
+ if (!NIL_P(args->opts)) {
708
+ quality = rb_hash_aref(args->opts, sym_quality);
709
+ if (!NIL_P(quality)) {
710
+ jpeg_set_quality(cinfo, FIX2INT(quality), FALSE);
711
+ }
712
+
713
+ markers = rb_hash_aref(args->opts, sym_markers);
714
+ if (!NIL_P(markers)) {
715
+ Check_Type(markers, T_HASH);
716
+ rb_hash_foreach(markers, markerhash_each, (VALUE)cinfo);
717
+ }
718
+ }
719
+
720
+ jpeg_start_compress(cinfo, TRUE);
721
+ jpeg_start_decompress(dinfo);
722
+
723
+ for(i=0; i<scaley; i++) {
724
+ while ((yinbuf = yscaler_next(ys))) {
725
+ jpeg_read_scanlines(dinfo, (JSAMPARRAY)&inwidthbuf, 1);
726
+ xscale(inwidthbuf, dinfo->output_width, yinbuf, scalex, cmp, opts);
727
+ }
728
+ yscaler_scale(ys, outwidthbuf, scalex, cmp, opts);
729
+ jpeg_write_scanlines(cinfo, (JSAMPARRAY)&outwidthbuf, 1);
730
+ }
731
+
732
+ jpeg_finish_compress(cinfo);
733
+
734
+ return Qnil;
735
+ }
736
+
737
+ /*
738
+ * call-seq:
739
+ * reader.each(opts, &block) -> self
740
+ *
741
+ * Yields a series of binary strings that make up the output JPEG image.
742
+ *
743
+ * Options is a hash which may have the following symbols:
744
+ *
745
+ * :quality - JPEG quality setting. Betweein 0 and 100.
746
+ * :markers - Custom markers to include in the output JPEG. Must be a hash where
747
+ * the keys are :APP[0-15] or :COM and the values are arrays of strings that
748
+ * will be inserted into the markers.
749
+ */
750
+
751
+ static VALUE each(int argc, VALUE *argv, VALUE self)
752
+ {
753
+ struct readerdata *reader;
754
+ struct writerdata writer;
755
+ int cmp, state;
756
+ struct write_jpeg_args args;
757
+ unsigned char *inwidthbuf, *outwidthbuf;
758
+ struct yscaler ys;
759
+ VALUE opts;
760
+
761
+ rb_scan_args(argc, argv, "01", &opts);
762
+
763
+ Data_Get_Struct(self, struct readerdata, reader);
764
+
765
+ if (!reader->scale_width) {
766
+ reader->scale_width = reader->dinfo.output_width;
767
+ }
768
+ if (!reader->scale_height) {
769
+ reader->scale_height = reader->dinfo.output_height;
770
+ }
771
+
772
+ writer.cinfo.err = &reader->jerr;
773
+ jpeg_create_compress(&writer.cinfo);
774
+
775
+ cmp = reader->dinfo.output_components;
776
+ inwidthbuf = malloc(reader->dinfo.output_width * cmp);
777
+ outwidthbuf = malloc(reader->scale_width * cmp);
778
+ yscaler_init(&ys, reader->dinfo.output_height, reader->scale_height,
779
+ reader->scale_width * cmp);
780
+
781
+ args.reader = reader;
782
+ args.opts = opts;
783
+ args.writer = &writer;
784
+ args.inwidthbuf = inwidthbuf;
785
+ args.outwidthbuf = outwidthbuf;
786
+ args.ys = &ys;
787
+ reader->locked = 1;
788
+ rb_protect((VALUE(*)(VALUE))each2, (VALUE)&args, &state);
789
+
790
+ yscaler_free(&ys);
791
+ free(inwidthbuf);
792
+ free(outwidthbuf);
793
+ jpeg_destroy_compress(&writer.cinfo);
794
+
795
+ if (state) {
796
+ rb_jump_tag(state);
797
+ }
798
+
799
+ return self;
800
+ }
801
+
802
+ /*
803
+ * Document-class: Oil::JPEGReader
804
+ *
805
+ * Read a compressed JPEG image given an IO object.
806
+ */
807
+
808
+ void Init_jpeg()
809
+ {
810
+ VALUE mOil, cJPEGReader;
811
+
812
+ mOil = rb_const_get(rb_cObject, rb_intern("Oil"));
813
+
814
+ cJPEGReader = rb_define_class_under(mOil, "JPEGReader", rb_cObject);
815
+ rb_define_alloc_func(cJPEGReader, allocate);
816
+ rb_define_method(cJPEGReader, "initialize", initialize, -1);
817
+ rb_define_method(cJPEGReader, "markers", markers, 0);
818
+ rb_define_method(cJPEGReader, "color_space", color_space, 0);
819
+ rb_define_method(cJPEGReader, "out_color_space", out_color_space, 0);
820
+ rb_define_method(cJPEGReader, "color_space=", set_out_color_space, 1);
821
+ rb_define_method(cJPEGReader, "components", components, 0);
822
+ rb_define_method(cJPEGReader, "width", width, 0);
823
+ rb_define_method(cJPEGReader, "height", height, 0);
824
+ rb_define_method(cJPEGReader, "each", each, -1);
825
+ rb_define_method(cJPEGReader, "scale_num", scale_num, 0);
826
+ rb_define_method(cJPEGReader, "scale_num=", set_scale_num, 1);
827
+ rb_define_method(cJPEGReader, "scale_denom", scale_denom, 0);
828
+ rb_define_method(cJPEGReader, "scale_denom=", set_scale_denom, 1);
829
+ rb_define_method(cJPEGReader, "scale_width", scale_width, 0);
830
+ rb_define_method(cJPEGReader, "scale_width=", set_scale_width, 1);
831
+ rb_define_method(cJPEGReader, "scale_height", scale_height, 0);
832
+ rb_define_method(cJPEGReader, "scale_height=", set_scale_height, 1);
833
+
834
+ id_GRAYSCALE = rb_intern("GRAYSCALE");
835
+ id_RGB = rb_intern("RGB");
836
+ id_YCbCr = rb_intern("YCbCr");
837
+ id_CMYK = rb_intern("CMYK");
838
+ id_YCCK = rb_intern("YCCK");
839
+ id_UNKNOWN = rb_intern("UNKNOWN");
840
+ id_APP0 = rb_intern("APP0");
841
+ id_APP1 = rb_intern("APP1");
842
+ id_APP2 = rb_intern("APP2");
843
+ id_APP3 = rb_intern("APP3");
844
+ id_APP4 = rb_intern("APP4");
845
+ id_APP5 = rb_intern("APP5");
846
+ id_APP6 = rb_intern("APP6");
847
+ id_APP7 = rb_intern("APP7");
848
+ id_APP8 = rb_intern("APP8");
849
+ id_APP9 = rb_intern("APP9");
850
+ id_APP10 = rb_intern("APP10");
851
+ id_APP11 = rb_intern("APP11");
852
+ id_APP12 = rb_intern("APP12");
853
+ id_APP13 = rb_intern("APP13");
854
+ id_APP14 = rb_intern("APP14");
855
+ id_APP15 = rb_intern("APP15");
856
+ id_COM = rb_intern("COM");
857
+ id_read = rb_intern("read");
858
+
859
+ sym_quality = ID2SYM(rb_intern("quality"));
860
+ sym_markers = ID2SYM(rb_intern("markers"));
861
+ }