oil 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/README.rdoc +36 -11
  2. data/Rakefile +0 -12
  3. data/ext/extconf.rb +18 -0
  4. data/ext/oil.c +344 -162
  5. data/test/test_jpeg.rb +19 -5
  6. data/test/test_png.rb +197 -0
  7. metadata +5 -3
@@ -4,34 +4,59 @@ http://github.com/ender672/oil
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Resize JPEG images.
7
+ Oil resizes JPEG and PNG images.
8
+
9
+ == INSTALLATION:
10
+
11
+ $ gem install oil
8
12
 
9
13
  == SYNOPSIS:
10
14
 
11
- # Resize a JPEG image
12
15
  require 'oil'
13
-
14
16
  io = File.open('image.jpg', 'rb')
17
+
18
+ # prepare to resize JPEG to fit in a 200x300 box, preserving aspect ratio
15
19
  jpeg = Oil::JPEG.new(io, 200, 300)
16
- jpeg.each do |data|
17
- # yields successive parts of the jpeg file
18
- end
20
+
21
+ # yield successive parts of the resized and compressed JPEG file
22
+ jpeg.each { |data| }
19
23
 
20
24
  == REQUIREMENTS:
21
25
 
22
- * IJG JPEG Library Version 6b or later.
26
+ * IJG JPEG Library or libjpeg-turbo.
27
+ * libpng
23
28
 
24
29
  Installing libjpeg headers (OSX):
25
30
 
26
31
  $ brew install libjpeg # requires homebrew (http://mxcl.github.com/homebrew/)
27
32
 
28
- Installing libjpeg headers (Debian/Ubuntu):
33
+ Installing libjpeg and libpng headers (Debian/Ubuntu):
29
34
 
30
- $ sudo apt-get install libjpeg-dev
35
+ $ sudo apt-get install libjpeg-dev libpng-dev
31
36
 
32
- == INSTALLATION:
37
+ == COMPILING & TESTING:
33
38
 
34
- $ gem install oil
39
+ Compile & run unit tests. Should show no warnings and no failing tests:
40
+
41
+ $ rake compile
42
+ $ rake test
43
+
44
+ Valgrind should not complain (ruby-1.9.3p125, compiled with -O3):
45
+
46
+ $ valgrind /path/to/ruby -Iext test/test_jpeg.rb
47
+ $ valgrind --suppressions=test/valgrind_suppressions.txt /path/to/ruby -Iext test/test_png.rb
48
+
49
+ Tests should not leak memory:
50
+
51
+ $ ruby -Iext:test -e "require 'test_jpeg.rb'; require 'test_png.rb'; loop{ MiniTest::Unit.new.run }"
52
+
53
+ Changes to the interpolator should be analyzed using ResampleScope:
54
+
55
+ https://github.com/jsummers/resamplescope
56
+
57
+ == TODO:
58
+
59
+ * JRuby
35
60
 
36
61
  == LICENSE:
37
62
 
data/Rakefile CHANGED
@@ -13,13 +13,6 @@ file 'ext/oil.so' => FileList.new('ext/Makefile', 'ext/oil.c') do
13
13
  end
14
14
  end
15
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
16
  Rake::TestTask.new do |t|
24
17
  t.libs = ['ext']
25
18
  t.test_files = FileList['test/test*.rb']
@@ -28,11 +21,6 @@ end
28
21
  CLEAN.add('ext/*{.o,.so,.log}', 'ext/Makefile')
29
22
  CLOBBER.add('*.gem')
30
23
 
31
- desc 'Build the gem'
32
- task :gem do
33
- system "gem build oil.gemspec"
34
- end
35
-
36
24
  desc 'Compile the extension'
37
25
  task :compile => "ext/oil.so"
38
26
 
@@ -1,6 +1,16 @@
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
+
3
12
  dir_config('jpeg')
13
+ dir_config('png', png_idefault, png_ldefault)
4
14
 
5
15
  unless have_header('jpeglib.h')
6
16
  abort "libjpeg headers were not found."
@@ -10,4 +20,12 @@ unless have_library('jpeg')
10
20
  abort "libjpeg was not found."
11
21
  end
12
22
 
23
+ unless have_header('png.h')
24
+ abort "libpng headers were not found."
25
+ end
26
+
27
+ unless have_library('png')
28
+ abort "libpng was not found."
29
+ end
30
+
13
31
  create_makefile('oil')
data/ext/oil.c CHANGED
@@ -1,5 +1,6 @@
1
1
  #include <ruby.h>
2
2
  #include <jpeglib.h>
3
+ #include <png.h>
3
4
 
4
5
  #define BUF_LEN 8192
5
6
 
