oil 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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