oil 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README.rdoc +61 -0
  2. data/Rakefile +40 -0
  3. data/ext/extconf.rb +13 -0
  4. data/ext/oil.c +413 -0
  5. data/test/test_jpeg.rb +190 -0
  6. metadata +52 -0
data/README.rdoc ADDED
@@ -0,0 +1,61 @@
1
+ = Oil
2
+
3
+ http://github.com/ender672/oil
4
+
5
+ == DESCRIPTION:
6
+
7
+ Resize JPEG images.
8
+
9
+ == SYNOPSIS:
10
+
11
+ # Resize a JPEG image
12
+ require 'oil'
13
+
14
+ io = File.open('image.jpg', 'rb')
15
+ jpeg = Oil::JPEG.new(io, 200, 300)
16
+ jpeg.each do |data|
17
+ # yields successive parts of the jpeg file
18
+ end
19
+
20
+ == REQUIREMENTS:
21
+
22
+ * IJG JPEG Library Version 6b or later.
23
+
24
+ Installing libjpeg headers (OSX):
25
+
26
+ $ brew install libjpeg # requires homebrew (http://mxcl.github.com/homebrew/)
27
+
28
+ Installing libjpeg headers (Debian/Ubuntu):
29
+
30
+ $ sudo apt-get install libjpeg-dev
31
+
32
+ == INSTALLATION:
33
+
34
+ $ gem install oil
35
+
36
+ == LICENSE:
37
+
38
+ (The MIT License)
39
+
40
+ Copyright (c) 2012
41
+
42
+ * {Timothy Elliott}[http://holymonkey.com]
43
+
44
+ Permission is hereby granted, free of charge, to any person obtaining
45
+ a copy of this software and associated documentation files (the
46
+ 'Software'), to deal in the Software without restriction, including
47
+ without limitation the rights to use, copy, modify, merge, publish,
48
+ distribute, sublicense, and/or sell copies of the Software, and to
49
+ permit persons to whom the Software is furnished to do so, subject to
50
+ the following conditions:
51
+
52
+ The above copyright notice and this permission notice shall be
53
+ included in all copies or substantial portions of the Software.
54
+
55
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
56
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
57
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
58
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
59
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
60
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
61
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+
4
+ file 'ext/Makefile' do
5
+ cd 'ext' do
6
+ ruby "extconf.rb #{ENV['EXTOPTS']}"
7
+ end
8
+ end
9
+
10
+ file 'ext/oil.so' => FileList.new('ext/Makefile', 'ext/oil.c') do
11
+ cd 'ext' do
12
+ sh 'make'
13
+ end
14
+ end
15
+
16
+ desc 'Clean up Rubinius .rbc files.'
17
+ namespace :clean do
18
+ task :rbc do
19
+ system "find . -name *.rbc -delete"
20
+ end
21
+ end
22
+
23
+ Rake::TestTask.new do |t|
24
+ t.libs = ['ext']
25
+ t.test_files = FileList['test/test*.rb']
26
+ end
27
+
28
+ CLEAN.add('ext/*{.o,.so,.log}', 'ext/Makefile')
29
+ CLOBBER.add('*.gem')
30
+
31
+ desc 'Build the gem'
32
+ task :gem do
33
+ system "gem build oil.gemspec"
34
+ end
35
+
36
+ desc 'Compile the extension'
37
+ task :compile => "ext/oil.so"
38
+
39
+ task :test => :compile
40
+ task :default => :test
data/ext/extconf.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('jpeg')
4
+
5
+ unless have_header('jpeglib.h')
6
+ abort "libjpeg headers were not found."
7
+ end
8
+
9
+ unless have_library('jpeg')
10
+ abort "libjpeg was not found."
11
+ end
12
+
13
+ create_makefile('oil')
data/ext/oil.c ADDED
@@ -0,0 +1,413 @@
1
+ #include <ruby.h>
2
+ #include <jpeglib.h>
3
+
4
+ #define BUF_LEN 8192
5
+
6
+ static ID id_read, id_seek;
7
+
8
+ struct thumbdata {
9
+ VALUE io;
10
+ long width;
11
+ long height;
12
+ };
13
+
14
+ static struct jpeg_error_mgr jerr;
15
+
16
+ struct jpeg_dest {
17
+ struct jpeg_destination_mgr mgr;
18
+ VALUE buffer;
19
+ };
20
+
21
+ struct jpeg_src {
22
+ struct jpeg_source_mgr mgr;
23
+ VALUE io;
24
+ VALUE buffer;
25
+ };
26
+
27
+ /* jpeglib error callbacks */
28
+
29
+ static void output_message(j_common_ptr cinfo) {}
30
+
31
+ static void
32
+ error_exit(j_common_ptr cinfo)
33
+ {
34
+ char buffer[JMSG_LENGTH_MAX];
35
+ (*cinfo->err->format_message) (cinfo, buffer);
36
+ rb_raise(rb_eRuntimeError, "jpeglib: %s", buffer);
37
+ }
38
+
39
+ /* jpeglib source callbacks */
40
+
41
+ static void term_source(j_decompress_ptr cinfo){};
42
+ static void init_source(j_decompress_ptr cinfo){};
43
+
44
+ static boolean
45
+ fill_input_buffer(j_decompress_ptr cinfo)
46
+ {
47
+ struct jpeg_src *src = (struct jpeg_src *)cinfo->src;
48
+ VALUE ret, string = src->buffer;
49
+ long len;
50
+ char *buf;
51
+
52
+ ret = rb_funcall(src->io, id_read, 2, INT2FIX(BUF_LEN), string);
53
+ len = RSTRING_LEN(string);
54
+
55
+ if (!len || NIL_P(ret)) {
56
+ rb_str_resize(string, 2);
57
+ buf = RSTRING_PTR(string);
58
+ buf[0] = 0xFF;
59
+ buf[1] = JPEG_EOI;
60
+ len = 2;
61
+ }
62
+ src->mgr.next_input_byte = (JOCTET *)RSTRING_PTR(string);
63
+ src->mgr.bytes_in_buffer = (size_t)len;
64
+
65
+ return TRUE;
66
+ }
67
+
68
+ static void
69
+ skip_input_data(j_decompress_ptr cinfo, long num_bytes)
70
+ {
71
+ struct jpeg_src *src = (struct jpeg_src *)cinfo->src;
72
+
73
+ if (num_bytes > (long)src->mgr.bytes_in_buffer) {
74
+ num_bytes -= (long)src->mgr.bytes_in_buffer;
75
+ rb_funcall(src->io, id_seek, 2, INT2FIX(num_bytes), INT2FIX(SEEK_CUR));
76
+ src->mgr.bytes_in_buffer = 0;
77
+ } else {
78
+ src->mgr.next_input_byte += (size_t)num_bytes;
79
+ src->mgr.bytes_in_buffer -= (size_t)num_bytes;
80
+ }
81
+ }
82
+
83
+ /* jpeglib destination callbacks */
84
+
85
+ static void init_destination(j_compress_ptr cinfo) {}
86
+
87
+ static boolean
88
+ empty_output_buffer(j_compress_ptr cinfo)
89
+ {
90
+ struct jpeg_dest *dest = (struct jpeg_dest *)cinfo->dest;
91
+ rb_yield(dest->buffer);
92
+ dest->mgr.next_output_byte = RSTRING_PTR(dest->buffer);
93
+ dest->mgr.free_in_buffer = RSTRING_LEN(dest->buffer);
94
+ return TRUE;
95
+ }
96
+
97
+ static void
98
+ term_destination(j_compress_ptr cinfo)
99
+ {
100
+ struct jpeg_dest *dest = (struct jpeg_dest *)cinfo->dest;
101
+ size_t len = BUF_LEN - dest->mgr.free_in_buffer;
102
+
103
+ if (len) {
104
+ rb_str_resize(dest->buffer, len);
105
+ rb_yield(dest->buffer);
106
+ }
107
+ }
108
+
109
+ /* Helper functions */
110
+
111
+ static void
112
+ advance_scanlines(struct jpeg_decompress_struct *dinfo, JSAMPROW *sl1,
113
+ JSAMPROW *sl2, int n)
114
+ {
115
+ int i;
116
+ JSAMPROW tmp;
117
+ for (i = 0; i < n; i++) {
118
+ tmp = *sl1;
119
+ *sl1 = *sl2;
120
+ *sl2 = tmp;
121
+ jpeg_read_scanlines(dinfo, sl2, 1);
122
+ }
123
+ }
124
+
125
+ static void
126
+ bilinear3(struct jpeg_compress_struct *cinfo, char *sl1, char *sl2,
127
+ char *sl_out, double width_ratio_inv, double ty)
128
+ {
129
+ double sample_x, tx, _tx, p00, p10, p01, p11;
130
+ unsigned char *c00, *c10, *c01, *c11;
131
+ size_t sample_x_i, i, j, cmp = cinfo->input_components;
132
+
133
+ for (i = 0; i < cinfo->image_width; i++) {
134
+ sample_x = i * width_ratio_inv;
135
+ sample_x_i = (int)sample_x;
136
+
137
+ tx = sample_x - sample_x_i;
138
+ _tx = 1 - tx;
139
+
140
+ p11 = tx * ty;
141
+ p01 = _tx * ty;
142
+ p10 = tx - p11;
143
+ p00 = _tx - p01;
144
+
145
+ c00 = sl1 + sample_x_i * cmp;
146
+ c10 = c00 + cmp;
147
+ c01 = sl2 + sample_x_i * cmp;
148
+ c11 = c01 + cmp;
149
+
150
+ for (j = 0; j < cmp; j++)
151
+ *sl_out++ = p00 * c00[j] + p10 * c10[j] + p01 * c01[j] +
152
+ p11 * c11[j];
153
+ }
154
+ }
155
+
156
+ static VALUE
157
+ bilinear2(VALUE *args)
158
+ {
159
+ struct jpeg_compress_struct *cinfo;
160
+ struct jpeg_decompress_struct *dinfo;
161
+ size_t i, smpy_i, smpy_last=0, sl_len;
162
+ double smpy, ty, inv_scale_y, inv_scale_x;
163
+ JSAMPROW sl1, sl2, sl_out;
164
+
165
+ cinfo = (struct jpeg_compress_struct *)args[0];
166
+ dinfo = (struct jpeg_decompress_struct *)args[1];
167
+ sl_len = *(size_t *)args[2];
168
+ sl1 = (JSAMPROW)args[3];
169
+ sl2 = (JSAMPROW)args[4];
170
+ sl_out = (JSAMPROW)args[5];
171
+
172
+ inv_scale_x = dinfo->output_width / (float)cinfo->image_width;
173
+ inv_scale_y = dinfo->output_height / (float)cinfo->image_height;
174
+ advance_scanlines(dinfo, &sl1, &sl2, 1);
175
+
176
+ for (i = 0; i < cinfo->image_height; i++) {
177
+ smpy = i * inv_scale_y;
178
+ smpy_i = (int)smpy;
179
+ ty = smpy - smpy_i;
180
+
181
+ advance_scanlines(dinfo, &sl1, &sl2, smpy_i - smpy_last);
182
+ smpy_last = smpy_i;
183
+
184
+ sl1[sl_len - 1] = sl1[sl_len - 2];
185
+ sl2[sl_len - 1] = sl2[sl_len - 2];
186
+ bilinear3(cinfo, smpy_i ? sl1 : sl2, sl2, sl_out, inv_scale_x, ty);
187
+ jpeg_write_scanlines(cinfo, &sl_out, 1);
188
+ }
189
+
190
+ return Qnil;
191
+ }
192
+
193
+ static VALUE
194
+ free_sl_bufs(VALUE *args)
195
+ {
196
+ free((char *)args[3]);
197
+ free((char *)args[4]);
198
+ free((char *)args[5]);
199
+ return Qnil;
200
+ }
201
+
202
+ static void
203
+ bilinear(struct jpeg_compress_struct *cinfo,
204
+ struct jpeg_decompress_struct *dinfo)
205
+ {
206
+ VALUE ensure_args[6];
207
+ size_t sl_len;
208
+ JSAMPROW sl1, sl2, sl_out;
209
+
210
+ sl_len = (dinfo->output_width + 1) * dinfo->output_components;
211
+ sl1 = (JSAMPROW)malloc(sl_len);
212
+ sl2 = (JSAMPROW)malloc(sl_len);
213
+ sl_out = (JSAMPROW)malloc(cinfo->image_width * cinfo->input_components);
214
+
215
+ ensure_args[0] = (VALUE)cinfo;
216
+ ensure_args[1] = (VALUE)dinfo;
217
+ ensure_args[2] = (VALUE)&sl_len;
218
+ ensure_args[3] = (VALUE)sl1;
219
+ ensure_args[4] = (VALUE)sl2;
220
+ ensure_args[5] = (VALUE)sl_out;
221
+ rb_ensure(bilinear2, (VALUE)ensure_args, free_sl_bufs, (VALUE)ensure_args);
222
+ }
223
+
224
+ static void
225
+ fix_aspect_ratio(struct jpeg_compress_struct *cinfo,
226
+ struct jpeg_decompress_struct *dinfo)
227
+ {
228
+ double x, y;
229
+
230
+ x = cinfo->image_width / (float)dinfo->output_width;
231
+ y = cinfo->image_height / (float)dinfo->output_height;
232
+
233
+ if (x < y) cinfo->image_height = dinfo->output_height * x;
234
+ else cinfo->image_width = dinfo->output_width * y;
235
+ }
236
+
237
+ static void
238
+ set_header(VALUE self, struct jpeg_compress_struct *cinfo,
239
+ struct jpeg_decompress_struct *dinfo)
240
+ {
241
+ struct thumbdata *data;
242
+ Data_Get_Struct(self, struct thumbdata, data);
243
+
244
+ cinfo->image_width = data->width;
245
+ cinfo->image_height = data->height;
246
+ cinfo->input_components = dinfo->output_components;
247
+ cinfo->in_color_space = dinfo->out_color_space;
248
+ jpeg_set_defaults(cinfo);
249
+ }
250
+
251
+ static void
252
+ pre_scale(struct jpeg_compress_struct *cinfo,
253
+ struct jpeg_decompress_struct *dinfo)
254
+ {
255
+ int inv_scale = dinfo->output_width / cinfo->image_width;
256
+
257
+ if (inv_scale >= 8) dinfo->scale_denom = 8;
258
+ else if (inv_scale >= 4) dinfo->scale_denom = 4;
259
+ else if (inv_scale >= 2) dinfo->scale_denom = 2;
260
+ jpeg_calc_output_dimensions(dinfo);
261
+ }
262
+
263
+ /* Ruby allocator, deallocator, mark, and methods */
264
+
265
+ static void
266
+ deallocate(struct thumbdata *data)
267
+ {
268
+ free(data);
269
+ }
270
+
271
+ static void
272
+ mark(struct thumbdata *data)
273
+ {
274
+ if (!NIL_P(data->io))
275
+ rb_gc_mark(data->io);
276
+ }
277
+
278
+ static VALUE
279
+ allocate(VALUE klass)
280
+ {
281
+ struct thumbdata *data;
282
+ return Data_Make_Struct(klass, struct thumbdata, mark, deallocate, data);
283
+ }
284
+
285
+ /*
286
+ * call-seq:
287
+ * JPEGThumb.new(io, width, height) -> jpeg_thumb
288
+ *
289
+ * Creates a new JPEG thumbnailer. +io_in+ must be an IO-like object that
290
+ * responds to #read(size).
291
+ *
292
+ * The resulting image will be scaled to fit in the box given by +width+ and
293
+ * +height+ while preserving the aspect ratio.
294
+ *
295
+ * If both box dimensions are larger than the source image, then the image will
296
+ * be unchanged. The thumbnailer will not enlarge images.
297
+ */
298
+
299
+ static VALUE
300
+ initialize(VALUE self, VALUE io, VALUE width, VALUE height)
301
+ {
302
+ struct thumbdata *data;
303
+
304
+ Data_Get_Struct(self, struct thumbdata, data);
305
+ data->io = io;
306
+ data->width = FIX2INT(width);
307
+ data->height = FIX2INT(height);
308
+
309
+ return self;
310
+ }
311
+
312
+ static VALUE
313
+ each2(VALUE *args)
314
+ {
315
+ VALUE self = args[0];
316
+ struct jpeg_decompress_struct *dinfo;
317
+ struct jpeg_compress_struct *cinfo;
318
+
319
+ dinfo = (struct jpeg_decompress_struct *)args[1];
320
+ cinfo = (struct jpeg_compress_struct *)args[2];
321
+
322
+ jpeg_read_header(dinfo, TRUE);
323
+ jpeg_calc_output_dimensions(dinfo);
324
+ set_header(self, cinfo, dinfo);
325
+ fix_aspect_ratio(cinfo, dinfo);
326
+ pre_scale(cinfo, dinfo);
327
+
328
+ jpeg_start_compress(cinfo, TRUE);
329
+ jpeg_start_decompress(dinfo);
330
+
331
+ bilinear(cinfo, dinfo);
332
+
333
+ jpeg_abort_decompress(dinfo);
334
+ jpeg_finish_compress(cinfo);
335
+
336
+ return Qnil;
337
+ }
338
+
339
+ static VALUE
340
+ destroy_info(VALUE *args)
341
+ {
342
+ jpeg_destroy_decompress((struct jpeg_decompress_struct *)args[1]);
343
+ jpeg_destroy_compress((struct jpeg_compress_struct *)args[2]);
344
+ return Qnil;
345
+ }
346
+
347
+ /*
348
+ * call-seq:
349
+ * resized_jpeg.each(&block) -> self
350
+ *
351
+ * Yields a series of binary strings that make up the resized JPEG image.
352
+ */
353
+
354
+ static VALUE
355
+ each(VALUE self)
356
+ {
357
+ VALUE ensure_args[3];
358
+ struct jpeg_src src;
359
+ struct jpeg_decompress_struct dinfo;
360
+ struct jpeg_dest dest;
361
+ struct jpeg_compress_struct cinfo;
362
+ struct thumbdata *data;
363
+ Data_Get_Struct(self, struct thumbdata, data);
364
+
365
+ dinfo.err = cinfo.err = &jerr;
366
+ jpeg_create_decompress(&dinfo);
367
+ jpeg_create_compress(&cinfo);
368
+
369
+ memset(&src, 0, sizeof(struct jpeg_src));
370
+ src.mgr.init_source = init_source;
371
+ src.mgr.fill_input_buffer = fill_input_buffer;
372
+ src.mgr.skip_input_data = skip_input_data;
373
+ src.mgr.resync_to_restart = jpeg_resync_to_restart;
374
+ src.mgr.term_source = term_source;
375
+ src.io = data->io;
376
+ src.buffer = rb_str_new(0, BUF_LEN);
377
+
378
+ memset(&dest, 0, sizeof(struct jpeg_dest));
379
+ dest.mgr.init_destination = init_destination;
380
+ dest.mgr.empty_output_buffer = empty_output_buffer;
381
+ dest.mgr.term_destination = term_destination;
382
+ dest.buffer = rb_str_new(0, BUF_LEN);
383
+ dest.mgr.next_output_byte = RSTRING_PTR(dest.buffer);
384
+ dest.mgr.free_in_buffer = BUF_LEN;
385
+
386
+ dinfo.src = (struct jpeg_source_mgr *)&src;
387
+ cinfo.dest = (struct jpeg_destination_mgr *)&dest;
388
+
389
+ ensure_args[0] = self;
390
+ ensure_args[1] = (VALUE)&dinfo;
391
+ ensure_args[2] = (VALUE)&cinfo;
392
+ rb_ensure(each2, (VALUE)ensure_args, destroy_info, (VALUE)ensure_args);
393
+
394
+ return self;
395
+ }
396
+
397
+ void
398
+ Init_oil()
399
+ {
400
+ VALUE mOil = rb_define_module("Oil");
401
+ VALUE cJPEG = rb_define_class_under(mOil, "JPEG", rb_cObject);
402
+ rb_define_alloc_func(cJPEG, allocate);
403
+ rb_define_method(cJPEG, "initialize", initialize, 3);
404
+ rb_define_method(cJPEG, "each", each, 0);
405
+
406
+ jpeg_std_error(&jerr);
407
+ jerr.error_exit = error_exit;
408
+ jerr.output_message = output_message;
409
+
410
+ id_read = rb_intern("read");
411
+ id_seek = rb_intern("seek");
412
+ }
413
+
data/test/test_jpeg.rb ADDED
@@ -0,0 +1,190 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+ require 'oil'
4
+ require 'stringio'
5
+
6
+ module Oil
7
+ class TestJPEG < MiniTest::Unit::TestCase
8
+ # http://www.techsupportteam.org/forum/digital-imaging-photography/1892-worlds-smallest-valid-jpeg.html
9
+ JPEG_DATA = "\
10
+ \xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\
11
+ \x00\xFF\xDB\x00\x43\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
12
+ \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
13
+ \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
14
+ \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC2\x00\x0B\x08\x00\
15
+ \x01\x00\x01\x01\x01\x11\x00\xFF\xC4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\
16
+ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00\x01\x3F\
17
+ \x10\xFF\xD9"
18
+
19
+ def test_valid_jpeg
20
+ validate_jpeg resize_string(JPEG_DATA)
21
+ end
22
+
23
+ def test_jpeg_missing_eof
24
+ validate_jpeg resize_string(JPEG_DATA[0..-2])
25
+ end
26
+
27
+ def test_jpeg_bogus_header_marker
28
+ str = JPEG_DATA.dup
29
+ str[3] = "\x10"
30
+ assert_raises(RuntimeError) { resize_string(str) }
31
+ end
32
+
33
+ def test_jpeg_bogus_body_marker
34
+ str = JPEG_DATA.dup
35
+ str[-1] = "\x10"
36
+ assert_raises(RuntimeError) { resize_string(str) }
37
+ end
38
+
39
+ def test_calls_each_during_yield
40
+ io = StringIO.new(JPEG_DATA)
41
+ j = JPEG.new(io, 600, 600)
42
+ assert_raises(RuntimeError) do
43
+ j.each{ |d| j.each { |e| j.each { |f| } } }
44
+ end
45
+ end
46
+
47
+ # Test io source handler
48
+
49
+ def test_io_returns_too_much_data
50
+ proc = Proc.new do |io, size, buf|
51
+ buf.slice!(0,0)
52
+ buf << (io.read(size)[0..-2] * 2)
53
+ end
54
+ assert_raises(RuntimeError) { custom_io proc, JPEG_DATA }
55
+ #custom_io proc, JPEG_DATA
56
+ end
57
+
58
+ def test_io_does_nothing
59
+ assert_raises(RuntimeError) { custom_io(nil) }
60
+ end
61
+
62
+ def test_io_raises_exception_immediately
63
+ proc = Proc.new{ raise CustomError }
64
+ assert_raises(CustomError) { custom_io proc }
65
+ end
66
+
67
+ def test_io_throws_immediately
68
+ proc = Proc.new{ throw(:foo) }
69
+ catch(:foo){ custom_io proc }
70
+ end
71
+
72
+ def test_io_raises_exception_in_body
73
+ flag = false
74
+ proc = Proc.new do |parent, size, buf|
75
+ raise CustomError if flag
76
+ flag = true
77
+ parent.read(size, buf)
78
+ end
79
+ assert_raises(CustomError) { custom_io proc, big_jpeg }
80
+ end
81
+
82
+ def test_io_throws_in_body
83
+ flag = false
84
+ proc = Proc.new do |parent, size, buf|
85
+ throw :foo if flag
86
+ flag = true
87
+ parent.read(size, buf)
88
+ end
89
+ catch(:foo){ custom_io proc, big_jpeg }
90
+ end
91
+
92
+ def test_io_shrinks_buffer
93
+ proc = Proc.new do |parent, size, buf|
94
+ parent.read(size, buf)
95
+ buf.slice!(0, 10)
96
+ end
97
+ assert_raises(RuntimeError) { custom_io(proc, big_jpeg) }
98
+ end
99
+
100
+ def test_io_enlarges_buffer
101
+ proc = Proc.new do |parent, size, buf|
102
+ res = parent.read(size, buf)
103
+ buf << res
104
+ end
105
+ validate_jpeg custom_io(proc, big_jpeg) # how is this okay?
106
+ end
107
+
108
+ def test_io_seek
109
+ # Craft a JPEG with header content that will be skipped.
110
+ # Make sure an actual seek happens, and it doesn't just skip buffer.
111
+ end
112
+
113
+ # Test yielding
114
+
115
+ def test_raise_in_each
116
+ assert_raises(CustomError) do
117
+ io = StringIO.new(JPEG_DATA)
118
+ JPEG.new(io, 200, 200).each { raise CustomError }
119
+ end
120
+ end
121
+
122
+ def test_throw_in_each
123
+ catch(:foo) do
124
+ io = StringIO.new(JPEG_DATA)
125
+ JPEG.new(io, 200, 200).each { throw :foo }
126
+ end
127
+ end
128
+
129
+ def test_each_shrinks_buffer
130
+ io = StringIO.new(JPEG_DATA)
131
+ io_out = binary_stringio
132
+ JPEG.new(io, 200, 200).each { |d| io_out << d; d.slice!(0, 4) }
133
+ validate_jpeg(io_out.string)
134
+ end
135
+
136
+ def test_each_enlarges_buffer
137
+ io = StringIO.new(JPEG_DATA)
138
+ io_out = binary_stringio
139
+ JPEG.new(io, 200, 200).each { |d| io_out << d; d << "foobar" }
140
+ validate_jpeg(io_out.string)
141
+ end
142
+
143
+ private
144
+
145
+ def io(io, width=nil, height=nil)
146
+ width ||= 100
147
+ height ||= 200
148
+ out = binary_stringio
149
+ JPEG.new(io, width, height).each{ |d| out << d }
150
+ return out.string
151
+ end
152
+
153
+ def custom_io(*args)
154
+ io CustomIO.new(*args)
155
+ end
156
+
157
+ def resize_string(str, width=nil, height=nil)
158
+ io(StringIO.new(str), width, height)
159
+ end
160
+
161
+ def validate_jpeg(data)
162
+ assert_equal "\xFF\xD8", data[0,2]
163
+ assert_equal "\xFF\xD9", data[-2, 2]
164
+ end
165
+
166
+ def big_jpeg
167
+ resize_string(JPEG_DATA, 1000, 1000)
168
+ end
169
+
170
+ def binary_stringio
171
+ io = StringIO.new
172
+ io.set_encoding 'ASCII-8BIT' if RUBY_VERSION >= '1.9'
173
+ io
174
+ end
175
+ end
176
+
177
+ class CustomError < RuntimeError; end
178
+
179
+ class CustomIO
180
+ def initialize(proc, *args)
181
+ @parent = StringIO.new(*args)
182
+ @proc = proc
183
+ end
184
+
185
+ def read(size, buf)
186
+ @proc.call(@parent, size, buf) if @proc
187
+ end
188
+ end
189
+ end
190
+
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oil
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Timothy Elliott
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: tle@holymonkey.com
16
+ executables: []
17
+ extensions:
18
+ - ext/extconf.rb
19
+ extra_rdoc_files:
20
+ - README.rdoc
21
+ files:
22
+ - Rakefile
23
+ - README.rdoc
24
+ - ext/oil.c
25
+ - ext/extconf.rb
26
+ - test/test_jpeg.rb
27
+ homepage: http://github.com/ender672/oil
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - ext
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 1.8.17
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: Resize JPEG images.
51
+ test_files:
52
+ - test/test_jpeg.rb