@@ -24,6 +25,22 @@ struct jpeg_src {
24
25
  VALUE buffer;
25
26
  };
26
27
 
28
+ struct png_src {
29
+ VALUE io;
30
+ VALUE buffer;
31
+ };
32
+
33
+ struct interpolation {
34
+ long sw;
35
+ long sh;
36
+ long dw;
37
+ long dh;
38
+ int cmp;
39
+ void (*read)(char *, void *);
40
+ void (*write)(char *, void *);
41
+ void *data;
42
+ };
43
+
27
44
  /* jpeglib error callbacks */
28
45
 
29
46
  static void output_message(j_common_ptr cinfo) {}
@@ -48,7 +65,7 @@ fill_input_buffer(j_decompress_ptr cinfo)
48
65
  VALUE ret, string = src->buffer;
49
66
  long len;
50
67
  char *buf;
51
-
68
+
52
69
  ret = rb_funcall(src->io, id_read, 2, INT2FIX(BUF_LEN), string);
53
70
  len = RSTRING_LEN(string);
54
71
 
@@ -106,124 +123,173 @@ term_destination(j_compress_ptr cinfo)
106
123
  }
107
124
  }
108
125
 
109
- /* Helper functions */
126
+ /* libpng error callbacks */
127
+
128
+ static void png_warning_fn(png_structp png_ptr, png_const_charp message) {}
110
129
 
111
130
  static void
112
- advance_scanlines(struct jpeg_decompress_struct *dinfo, JSAMPROW *sl1,
113
- JSAMPROW *sl2, int n)
131
+ png_error_fn(png_structp png_ptr, png_const_charp message)
132
+ {
133
+ rb_raise(rb_eRuntimeError, "pnglib: %s", message);
134
+ }
135
+
136
+ /* libpng io callbacks */
137
+
138
+ void png_flush_data(png_structp png_ptr){}
139
+
140
+ void
141
+ png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
142
+ {
143
+ if (!png_ptr) return;
144
+ rb_yield(rb_str_new(data, length));
145
+ }
146
+
147
+ void
148
+ png_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
114
149
  {
115
- int i;
116
- JSAMPROW tmp;
117
- for (i = 0; i < n; i++) {
150
+ VALUE string;
151
+ png_size_t rlen;
152
+ struct png_src *io_ptr;
153
+
154
+ if (!png_ptr) return;
155
+ io_ptr = (struct png_src *)png_get_io_ptr(png_ptr);
156
+ string = io_ptr->buffer;
157
+ rb_funcall(io_ptr->io, id_read, 2, INT2FIX(length), string);
158
+ rlen = RSTRING_LEN(string);
159
+ if (rlen > length) rb_raise(rb_eRuntimeError, "IO return buffer is too big.");
160
+ memcpy(data, RSTRING_PTR(string), RSTRING_LEN(string));
161
+ }
162
+
163
+ /* png read/write callbacks */
164
+
165
+ static void
166
+ png_read(char *sl, void *data)
167
+ {
168
+ void **data_ary = (void **)data;
169
+ png_read_row((png_structp)data_ary[2], (png_bytep)sl, (png_bytep)NULL);
170
+ }
171
+
172
+ static void
173
+ png_write(char *sl, void *data)
174
+ {
175
+ void **data_ary = (void **)data;
176
+ png_write_row((png_structp)*data_ary, (png_bytep)sl);
177
+ }
178
+
179
+ /* Bilinear Interpolation */
180
+
181
+ static void
182
+ bilinear_get_scanlines(struct interpolation *bi, char **sl1, char **sl2,
183
+ size_t *next, double ysmp)
184
+ {
185
+ int i, j, n, len=bi->cmp * bi->sw - 1;
186
+ int line=ysmp;
187
+ char *tmp;
188
+
189
+ n = line + 1 - *next;
190
+ if (line < bi->sh - 1) n++;
191
+ *next += n;
192
+
193
+ for (i=0; i<n; i++) {
118
194
  tmp = *sl1;
119
195
  *sl1 = *sl2;
120
196
  *sl2 = tmp;
121
- jpeg_read_scanlines(dinfo, sl2, 1);
197
+ bi->read(*sl2, bi->data);
198
+
199
+ for (j=0; j<bi->cmp; j++)
200
+ sl2[0][len + bi->cmp - j] = sl2[0][len - j];
122
201
  }
202
+
203
+ if (line >= bi->sh - 1) *sl1 = *sl2;
123
204
  }
124
205
 
125
206
  static void
126
- bilinear3(struct jpeg_compress_struct *cinfo, char *sl1, char *sl2,
127
- char *sl_out, double width_ratio_inv, double ty)
207
+ bilinear3(char *sl1, char *sl2, char *sl_out, size_t sw, size_t dw, int cmp,
208
+ double ysmp)
128
209
  {
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;
210
+ double xsmp, tx, _tx, p1, p2, p3, p4;
211
+ unsigned char *c1, *c2, *c3, *c4;
212
+ size_t xsmp_i, i, j;
213
+ double xscale_inv, xscale_ctr, ty;
132
214
 
133
- for (i = 0; i < cinfo->image_width; i++) {
134
- sample_x = i * width_ratio_inv;
135
- sample_x_i = (int)sample_x;
215
+ xscale_inv = sw / (float)dw;
216
+ xscale_ctr = (xscale_inv - 1) / 2;
217
+ ty = ysmp - (int)ysmp;
136
218
 
137
- tx = sample_x - sample_x_i;
219
+ for (i = 0; i < dw; i++) {
220
+ xsmp = i * xscale_inv + xscale_ctr;
221
+ xsmp_i = (int)xsmp;
222
+
223
+ tx = xsmp - xsmp_i;
138
224
  _tx = 1 - tx;
139
225
 
140
- p11 = tx * ty;
141
- p01 = _tx * ty;
142
- p10 = tx - p11;
143
- p00 = _tx - p01;
226
+ p4 = tx * ty;
227
+ p3 = _tx * ty;
228
+ p2 = tx - p4;
229
+ p1 = _tx - p3;
144
230
 
145
- c00 = sl1 + sample_x_i * cmp;
146
- c10 = c00 + cmp;
147
- c01 = sl2 + sample_x_i * cmp;
148
- c11 = c01 + cmp;
231
+ c1 = sl1 + xsmp_i * cmp;
232
+ c2 = c1 + cmp;
233
+ c3 = sl2 + xsmp_i * cmp;
234
+ c4 = c3 + cmp;
149
235
 
150
236
  for (j = 0; j < cmp; j++)
151
- *sl_out++ = p00 * c00[j] + p10 * c10[j] + p01 * c01[j] +
152
- p11 * c11[j];
237
+ *sl_out++ = p1 * c1[j] + p2 * c2[j] + p3 * c3[j] + p4 * c4[j];
153
238
  }
154
239
  }
155
240
 
156
241
  static VALUE
157
- bilinear2(VALUE *args)
242
+ bilinear2(VALUE _args)
158
243
  {
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
- }
244
+ VALUE *args = (VALUE *)_args;
245
+ struct interpolation *bi=(struct interpolation *)args[0];
246
+ char *sl1=(char *)args[1], *sl2=(char *)args[2], *sl_out=(char *)args[3];
247
+ size_t i, line=0;
248
+ double ysmp, yscale_inv, yscale_ctr;
189
249
 
190
- return Qnil;
191
- }
250
+ yscale_inv = bi->sh / (float)bi->dh;
251
+ yscale_ctr = (yscale_inv - 1) / 2;
252
+
253
+ for (i = 0; i<bi->dh; i++) {
254
+ ysmp = i * yscale_inv + yscale_ctr;
255
+
256
+ bilinear_get_scanlines(bi, &sl1, &sl2, &line, ysmp);
257
+ bilinear3(sl1, sl2, sl_out, bi->sw, bi->dw, bi->cmp, ysmp);
258
+ bi->write(sl_out, bi->data);
259
+ }
192
260
 
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
261
  return Qnil;
200
262
  }
201
263
 
202
264
  static void
203
- bilinear(struct jpeg_compress_struct *cinfo,
204
- struct jpeg_decompress_struct *dinfo)
265
+ bilinear(struct interpolation *bi)
205
266
  {
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);
267
+ VALUE args[4];
268
+ int state, in_len;
269
+ char *sl1, *sl2, *sl_out;
270
+
271
+ in_len = (bi->sw + 1) * bi->cmp;
272
+ sl1 = malloc(in_len);
273
+ sl2 = malloc(in_len);
274
+ sl_out = malloc(bi->dw * bi->cmp);
275
+
276
+ args[0] = (VALUE)bi;
277
+ args[1] = (VALUE)sl1;
278
+ args[2] = (VALUE)sl2;
279
+ args[3] = (VALUE)sl_out;
280
+ rb_protect(bilinear2, (VALUE)args, &state);
281
+
282
+ free(sl1);
283
+ free(sl2);
284
+ free(sl_out);
285
+
286
+ if (state) rb_jump_tag(state);
222
287
  }
223
288
 
289
+ /* JPEG helper functions */
290
+
224
291
  static void
225
- fix_aspect_ratio(struct jpeg_compress_struct *cinfo,
226
- struct jpeg_decompress_struct *dinfo)
292
+ jpeg_fix_aspect_ratio(j_compress_ptr cinfo, j_decompress_ptr dinfo)
227
293
  {
228
294
  double x, y;
229
295
 
@@ -235,22 +301,15 @@ fix_aspect_ratio(struct jpeg_compress_struct *cinfo,
235
301
  }
236
302
 
237
303
  static void
238
- set_header(VALUE self, struct jpeg_compress_struct *cinfo,
239
- struct jpeg_decompress_struct *dinfo)
304
+ jpeg_set_output_header(j_compress_ptr cinfo, j_decompress_ptr dinfo)
240
305
  {
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
306
  cinfo->input_components = dinfo->output_components;
247
307
  cinfo->in_color_space = dinfo->out_color_space;
248
308
  jpeg_set_defaults(cinfo);
249
309
  }
250
310
 
251
311
  static void
252
- pre_scale(struct jpeg_compress_struct *cinfo,
253
- struct jpeg_decompress_struct *dinfo)
312
+ jpeg_pre_scale(j_compress_ptr cinfo, j_decompress_ptr dinfo)
254
313
  {
255
314
  int inv_scale = dinfo->output_width / cinfo->image_width;
256
315
 
@@ -260,6 +319,86 @@ pre_scale(struct jpeg_compress_struct *cinfo,
260
319
  jpeg_calc_output_dimensions(dinfo);
261
320
  }
262
321
 
322
+ static void
323
+ jpeg_read(char *sl, void *data)
324
+ {
325
+ void **data_ary = (void **)data;
326
+ jpeg_read_scanlines((j_decompress_ptr)*data_ary, (JSAMPROW *)&sl, 1);
327
+ }
328
+
329
+ static void
330
+ jpeg_write(char *sl, void *data)
331
+ {
332
+ void **data_ary = (void **)data;
333
+ jpeg_write_scanlines((j_compress_ptr)data_ary[1], (JSAMPROW *)&sl, 1);
334
+ }
335
+
336
+ static VALUE
337
+ jpeg_each3(VALUE _args)
338
+ {
339
+ VALUE *args = (VALUE *)_args;
340
+ j_decompress_ptr dinfo = (j_decompress_ptr)args[0];
341
+ j_compress_ptr cinfo = (j_compress_ptr)args[1];
342
+ struct interpolation intrp;
343
+ void *data[2];
344
+
345
+ jpeg_read_header(dinfo, TRUE);
346
+ jpeg_calc_output_dimensions(dinfo);
347
+ jpeg_set_output_header(cinfo, dinfo);
348
+ jpeg_fix_aspect_ratio(cinfo, dinfo);
349
+ jpeg_pre_scale(cinfo, dinfo);
350
+
351
+ jpeg_start_compress(cinfo, TRUE);
352
+ jpeg_start_decompress(dinfo);
353
+
354
+ intrp.sw = dinfo->output_width;
355
+ intrp.sh = dinfo->output_height;
356
+ intrp.dw = cinfo->image_width;
357
+ intrp.dh = cinfo->image_height;
358
+ intrp.cmp = dinfo->output_components;
359
+ intrp.read = jpeg_read;
360
+ intrp.write = jpeg_write;
361
+ data[0] = dinfo;
362
+ data[1] = cinfo;
363
+ intrp.data = (void *)data;
364
+
365
+ bilinear(&intrp);
366
+ //nearest(&intrp);
367
+
368
+ jpeg_abort_decompress(dinfo);
369
+ jpeg_finish_compress(cinfo);
370
+
371
+ return Qnil;
372
+ }
373
+
374
+ static void
375
+ jpeg_each2(struct thumbdata *data, struct jpeg_src *src, struct jpeg_dest *dest)
376
+ {
377
+ int state;
378
+ VALUE args[2];
379
+ struct jpeg_decompress_struct dinfo;
380
+ struct jpeg_compress_struct cinfo;
381
+
382
+ dinfo.err = cinfo.err = &jerr;
383
+
384
+ jpeg_create_compress(&cinfo);
385
+ cinfo.dest = &dest->mgr;
386
+ cinfo.image_width = data->width;
387
+ cinfo.image_height = data->height;
388
+
389
+ jpeg_create_decompress(&dinfo);
390
+ dinfo.src = &src->mgr;
391
+
392
+ args[0] = (VALUE)&dinfo;
393
+ args[1] = (VALUE)&cinfo;
394
+ rb_protect(jpeg_each3, (VALUE)args, &state);
395
+
396
+ jpeg_destroy_decompress(&dinfo);
397
+ jpeg_destroy_compress(&cinfo);
398
+
399
+ if (state) rb_jump_tag(state);
400
+ }
401
+
263
402
  /* Ruby allocator, deallocator, mark, and methods */
264
403
 
265
404
  static void
@@ -284,113 +423,151 @@ allocate(VALUE klass)
284
423
 
285
424
  /*
286
425
  * call-seq:
287
- * JPEGThumb.new(io, width, height) -> jpeg_thumb
426
+ * Class.new(io, width, height) -> obj
288
427
  *
289
- * Creates a new JPEG thumbnailer. +io_in+ must be an IO-like object that
290
- * responds to #read(size).
428
+ * Creates a new resizer. +io_in+ must be an IO-like object that responds to
429
+ * #read(size, buffer) and #seek(size).
291
430
  *
292
431
  * 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.
432
+ * +height+ while preserving the original aspect ratio.
297
433
  */
298
434
 
299
435
  static VALUE
300
- initialize(VALUE self, VALUE io, VALUE width, VALUE height)
436
+ initialize(VALUE self, VALUE io, VALUE rb_width, VALUE rb_height)
301
437
  {
438
+ int width=FIX2INT(rb_width), height=FIX2INT(rb_height);
302
439
  struct thumbdata *data;
440
+
441
+ if (width<1 || height<1) rb_raise(rb_eArgError, "Dimensions must be > 0");
303
442
 
304
443
  Data_Get_Struct(self, struct thumbdata, data);
305
444
  data->io = io;
306
- data->width = FIX2INT(width);
307
- data->height = FIX2INT(height);
445
+ data->width = width;
446
+ data->height = height;
308
447
 
309
448
  return self;
310
449
  }
311
450
 
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
451
  /*
348
452
  * call-seq:
349
- * resized_jpeg.each(&block) -> self
453
+ * jpeg.each(&block) -> self
350
454
  *
351
455
  * Yields a series of binary strings that make up the resized JPEG image.
352
456
  */
353
457
 
354
458
  static VALUE
355
- each(VALUE self)
459
+ jpeg_each(VALUE self)
356
460
  {
357
- VALUE ensure_args[3];
358
461
  struct jpeg_src src;
359
- struct jpeg_decompress_struct dinfo;
360
462
  struct jpeg_dest dest;
361
- struct jpeg_compress_struct cinfo;
362
463
  struct thumbdata *data;
363
464
  Data_Get_Struct(self, struct thumbdata, data);
364
465
 
365
- dinfo.err = cinfo.err = &jerr;
366
- jpeg_create_decompress(&dinfo);
367
- jpeg_create_compress(&cinfo);
368
-
369
466
  memset(&src, 0, sizeof(struct jpeg_src));
467
+ src.io = data->io;
468
+ src.buffer = rb_str_new(0, 0);
370
469
  src.mgr.init_source = init_source;
371
470
  src.mgr.fill_input_buffer = fill_input_buffer;
372
471
  src.mgr.skip_input_data = skip_input_data;
373
472
  src.mgr.resync_to_restart = jpeg_resync_to_restart;
374
473
  src.mgr.term_source = term_source;
375
- src.io = data->io;
376
- src.buffer = rb_str_new(0, BUF_LEN);
377
474
 
378
475
  memset(&dest, 0, sizeof(struct jpeg_dest));
476
+ dest.buffer = rb_str_new(0, BUF_LEN);
477
+ dest.mgr.next_output_byte = RSTRING_PTR(dest.buffer);
478
+ dest.mgr.free_in_buffer = BUF_LEN;
379
479
  dest.mgr.init_destination = init_destination;
380
480
  dest.mgr.empty_output_buffer = empty_output_buffer;
381
481
  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
482
 
386
- dinfo.src = (struct jpeg_source_mgr *)&src;
387
- cinfo.dest = (struct jpeg_destination_mgr *)&dest;
483
+ jpeg_each2(data, &src, &dest);
484
+
485
+ return self;
486
+ }
487
+
488
+ static VALUE
489
+ png_each2(VALUE _args)
490
+ {
491
+ VALUE *args = (VALUE *)_args;
492
+ png_structp write_ptr=(png_structp)args[0];
493
+ png_infop write_i_ptr=(png_infop)args[1];
494
+ png_structp read_ptr=(png_structp)args[2];
495
+ png_infop read_i_ptr=(png_infop)args[3];
496
+ struct thumbdata *thumb=(struct thumbdata *)args[4];
497
+ void *data[4];
498
+ struct interpolation intrp;
499
+
500
+ png_read_info(read_ptr, read_i_ptr);
501
+ png_set_IHDR(write_ptr, write_i_ptr, thumb->width, thumb->height, 8,
502
+ PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
503
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
504
+ png_write_info(write_ptr, write_i_ptr);
505
+
506
+ intrp.sw = png_get_image_width(read_ptr, read_i_ptr);
507
+ intrp.sh = png_get_image_height(read_ptr, read_i_ptr);
508
+ intrp.dw = thumb->width;
509
+ intrp.dh = thumb->height;
510
+ intrp.cmp = 3;
511
+ intrp.read = png_read;
512
+ intrp.write = png_write;
513
+ data[0] = write_ptr;
514
+ data[1] = write_i_ptr;
515
+ data[2] = read_ptr;
516
+ data[3] = read_i_ptr;
517
+ intrp.data = (void *)data;
518
+
519
+ bilinear(&intrp);
520
+
521
+ png_read_end(read_ptr, (png_infop)NULL);
522
+ png_write_end(write_ptr, write_i_ptr);
523
+
524
+ return Qnil;
525
+ }
388
526
 
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);
527
+ /*
528
+ * call-seq:
529
+ * png.each(&block) -> self
530
+ *
531
+ * Yields a series of binary strings that make up the resized PNG image.
532
+ */
393
533
 
534
+ static VALUE
535
+ png_each(VALUE self)
536
+ {
537
+ int state;
538
+ VALUE args[5];
539
+ png_structp read_ptr, write_ptr;
540
+ png_infop read_i_ptr, write_i_ptr;
541
+ struct png_src src;
542
+ struct thumbdata *thumb;
543
+ Data_Get_Struct(self, struct thumbdata, thumb);
544
+
545
+ write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
546
+ (png_error_ptr)png_error_fn,
547
+ (png_error_ptr)png_warning_fn);
548
+ write_i_ptr = png_create_info_struct(write_ptr);
549
+ png_set_write_fn(write_ptr, NULL, png_write_data, png_flush_data);
550
+
551
+ src.io = thumb->io;
552
+ src.buffer = rb_str_new(0, 0);
553
+
554
+ read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
555
+ (png_error_ptr)png_error_fn,
556
+ (png_error_ptr)png_warning_fn);
557
+ read_i_ptr = png_create_info_struct(read_ptr);
558
+ png_set_read_fn(read_ptr, (void *)&src, png_read_data);
559
+
560
+ args[0] = (VALUE)write_ptr;
561
+ args[1] = (VALUE)write_i_ptr;
562
+ args[2] = (VALUE)read_ptr;
563
+ args[3] = (VALUE)read_i_ptr;
564
+ args[4] = (VALUE)thumb;
565
+ rb_protect(png_each2, (VALUE)args, &state);
566
+
567
+ png_destroy_read_struct(&read_ptr, &read_i_ptr, (png_info **)NULL);
568
+ png_destroy_write_struct(&write_ptr, &write_i_ptr);
569
+
570
+ if (state) rb_jump_tag(state);
394
571
  return self;
395
572
  }
396
573
 
@@ -398,10 +575,16 @@ void
398
575
  Init_oil()
399
576
  {
400
577
  VALUE mOil = rb_define_module("Oil");
578
+
401
579
  VALUE cJPEG = rb_define_class_under(mOil, "JPEG", rb_cObject);
402
580
  rb_define_alloc_func(cJPEG, allocate);
403
581
  rb_define_method(cJPEG, "initialize", initialize, 3);
404
- rb_define_method(cJPEG, "each", each, 0);
582
+ rb_define_method(cJPEG, "each", jpeg_each, 0);
583
+
584
+ VALUE cPNG = rb_define_class_under(mOil, "PNG", rb_cObject);
585
+ rb_define_alloc_func(cPNG, allocate);
586
+ rb_define_method(cPNG, "initialize", initialize, 3);
587
+ rb_define_method(cPNG, "each", png_each, 0);
405
588
 
406
589
  jpeg_std_error(&jerr);
407
590
  jerr.error_exit = error_exit;
@@ -410,4 +593,3 @@ Init_oil()
410
593
  id_read = rb_intern("read");
411
594
  id_seek = rb_intern("seek");
412
595
  }
413
-
@@ -16,21 +16,21 @@ module Oil
16
16
  \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00\x01\x3F\
17
17
  \x10\xFF\xD9"
18
18
 
19
- def test_valid_jpeg
19
+ def test_valid
20
20
  validate_jpeg resize_string(JPEG_DATA)
21
21
  end
22
22
 
23
- def test_jpeg_missing_eof
23
+ def test_missing_eof
24
24
  validate_jpeg resize_string(JPEG_DATA[0..-2])
25
25
  end
26
26
 
27
- def test_jpeg_bogus_header_marker
27
+ def test_bogus_header_marker
28
28
  str = JPEG_DATA.dup
29
29
  str[3] = "\x10"
30
30
  assert_raises(RuntimeError) { resize_string(str) }
31
31
  end
32
32
 
33
- def test_jpeg_bogus_body_marker
33
+ def test_bogus_body_marker
34
34
  str = JPEG_DATA.dup
35
35
  str[-1] = "\x10"
36
36
  assert_raises(RuntimeError) { resize_string(str) }
@@ -44,6 +44,21 @@ module Oil
44
44
  end
45
45
  end
46
46
 
47
+ def test_alloc_each
48
+ io = JPEG.allocate
49
+ assert_raises(NoMethodError){ io.each{ |f| } }
50
+ end
51
+
52
+ # Test dimensions
53
+
54
+ def test_zero_dim
55
+ assert_raises(ArgumentError){ resize_string(JPEG_DATA, 0, 0) }
56
+ end
57
+
58
+ def test_neg_dim
59
+ assert_raises(ArgumentError){ resize_string(JPEG_DATA, -1231, -123) }
60
+ end
61
+
47
62
  # Test io source handler
48
63
 
49
64
  def test_io_returns_too_much_data
@@ -52,7 +67,6 @@ module Oil
52
67
  buf << (io.read(size)[0..-2] * 2)
53
68
  end
54
69
  assert_raises(RuntimeError) { custom_io proc, JPEG_DATA }
55
- #custom_io proc, JPEG_DATA
56
70
  end
57
71
 
58
72
  def test_io_does_nothing
@@ -0,0 +1,197 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+ require 'oil'
4
+ require 'stringio'
5
+
6
+ module Oil
7
+ class TestPNG < MiniTest::Unit::TestCase
8
+ # http://garethrees.org/2007/11/14/pngcrush/
9
+ PNG_DATA = "\
10
+ \x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\
11
+ \x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x37\x6E\xF9\x24\x00\x00\x00\x10\x49\
12
+ \x44\x41\x54\x78\x9C\x62\x60\x01\x00\x00\x00\xFF\xFF\x03\x00\x00\x06\x00\x05\
13
+ \x57\xBF\xAB\xD4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"
14
+
15
+ def test_valid
16
+ validate_png resize_string(PNG_DATA)
17
+ end
18
+
19
+ def test_bogus_header_chunk
20
+ str = PNG_DATA.dup
21
+ str[15] = "\x10"
22
+ assert_raises(RuntimeError) { resize_string(str) }
23
+ end
24
+
25
+ def test_bogus_body_chunk
26
+ str = PNG_DATA.dup
27
+ str[37] = "\x10"
28
+ assert_raises(RuntimeError) { resize_string(str) }
29
+ end
30
+
31
+ def test_bogus_end_chunk
32
+ str = PNG_DATA.dup
33
+ str[-6] = "\x10"
34
+ assert_raises(RuntimeError) { resize_string(str) }
35
+ end
36
+
37
+ def test_calls_each_during_yield
38
+ io = StringIO.new(PNG_DATA)
39
+ j = PNG.new(io, 600, 600)
40
+ assert_raises(RuntimeError) do
41
+ j.each{ |d| j.each { |e| j.each { |f| } } }
42
+ end
43
+ end
44
+
45
+ def test_alloc_each
46
+ io = PNG.allocate
47
+ assert_raises(NoMethodError){ io.each{ |f| } }
48
+ end
49
+
50
+ # Test dimensions
51
+
52
+ def test_zero_dim
53
+ assert_raises(ArgumentError){ resize_string(PNG_DATA, 0, 0) }
54
+ end
55
+
56
+ def test_neg_dim
57
+ assert_raises(ArgumentError){ resize_string(PNG_DATA, -1231, -123) }
58
+ end
59
+
60
+ # Test io source handler
61
+
62
+ def test_io_returns_too_much_data
63
+ proc = Proc.new do |io, size, buf|
64
+ buf.slice!(0,0)
65
+ buf << (io.read(size)[0..-2] * 2)
66
+ end
67
+ assert_raises(RuntimeError) { custom_io proc, PNG_DATA }
68
+ end
69
+
70
+ def test_io_does_nothing
71
+ assert_raises(RuntimeError) { custom_io(nil) }
72
+ end
73
+
74
+ def test_io_raises_exception_immediately
75
+ proc = Proc.new{ raise CustomError }
76
+ assert_raises(CustomError) { custom_io proc }
77
+ end
78
+
79
+ def test_io_throws_immediately
80
+ proc = Proc.new{ throw(:foo) }
81
+ catch(:foo){ custom_io proc }
82
+ end
83
+
84
+ def test_io_raises_exception_in_body
85
+ flag = false
86
+ proc = Proc.new do |parent, size, buf|
87
+ raise CustomError if flag
88
+ flag = true
89
+ parent.read(size, buf)
90
+ end
91
+ assert_raises(CustomError) { custom_io proc, big_png }
92
+ end
93
+
94
+ def test_io_throws_in_body
95
+ flag = false
96
+ proc = Proc.new do |parent, size, buf|
97
+ throw :foo if flag
98
+ flag = true
99
+ parent.read(size, buf)
100
+ end
101
+ catch(:foo){ custom_io proc, big_png }
102
+ end
103
+
104
+ def test_io_shrinks_buffer
105
+ proc = Proc.new do |parent, size, buf|
106
+ parent.read(size, buf)
107
+ buf.slice!(0, 10)
108
+ end
109
+ assert_raises(RuntimeError) { custom_io(proc, big_png) }
110
+ end
111
+
112
+ def test_io_enlarges_buffer
113
+ proc = Proc.new do |parent, size, buf|
114
+ res = parent.read(size, buf)
115
+ buf << res
116
+ end
117
+ assert_raises(RuntimeError) { validate_jpeg custom_io(proc, big_png) }
118
+ end
119
+
120
+ # Test yielding
121
+
122
+ def test_raise_in_each
123
+ assert_raises(CustomError) do
124
+ io = StringIO.new(PNG_DATA)
125
+ PNG.new(io, 200, 200).each { raise CustomError }
126
+ end
127
+ end
128
+
129
+ def test_throw_in_each
130
+ catch(:foo) do
131
+ io = StringIO.new(PNG_DATA)
132
+ PNG.new(io, 200, 200).each { throw :foo }
133
+ end
134
+ end
135
+
136
+ def test_each_shrinks_buffer
137
+ io = StringIO.new(PNG_DATA)
138
+ io_out = binary_stringio
139
+ PNG.new(io, 200, 200).each { |d| io_out << d; d.slice!(0, 4) }
140
+ validate_png(io_out.string)
141
+ end
142
+
143
+ def test_each_enlarges_buffer
144
+ io = StringIO.new(PNG_DATA)
145
+ io_out = binary_stringio
146
+ PNG.new(io, 200, 200).each { |d| io_out << d; d << "foobar" }
147
+ validate_png(io_out.string)
148
+ end
149
+
150
+ private
151
+
152
+ def io(io, width=nil, height=nil)
153
+ width ||= 100
154
+ height ||= 200
155
+ out = binary_stringio
156
+ PNG.new(io, width, height).each{ |d| out << d }
157
+ return out.string
158
+ end
159
+
160
+ def custom_io(*args)
161
+ io CustomIO.new(*args)
162
+ end
163
+
164
+ def resize_string(str, width=nil, height=nil)
165
+ io(StringIO.new(str), width, height)
166
+ end
167
+
168
+ def validate_png(data)
169
+ assert_equal "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data[0,8]
170
+ assert_equal "\x49\x45\x4E\x44\xAE\x42\x60\x82", data[-8, 8]
171
+ end
172
+
173
+ def big_png
174
+ resize_string(PNG_DATA, 200, 200)
175
+ end
176
+
177
+ def binary_stringio
178
+ io = StringIO.new
179
+ io.set_encoding 'ASCII-8BIT' if RUBY_VERSION >= '1.9'
180
+ io
181
+ end
182
+ end
183
+
184
+ class CustomError < RuntimeError; end
185
+
186
+ class CustomIO
187
+ def initialize(proc, *args)
188
+ @parent = StringIO.new(*args)
189
+ @proc = proc
190
+ end
191
+
192
+ def read(size, buf)
193
+ @proc.call(@parent, size, buf) if @proc
194
+ end
195
+ end
196
+ end
197
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oil
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-27 00:00:00.000000000 Z
12
+ date: 2012-03-08 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: tle@holymonkey.com
@@ -24,6 +24,7 @@ files:
24
24
  - ext/oil.c
25
25
  - ext/extconf.rb
26
26
  - test/test_jpeg.rb
27
+ - test/test_png.rb
27
28
  homepage: http://github.com/ender672/oil
28
29
  licenses: []
29
30
  post_install_message:
@@ -47,6 +48,7 @@ rubyforge_project:
47
48
  rubygems_version: 1.8.17
48
49
  signing_key:
49
50
  specification_version: 3
50
- summary: Resize JPEG images.
51
+ summary: Oil resizes JPEG and PNG images.
51
52
  test_files:
52
53
  - test/test_jpeg.rb
54
+ - test/test_png.rb