oil 0.1.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd14ee7146af851bb1e18d91cd713a70245ccd18
4
- data.tar.gz: 7f7821f5b3d7f93e916783fdd1d4373a1e7ca9c8
3
+ metadata.gz: b39dd91704802e2731a0f67da4743ae67c0535ed
4
+ data.tar.gz: 9f7767d654f542c11a9fec84f3dc4c097004755c
5
5
  SHA512:
6
- metadata.gz: 3eb3e7645f3a4930ef96932615f06d3802fb5aa200319e22c015d4568cfb2448cbf21e966b5a6cc0775c3ab22d41cca77215a5e23caa203d219310808e71e22d
7
- data.tar.gz: 25c9425aff69764f680a521808db27ab76418a1d4bec6428689885ea4d718f47e87d37b79cdd095edc51c2c9fb6616ed9ccdf862fd37a44ed070f7ace61456f4
6
+ metadata.gz: 2a862073a15ee977727398689263311dd9868c6c66d518cc840a1e5c7a81f638246b62b1d5d32a4dc2a28d8205a36f4327463a025b4dcb67f491711c19f5d2d5
7
+ data.tar.gz: 14da2b6aea208767826869a19c6fe8ba4824f15fba435ca1e8862adfe8d19603ce3e7e5a48e66cd693a3afa65c9ee923f6834ac49515c7a6751cd8f23b4e12dd
@@ -27,8 +27,6 @@ performance and low memory use.
27
27
 
28
28
  == REQUIREMENTS:
29
29
 
30
- These requirements do not apply to the JRuby gem.
31
-
32
30
  * libjpeg-turbo
33
31
  * libpng
34
32
 
@@ -48,10 +46,6 @@ Valgrind should not complain (ruby-1.9.3p125, compiled with -O3):
48
46
  $ valgrind /path/to/ruby -Iext:test test/test_jpeg.rb
49
47
  $ valgrind /path/to/ruby -Iext:test test/test_png.rb
50
48
 
51
- Tests should not leak memory:
52
-
53
- $ ruby -Iext:test -e "require 'test_jpeg.rb'; require 'test_png.rb'; loop{ MiniTest.run }"
54
-
55
49
  Changes to the interpolator should be analyzed using ResampleScope:
56
50
 
57
51
  https://github.com/jsummers/resamplescope
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ Rake::ExtensionTask.new('oil') do |ext|
6
6
  ext.lib_dir = 'lib/oil'
7
7
  end
8
8
 
9
- s = Gem::Specification.new('oil', '0.1.1') do |s|
9
+ s = Gem::Specification.new('oil', '0.1.2') do |s|
10
10
  s.license = 'MIT'
11
11
  s.summary = 'Resize JPEG and PNG images.'
12
12
  s.description = 'Resize JPEG and PNG images, aiming for fast performance and low memory use.'
@@ -7,7 +7,7 @@
7
7
  #define READ_SIZE 1024
8
8
  #define WRITE_SIZE 1024
9
9
 
10
- static ID id_GRAYSCALE, id_RGB, id_YCbCr, id_CMYK, id_YCCK, id_UNKNOWN;
10
+ static ID id_GRAYSCALE, id_RGB, id_YCbCr, id_CMYK, id_YCCK, id_RGBX, id_UNKNOWN;
11
11
  static ID id_APP0, id_APP1, id_APP2, id_APP3, id_APP4, id_APP5, id_APP6,
12
12
  id_APP7, id_APP8, id_APP9, id_APP10, id_APP11, id_APP12, id_APP13,
13
13
  id_APP14, id_APP15, id_COM;
@@ -49,6 +49,8 @@ static J_COLOR_SPACE sym_to_j_color_space(VALUE sym)
49
49
  return JCS_CMYK;
50
50
  } else if (rb == id_YCCK) {
51
51
  return JCS_YCCK;
52
+ } else if (rb == id_RGBX) {
53
+ return JCS_EXT_RGBX;
52
54
  }
53
55
  rb_raise(rb_eRuntimeError, "Color space not recognized.");
54
56
  }
@@ -293,10 +295,11 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self)
293
295
  */
294
296
  if (reader->source_io) {
295
297
  jpeg_abort_decompress(dinfo);
298
+ } else {
299
+ jpeg_create_decompress(dinfo);
296
300
  }
297
301
 
298
- jpeg_create_decompress(&reader->dinfo);
299
- reader->dinfo.src = &reader->mgr;
302
+ dinfo->src = &reader->mgr;
300
303
 
301
304
  rb_scan_args(argc, argv, "11", &io, &markers);
302
305
  reader->source_io = io;
@@ -305,13 +308,16 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self)
305
308
  if(!NIL_P(markers)) {
306
309
  Check_Type(markers, T_ARRAY);
307
310
  for (i=0; i<RARRAY_LEN(markers); i++) {
311
+ if (!SYMBOL_P(RARRAY_PTR(markers)[i])) {
312
+ rb_raise(rb_eTypeError, "Marker code is not a symbol.");
313
+ }
308
314
  marker_code = sym_to_marker_code(RARRAY_PTR(markers)[i]);
309
315
  jpeg_save_markers(dinfo, marker_code, 0xFFFF);
310
316
  }
311
317
  }
312
318
 
313
319
  /* Be warned that this can raise a ruby exception and longjmp away. */
314
- jpeg_read_header(&reader->dinfo, TRUE);
320
+ jpeg_read_header(dinfo, TRUE);
315
321
 
316
322
  jpeg_calc_output_dimensions(dinfo);
317
323
 
@@ -320,12 +326,16 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self)
320
326
 
321
327
  /*
322
328
  * call-seq:
323
- * reader.components -> number
329
+ * reader.num_components -> number
324
330
  *
325
- * Retrieve the number of components as stored in the JPEG image.
331
+ * Retrieve the number of components per pixel as indicated by the image
332
+ * header.
333
+ *
334
+ * This may differ from the number of components that will be returned by the
335
+ * decompressor if we ask for a color space transformation.
326
336
  */
327
337
 
328
- static VALUE components(VALUE self)
338
+ static VALUE num_components(VALUE self)
329
339
  {
330
340
  struct jpeg_decompress_struct * dinfo;
331
341
  Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
@@ -334,18 +344,53 @@ static VALUE components(VALUE self)
334
344
 
335
345
  /*
336
346
  * call-seq:
337
- * reader.color_space -> symbol
347
+ * reader.output_components -> number
348
+ *
349
+ * Retrieve the number of bytes per pixel that will be in the output image.
338
350
  *
339
- * Returns a symbol representing the color model in which the JPEG is stored.
351
+ * Not all bytes will necessarily have data, since some color spaces have
352
+ * padding.
353
+ */
354
+
355
+ static VALUE output_components(VALUE self)
356
+ {
357
+ struct jpeg_decompress_struct * dinfo;
358
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
359
+ return INT2FIX(dinfo->output_components);
360
+ }
361
+
362
+ /*
363
+ * call-seq:
364
+ * reader.out_color_components -> number
340
365
  *
341
- * This does not have to be set explicitly and can be relied upon when the file
342
- * conforms to JFIF or Adobe conventions. Otherwise it is guessed.
366
+ * Retrieve the number of components in the output color space.
367
+ *
368
+ * Some color spaces have padding, so this may not accurately represent the
369
+ * size of output pixels.
370
+ */
371
+
372
+ static VALUE out_color_components(VALUE self)
373
+ {
374
+ struct jpeg_decompress_struct * dinfo;
375
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
376
+ return INT2FIX(dinfo->out_color_components);
377
+ }
378
+
379
+ /*
380
+ * call-seq:
381
+ * reader.jpeg_color_space -> symbol
382
+ *
383
+ * Returns a symbol representing the color model in which the JPEG is stored,
384
+ * as indicated by the image header.
343
385
  *
344
386
  * Possible color models are: :GRAYSCALE, :RGB, :YCbCr, :CMYK, and :YCCK. This
345
387
  * method will return :UNKNOWN if the color model is not recognized.
388
+ *
389
+ * This may differ from the color space that will be returned by the
390
+ * decompressor if we ask for a color space transformation.
346
391
  */
347
392
 
348
- static VALUE color_space(VALUE self)
393
+ static VALUE jpeg_color_space(VALUE self)
349
394
  {
350
395
  struct jpeg_decompress_struct * dinfo;
351
396
  ID id;
@@ -370,7 +415,7 @@ static VALUE out_color_space(VALUE self)
370
415
  ID id;
371
416
 
372
417
  Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
373
- id = j_color_space_to_id(dinfo->jpeg_color_space);
418
+ id = j_color_space_to_id(dinfo->out_color_space);
374
419
 
375
420
  return ID2SYM(id);
376
421
  }
@@ -379,7 +424,7 @@ static VALUE out_color_space(VALUE self)
379
424
  * call-seq:
380
425
  * reader.out_color_space = symbol
381
426
  *
382
- * Set the color model to which teh image will be converted on decompress.
427
+ * Set the color model to which the image will be converted on decompress.
383
428
  */
384
429
 
385
430
  static VALUE set_out_color_space(VALUE self, VALUE cs)
@@ -389,19 +434,22 @@ static VALUE set_out_color_space(VALUE self, VALUE cs)
389
434
  Data_Get_Struct(self, struct readerdata, reader);
390
435
  raise_if_locked(reader);
391
436
 
392
- reader->dinfo.jpeg_color_space = sym_to_j_color_space(cs);
437
+ reader->dinfo.out_color_space = sym_to_j_color_space(cs);
393
438
  jpeg_calc_output_dimensions(&reader->dinfo);
394
439
  return cs;
395
440
  }
396
441
 
397
442
  /*
398
443
  * call-seq:
399
- * reader.width -> number
444
+ * reader.image_width -> number
400
445
  *
401
- * Retrieve the width of the image.
446
+ * The width of the of the image as indicated by the header.
447
+ *
448
+ * This may differ from the width of the image that will be returned by the
449
+ * decompressor if we request DCT scaling.
402
450
  */
403
451
 
404
- static VALUE width(VALUE self)
452
+ static VALUE image_width(VALUE self)
405
453
  {
406
454
  struct jpeg_decompress_struct * dinfo;
407
455
  Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
@@ -410,18 +458,49 @@ static VALUE width(VALUE self)
410
458
 
411
459
  /*
412
460
  * call-seq:
413
- * reader.height -> number
461
+ * reader.image_height -> number
462
+ *
463
+ * The height of the image as indicated by the header.
414
464
  *
415
- * Retrieve the height of the image.
465
+ * This may differ from the height of the image that will be returned by the
466
+ * decompressor if we request DCT scaling.
416
467
  */
417
468
 
418
- static VALUE height(VALUE self)
469
+ static VALUE image_height(VALUE self)
419
470
  {
420
471
  struct jpeg_decompress_struct * dinfo;
421
472
  Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
422
473
  return INT2FIX(dinfo->image_height);
423
474
  }
424
475
 
476
+ /*
477
+ * call-seq:
478
+ * reader.output_width -> number
479
+ *
480
+ * The width of the of the image that will be output by the decompressor.
481
+ */
482
+
483
+ static VALUE output_width(VALUE self)
484
+ {
485
+ struct jpeg_decompress_struct * dinfo;
486
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
487
+ return INT2FIX(dinfo->output_width);
488
+ }
489
+
490
+ /*
491
+ * call-seq:
492
+ * reader.image_height -> number
493
+ *
494
+ * The height of the image that will be output by the decompressor.
495
+ */
496
+
497
+ static VALUE output_height(VALUE self)
498
+ {
499
+ struct jpeg_decompress_struct * dinfo;
500
+ Data_Get_Struct(self, struct jpeg_decompress_struct, dinfo);
501
+ return INT2FIX(dinfo->output_height);
502
+ }
503
+
425
504
  /*
426
505
  * call-seq:
427
506
  * reader.markers -> hash
@@ -710,7 +789,12 @@ static VALUE each2(struct write_jpeg_args *args)
710
789
  if (!NIL_P(quality)) {
711
790
  jpeg_set_quality(cinfo, FIX2INT(quality), FALSE);
712
791
  }
792
+ }
713
793
 
794
+ jpeg_start_compress(cinfo, TRUE);
795
+ jpeg_start_decompress(dinfo);
796
+
797
+ if (!NIL_P(args->opts)) {
714
798
  markers = rb_hash_aref(args->opts, sym_markers);
715
799
  if (!NIL_P(markers)) {
716
800
  Check_Type(markers, T_HASH);
@@ -718,9 +802,6 @@ static VALUE each2(struct write_jpeg_args *args)
718
802
  }
719
803
  }
720
804
 
721
- jpeg_start_compress(cinfo, TRUE);
722
- jpeg_start_decompress(dinfo);
723
-
724
805
  for(i=0; i<scaley; i++) {
725
806
  while ((yinbuf = yscaler_next(ys))) {
726
807
  jpeg_read_scanlines(dinfo, (JSAMPARRAY)&inwidthbuf, 1);
@@ -816,12 +897,16 @@ void Init_jpeg()
816
897
  rb_define_alloc_func(cJPEGReader, allocate);
817
898
  rb_define_method(cJPEGReader, "initialize", initialize, -1);
818
899
  rb_define_method(cJPEGReader, "markers", markers, 0);
819
- rb_define_method(cJPEGReader, "color_space", color_space, 0);
900
+ rb_define_method(cJPEGReader, "jpeg_color_space", jpeg_color_space, 0);
820
901
  rb_define_method(cJPEGReader, "out_color_space", out_color_space, 0);
821
- rb_define_method(cJPEGReader, "color_space=", set_out_color_space, 1);
822
- rb_define_method(cJPEGReader, "components", components, 0);
823
- rb_define_method(cJPEGReader, "width", width, 0);
824
- rb_define_method(cJPEGReader, "height", height, 0);
902
+ rb_define_method(cJPEGReader, "out_color_space=", set_out_color_space, 1);
903
+ rb_define_method(cJPEGReader, "num_components", num_components, 0);
904
+ rb_define_method(cJPEGReader, "output_components", output_components, 0);
905
+ rb_define_method(cJPEGReader, "out_color_components", out_color_components, 0);
906
+ rb_define_method(cJPEGReader, "image_width", image_width, 0);
907
+ rb_define_method(cJPEGReader, "image_height", image_height, 0);
908
+ rb_define_method(cJPEGReader, "output_width", output_width, 0);
909
+ rb_define_method(cJPEGReader, "output_height", output_height, 0);
825
910
  rb_define_method(cJPEGReader, "each", each, -1);
826
911
  rb_define_method(cJPEGReader, "scale_num", scale_num, 0);
827
912
  rb_define_method(cJPEGReader, "scale_num=", set_scale_num, 1);
@@ -837,6 +922,7 @@ void Init_jpeg()
837
922
  id_YCbCr = rb_intern("YCbCr");
838
923
  id_CMYK = rb_intern("CMYK");
839
924
  id_YCCK = rb_intern("YCCK");
925
+ id_RGBX = rb_intern("RGBX");
840
926
  id_UNKNOWN = rb_intern("UNKNOWN");
841
927
  id_APP0 = rb_intern("APP0");
842
928
  id_APP1 = rb_intern("APP1");
@@ -54,6 +54,7 @@ static void write_data_fn(png_structp png_ptr, png_bytep data, png_size_t length
54
54
  static void deallocate(struct readerdata *reader)
55
55
  {
56
56
  png_destroy_read_struct(&reader->png, &reader->info, NULL);
57
+ free(reader);
57
58
  }
58
59
 
59
60
  static void mark(struct readerdata *reader)
@@ -63,13 +64,19 @@ static void mark(struct readerdata *reader)
63
64
  }
64
65
  }
65
66
 
67
+ static void allocate2(struct readerdata *reader)
68
+ {
69
+ reader->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, (png_error_ptr)error, (png_error_ptr)warning);
70
+ reader->info = png_create_info_struct(reader->png);
71
+ }
72
+
66
73
  static VALUE allocate(VALUE klass)
67
74
  {
68
75
  struct readerdata *reader;
69
76
  VALUE self;
70
77
 
71
78
  self = Data_Make_Struct(klass, struct readerdata, mark, deallocate, reader);
72
- reader->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, (png_error_ptr)error, (png_error_ptr)warning);
79
+ allocate2(reader);
73
80
  return self;
74
81
  }
75
82
 
@@ -88,16 +95,23 @@ static VALUE initialize(VALUE self, VALUE io)
88
95
 
89
96
  Data_Get_Struct(self, struct readerdata, reader);
90
97
 
91
- if (reader->source_io) {
98
+ if (reader->info) {
92
99
  png_destroy_read_struct(&reader->png, &reader->info, NULL);
93
- reader->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, (png_error_ptr)error, (png_error_ptr)warning);
100
+ allocate2(reader);
94
101
  reader->locked = 0;
95
102
  }
96
103
 
97
104
  reader->source_io = io;
98
- reader->info = png_create_info_struct(reader->png);
99
105
  png_set_read_fn(reader->png, (void*)io, read_data);
100
106
  png_read_info(reader->png, reader->info);
107
+
108
+ png_set_packing(reader->png);
109
+ png_set_strip_16(reader->png);
110
+ png_set_expand(reader->png);
111
+ png_read_update_info(reader->png, reader->info);
112
+
113
+ reader->scale_width = png_get_image_width(reader->png, reader->info);
114
+ reader->scale_height = png_get_image_height(reader->png, reader->info);
101
115
  return self;
102
116
  }
103
117
 
@@ -193,54 +207,75 @@ static VALUE set_scale_height(VALUE self, VALUE scale_height)
193
207
  return scale_height;
194
208
  }
195
209
 
196
- struct write_png_args {
197
- VALUE opts;
210
+ struct each_args {
198
211
  struct readerdata *reader;
212
+ png_structp wpng;
213
+ png_infop winfo;
199
214
  unsigned char *inwidthbuf;
200
215
  unsigned char *outwidthbuf;
201
- struct yscaler *ys;
202
- png_structp png;
203
- png_infop info;
216
+ unsigned char **scanlines;
217
+ struct yscaler ys;
204
218
  };
205
219
 
206
- static VALUE each2(struct write_png_args *args)
220
+ static VALUE each_interlace(struct each_args *args)
221
+ {
222
+ struct readerdata *reader;
223
+ unsigned char *inwidthbuf, *outwidthbuf;
224
+ uint32_t i, width, height, scalex, scaley;
225
+ int cmp;
226
+
227
+ reader = args->reader;
228
+ inwidthbuf = args->inwidthbuf;
229
+ outwidthbuf = args->outwidthbuf;
230
+ scalex = reader->scale_width;
231
+ scaley = reader->scale_height;
232
+ cmp = png_get_channels(reader->png, reader->info);
233
+ width = png_get_image_width(reader->png, reader->info);
234
+ height = png_get_image_height(reader->png, reader->info);
235
+
236
+ png_write_info(args->wpng, args->winfo);
237
+ png_read_image(args->reader->png, (png_bytepp)args->scanlines);
238
+
239
+ for (i=0; i<scaley; i++) {
240
+ yscaler_prealloc_scale(height, scaley,
241
+ (uint8_t **)args->scanlines, (uint8_t *)inwidthbuf,
242
+ i, width, cmp, 0);
243
+ xscale(inwidthbuf, width, outwidthbuf, scalex, cmp, 0);
244
+ png_write_row(args->wpng, outwidthbuf);
245
+ }
246
+ png_write_end(args->wpng, args->winfo);
247
+ return Qnil;
248
+ }
249
+
250
+ static VALUE each_interlace_none(struct each_args *args)
207
251
  {
208
- png_structp png;
209
- png_infop info;
210
- png_byte ctype;
211
252
  struct readerdata *reader;
212
253
  unsigned char *inwidthbuf, *outwidthbuf, *yinbuf;
213
254
  struct yscaler *ys;
214
- uint32_t i, scalex, scaley;
255
+ uint32_t i, width, scalex, scaley;
215
256
  int cmp;
216
257
 
217
258
  reader = args->reader;
218
- png = args->png;
219
- info = args->info;
220
259
  inwidthbuf = args->inwidthbuf;
221
260
  outwidthbuf = args->outwidthbuf;
222
- ys = args->ys;
223
- scalex = args->reader->scale_width;
224
- scaley = args->reader->scale_height;
225
-
261
+ ys = &args->ys;
262
+ scalex = reader->scale_width;
263
+ scaley = reader->scale_height;
226
264
  cmp = png_get_channels(reader->png, reader->info);
227
- png_set_write_fn(png, 0, write_data_fn, flush_data_fn);
228
- ctype = png_get_color_type(reader->png, reader->info);
265
+ width = png_get_image_width(reader->png, reader->info);
229
266
 
230
- png_set_IHDR(png, info, scalex, scaley, 8, ctype, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
231
- png_write_info(png, info);
267
+ png_write_info(args->wpng, args->winfo);
232
268
 
233
269
  for(i=0; i<scaley; i++) {
234
270
  while ((yinbuf = yscaler_next(ys))) {
235
271
  png_read_row(reader->png, inwidthbuf, NULL);
236
- xscale(inwidthbuf, png_get_image_width(reader->png, reader->info), yinbuf, scalex, cmp, 0);
272
+ xscale(inwidthbuf, width, yinbuf, scalex, cmp, 0);
237
273
  }
238
274
  yscaler_scale(ys, outwidthbuf, scalex, cmp, 0);
239
- png_write_row(png, outwidthbuf);
275
+ png_write_row(args->wpng, outwidthbuf);
240
276
  }
241
277
 
242
- png_write_end(png, info);
243
-
278
+ png_write_end(args->wpng, args->winfo);
244
279
  return Qnil;
245
280
  }
246
281
 
@@ -261,13 +296,15 @@ static VALUE each2(struct write_png_args *args)
261
296
  static VALUE each(int argc, VALUE *argv, VALUE self)
262
297
  {
263
298
  struct readerdata *reader;
264
- int cmp, state;
265
- struct write_png_args args;
266
- unsigned char *inwidthbuf, *outwidthbuf;
267
- struct yscaler ys;
299
+ png_infop winfo;
300
+ png_structp wpng;
268
301
  VALUE opts;
269
- png_structp png;
270
- png_infop info;
302
+ int cmp, state;
303
+ struct each_args args;
304
+ uint32_t i, height;
305
+ png_byte ctype;
306
+ unsigned char **scanlines;
307
+ size_t row_bytes;
271
308
 
272
309
  rb_scan_args(argc, argv, "01", &opts);
273
310
 
@@ -276,41 +313,50 @@ static VALUE each(int argc, VALUE *argv, VALUE self)
276
313
  raise_if_locked(reader);
277
314
  reader->locked = 1;
278
315
 
279
- png_set_packing(reader->png);
280
- png_set_strip_16(reader->png);
281
- png_set_expand(reader->png);
282
- png_read_update_info(reader->png, reader->info);
283
-
284
- if (!reader->scale_width) {
285
- reader->scale_width = png_get_image_width(reader->png, reader->info);
286
- }
287
- if (!reader->scale_height) {
288
- reader->scale_height = png_get_image_height(reader->png, reader->info);
289
- }
290
-
291
- png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
316
+ wpng = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
292
317
  (png_error_ptr)error, (png_error_ptr)warning);
293
- info = png_create_info_struct(png);
318
+ winfo = png_create_info_struct(wpng);
319
+ png_set_write_fn(wpng, 0, write_data_fn, flush_data_fn);
294
320
 
295
- inwidthbuf = malloc(png_get_rowbytes(reader->png, reader->info));
296
321
  cmp = png_get_channels(reader->png, reader->info);
297
- outwidthbuf = malloc(reader->scale_width * cmp);
298
- yscaler_init(&ys, png_get_image_height(reader->png, reader->info),
299
- reader->scale_height, reader->scale_width * cmp);
322
+ ctype = png_get_color_type(reader->png, reader->info);
323
+
324
+ png_set_IHDR(wpng, winfo, reader->scale_width, reader->scale_height, 8,
325
+ ctype, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
326
+ PNG_FILTER_TYPE_DEFAULT);
327
+
328
+ height = png_get_image_height(reader->png, reader->info);
329
+ row_bytes = png_get_rowbytes(reader->png, reader->info);
300
330
 
301
331
  args.reader = reader;
302
- args.opts = opts;
303
- args.png = png;
304
- args.info = info;
305
- args.inwidthbuf = inwidthbuf;
306
- args.outwidthbuf = outwidthbuf;
307
- args.ys = &ys;
308
- rb_protect((VALUE(*)(VALUE))each2, (VALUE)&args, &state);
309
-
310
- yscaler_free(&ys);
311
- free(inwidthbuf);
312
- free(outwidthbuf);
313
- png_destroy_write_struct(&png, &info);
332
+ args.wpng = wpng;
333
+ args.winfo = winfo;
334
+ args.inwidthbuf = malloc(row_bytes);
335
+ args.outwidthbuf = malloc(reader->scale_width * cmp);
336
+
337
+ if (png_get_interlace_type(reader->png, reader->info) == PNG_INTERLACE_NONE) {
338
+ yscaler_init(&args.ys, height, reader->scale_height,
339
+ reader->scale_width * cmp);
340
+ rb_protect((VALUE(*)(VALUE))each_interlace_none, (VALUE)&args, &state);
341
+ yscaler_free(&args.ys);
342
+ } else {
343
+ scanlines = malloc(height * sizeof(unsigned char *));
344
+ for (i=0; i<height; i++) {
345
+ scanlines[i] = malloc(row_bytes);
346
+ }
347
+
348
+ args.scanlines = scanlines;
349
+ rb_protect((VALUE(*)(VALUE))each_interlace, (VALUE)&args, &state);
350
+
351
+ for (i=0; i<height; i++) {
352
+ free(scanlines[i]);
353
+ }
354
+ free(scanlines);
355
+ }
356
+
357
+ free(args.inwidthbuf);
358
+ free(args.outwidthbuf);
359
+ png_destroy_write_struct(&wpng, &winfo);
314
360
 
315
361
  if (state) {
316
362
  rb_jump_tag(state);
@@ -1,4 +1,5 @@
1
1
  #include "yscaler.h"
2
+ #include "resample.h"
2
3
  #include <stdlib.h>
3
4
  #include <stdint.h>
4
5
 
@@ -136,3 +137,31 @@ void yscaler_scale(struct yscaler *ys, uint8_t *out, uint32_t width,
136
137
  ys->out_pos++;
137
138
  yscaler_map_pos(ys);
138
139
  }
140
+
141
+ void yscaler_prealloc_scale(uint32_t in_height, uint32_t out_height,
142
+ uint8_t **in, uint8_t *out, uint32_t pos, uint32_t width, uint8_t cmp,
143
+ uint8_t opts)
144
+ {
145
+ uint32_t i, taps;
146
+ int32_t smp_i, strip_pos;
147
+ uint8_t **virt;
148
+ float ty;
149
+
150
+ taps = calc_taps(in_height, out_height);
151
+ virt = malloc(taps * sizeof(uint8_t *));
152
+ smp_i = split_map(in_height, out_height, pos, &ty);
153
+ strip_pos = smp_i + 1 - taps / 2;
154
+
155
+ for (i=0; i<taps; i++) {
156
+ if (strip_pos < 0) {
157
+ virt[i] = in[0];
158
+ } else if ((uint32_t)strip_pos > in_height - 1) {
159
+ virt[i] = in[in_height - 1];
160
+ } else {
161
+ virt[i] = in[strip_pos];
162
+ }
163
+ strip_pos++;
164
+ }
165
+
166
+ strip_scale((void **)virt, taps, width, (void *)out, ty, cmp, opts);
167
+ }
@@ -1,7 +1,6 @@
1
1
  #ifndef YSCALER_H
2
2
  #define YSCALER_H
3
3
 
4
- #include "resample.h"
5
4
  #include <stdint.h>
6
5
 
7
6
  struct strip {
@@ -27,5 +26,8 @@ void yscaler_free(struct yscaler *ys);
27
26
  unsigned char *yscaler_next(struct yscaler *ys);
28
27
  void yscaler_scale(struct yscaler *ys, uint8_t *out, uint32_t width,
29
28
  uint8_t cmp, uint8_t opts);
29
+ void yscaler_prealloc_scale(uint32_t in_height, uint32_t out_height,
30
+ uint8_t **in, uint8_t *out, uint32_t pos, uint32_t width, uint8_t cmp,
31
+ uint8_t opts);
30
32
 
31
33
  #endif
data/lib/oil.rb CHANGED
@@ -1,5 +1,31 @@
1
1
  module Oil
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
+
4
+ def self.sniff_signature(io)
5
+ a = io.getc
6
+ b = io.getc
7
+ io.ungetc(b)
8
+ io.ungetc(a)
9
+
10
+ if (a == "\xFF".b && b == "\xD8".b)
11
+ return :JPEG
12
+ elsif (a == "\x89".b && b == "P".b)
13
+ return :PNG
14
+ end
15
+ end
16
+
17
+ def self.new(io, box_width, box_height)
18
+ case sniff_signature(io)
19
+ when :JPEG
20
+ return new_jpeg_reader(io, box_width, box_height)
21
+ when :PNG
22
+ return new_png_reader(io, box_width, box_height)
23
+ else
24
+ raise "Unknown image file format."
25
+ end
26
+ end
27
+
28
+ private
3
29
 
4
30
  def self.fix_ratio(sw, sh, boxw, boxh)
5
31
  x = boxw / sw.to_f
@@ -25,52 +51,52 @@ module Oil
25
51
  return destw, desth
26
52
  end
27
53
 
28
- # JPEG Pre-scaling is equivalent to a box filter at an integer scale factor.
29
- # We don't use this to scale down past 4x the target image size in order to
30
- # get proper bicubic scaling in the final image.
31
- def self.pre_scale(sw, sh, dw, dh)
32
- inv_scale = sw / dw
54
+ def self.new_jpeg_reader(io, box_width, box_height)
55
+ o = JPEGReader.new(io, [:COM, :APP1, :APP2])
56
+
57
+ # bump RGB images to RGBX
58
+ if (o.out_color_space == :RGB)
59
+ o.out_color_space = :RGBX
60
+ end
61
+
62
+ # JPEG Pre-scaling is equivalent to a box filter at an integer scale factor.
63
+ # We don't use this to scale down past 4x the target image size in order to
64
+ # get proper bicubic scaling in the final image.
65
+ inv_scale = o.image_width / box_width
33
66
  inv_scale /= 4
34
67
 
35
68
  if inv_scale >= 8
36
- return 8
69
+ o.scale_denom = 8
37
70
  elsif inv_scale >= 4
38
- return 4
71
+ o.scale_denom = 4
39
72
  elsif inv_scale >= 2
40
- return 2
41
- else
42
- return 0
73
+ o.scale_denom = 2
43
74
  end
44
- end
45
75
 
46
- def self.new(io, box_width, box_height)
47
- a = io.getc
48
- b = io.getc
49
- io.ungetc(b)
50
- io.ungetc(a)
76
+ destw, desth = self.fix_ratio(o.output_width, o.output_height, box_width, box_height)
77
+ o.scale_width = destw
78
+ o.scale_height = desth
51
79
 
52
- if (a == "\xFF".b && b == "\xD8".b)
53
- o = JPEGReader.new(io, [:COM, :APP1, :APP2])
54
- if (o.color_space == :RGB)
55
- o.out_color_space = :RGBX
56
- end
57
- elsif (a == "\x89".b && b == "P".b)
58
- o = PNGReader.new(io)
59
- else
60
- raise "Unknown image file format."
61
- end
80
+ return JPEGReaderWrapper.new(o, { markers: o.markers, quality: 95 })
81
+ end
62
82
 
83
+ def self.new_png_reader(io, box_width, box_height)
84
+ o = PNGReader.new(io)
63
85
  destw, desth = self.fix_ratio(o.width, o.height, box_width, box_height)
64
- pre = self.pre_scale(o.width, o.height, box_width, box_height)
65
-
66
86
  o.scale_width = destw
67
87
  o.scale_height = desth
88
+ return o
89
+ end
90
+ end
68
91
 
69
- if o.respond_to?(:scale_denom) and pre > 0
70
- o.scale_denom = pre
71
- end
92
+ class JPEGReaderWrapper
93
+ def initialize(reader, opts)
94
+ @reader = reader
95
+ @opts = opts
96
+ end
72
97
 
73
- return o
98
+ def each(&block)
99
+ @reader.each(@opts, &block)
74
100
  end
75
101
  end
76
102
 
@@ -72,19 +72,3 @@ class NotStringIO < CustomIO
72
72
  return 78887
73
73
  end
74
74
  end
75
-
76
- def resize_string(str, width=nil, height=nil)
77
- io = StringIO.new(str)
78
- width ||= 100
79
- height ||= 200
80
- out = binary_stringio
81
- o = Oil.new(io, width, height).each{ |d| out << d }
82
- out.string
83
- end
84
-
85
- def binary_stringio
86
- io = StringIO.new
87
- io.set_encoding 'ASCII-8BIT' if RUBY_VERSION >= '1.9'
88
- io
89
- end
90
-
@@ -16,32 +16,46 @@ class TestJPEG < MiniTest::Test
16
16
  \x01\x01\x00\x00\x3f\x00\xd2\xcf\x20\xff\xd9".b
17
17
 
18
18
  BIG_JPEG = begin
19
- resize_string(JPEG_DATA, 2000, 2000)
19
+ s = ""
20
+ r = Oil::JPEGReader.new(StringIO.new(JPEG_DATA))
21
+ r.scale_width = 2000
22
+ r.scale_height = 2000
23
+ r.each{ |a| s << a }
24
+ s
20
25
  end
21
26
 
22
27
  def test_valid
23
28
  o = Oil::JPEGReader.new(jpeg_io)
24
- assert_equal 1, o.width
25
- assert_equal 1, o.height
29
+ assert_equal 1, o.image_width
30
+ assert_equal 1, o.image_height
26
31
  end
27
32
 
28
33
  def test_missing_eof
29
34
  io = StringIO.new(JPEG_DATA[0..-2])
30
35
  o = Oil::JPEGReader.new(io)
31
- assert_equal 1, o.width
32
- assert_equal 1, o.height
36
+ assert_equal 1, o.image_width
37
+ assert_equal 1, o.image_height
33
38
  end
34
39
 
35
40
  def test_bogus_header_marker
36
41
  str = JPEG_DATA.dup
37
42
  str[3] = "\x10"
38
- assert_raises(RuntimeError) { resize_string(str) }
43
+ assert_raises(RuntimeError) { drain_string(str) }
39
44
  end
40
45
 
41
46
  def test_bogus_body_marker
42
47
  str = JPEG_DATA.dup
43
48
  str[-22] = "\x10"
44
- assert_raises(RuntimeError) { resize_string(str) }
49
+ assert_raises(RuntimeError) { drain_string(str) }
50
+ end
51
+
52
+ def test_color_space
53
+ o = Oil::JPEGReader.new(jpeg_io)
54
+ assert_equal :GRAYSCALE, o.jpeg_color_space
55
+ assert_equal :GRAYSCALE, o.out_color_space
56
+ assert_equal 1, o.num_components
57
+ assert_equal 1, o.output_components
58
+ assert_equal 1, o.out_color_components
45
59
  end
46
60
 
47
61
  # Allocation tests
@@ -67,7 +81,10 @@ class TestJPEG < MiniTest::Test
67
81
  end
68
82
 
69
83
  def resize(io)
70
- o = Oil.new(io, 20, 10).each{ |d| }
84
+ o = Oil::JPEGReader.new(io)
85
+ o.scale_width = 10
86
+ o.scale_height = 20
87
+ o.each{ |d| }
71
88
  end
72
89
 
73
90
  def test_io_too_much_data
@@ -112,18 +129,18 @@ class TestJPEG < MiniTest::Test
112
129
 
113
130
  def test_raise_in_each
114
131
  assert_raises(CustomError) do
115
- Oil.new(jpeg_io, 10, 20).each{ raise CustomError }
132
+ Oil::JPEGReader.new(jpeg_io).each{ raise CustomError }
116
133
  end
117
134
  end
118
135
 
119
136
  def test_throw_in_each
120
137
  catch(:foo) do
121
- Oil.new(jpeg_io, 10, 20).each{ throw :foo }
138
+ Oil::JPEGReader.new(jpeg_io).each{ throw :foo }
122
139
  end
123
140
  end
124
141
 
125
142
  def test_each_in_each
126
- o = Oil.new(jpeg_io, 10, 20)
143
+ o = Oil::JPEGReader.new(jpeg_io)
127
144
  o.each do |d|
128
145
  assert_raises(RuntimeError) do
129
146
  o.each { |e| }
@@ -132,15 +149,78 @@ class TestJPEG < MiniTest::Test
132
149
  end
133
150
 
134
151
  def test_each_shrinks_buffer
135
- io = StringIO.new(JPEG_DATA)
136
- io_out = binary_stringio
137
- Oil.new(io, 200, 200).each{ |d| io_out << d; d.slice!(0, 4) }
152
+ Oil::JPEGReader.new(jpeg_io).each{ |d| d.slice!(0, 4) }
138
153
  end
139
154
 
140
155
  def test_each_enlarges_buffer
141
- io = StringIO.new(JPEG_DATA)
142
- io_out = binary_stringio
143
- o = Oil.new(io, 200, 200).each{ |d| io_out << d; d << "foobar" }
156
+ Oil::JPEGReader.new(jpeg_io).each{ |d| d << "foobar" }
157
+ end
158
+
159
+ def test_marker_roundtrip
160
+ str = ""
161
+ opts = { markers: { COM: ["hello world", "foobar123"]}}
162
+ Oil::JPEGReader.new(jpeg_io).each(opts){ |s| str << s }
163
+
164
+ r = Oil::JPEGReader.new(StringIO.new(str), [:COM])
165
+
166
+ assert_equal(r.markers, opts[:markers])
167
+ end
168
+
169
+ def test_marker_code_unrecognized
170
+ assert_raises(RuntimeError) do
171
+ Oil::JPEGReader.new(jpeg_io, [:FOOBAR])
172
+ end
173
+ end
174
+
175
+ def test_marker_codes_not_array
176
+ assert_raises(TypeError) do
177
+ Oil::JPEGReader.new(jpeg_io, 1234)
178
+ end
179
+ end
180
+
181
+ def test_marker_code_not_symbol
182
+ assert_raises(TypeError) do
183
+ Oil::JPEGReader.new(jpeg_io, [1234])
184
+ end
185
+ end
186
+
187
+ def test_marker_too_big
188
+ opts = { markers: { COM: ["hello world"*10000]}}
189
+
190
+ assert_raises(RuntimeError) do
191
+ Oil::JPEGReader.new(jpeg_io).each(opts){ |s| str << s }
192
+ end
193
+ end
194
+
195
+ def test_markers_not_hash
196
+ opts = { markers: 1234 }
197
+
198
+ assert_raises(TypeError) do
199
+ Oil::JPEGReader.new(jpeg_io).each(opts){}
200
+ end
201
+ end
202
+
203
+ def test_marker_value_not_array
204
+ opts = { markers: { COM: 1234}}
205
+
206
+ assert_raises(TypeError) do
207
+ Oil::JPEGReader.new(jpeg_io).each(opts){}
208
+ end
209
+ end
210
+
211
+ def test_marker_value_entry_not_string
212
+ opts = { markers: { COM: [1234]}}
213
+
214
+ assert_raises(TypeError) do
215
+ Oil::JPEGReader.new(jpeg_io).each(opts){}
216
+ end
217
+ end
218
+
219
+ def test_quality_not_a_number
220
+ opts = { quality: "foobar" }
221
+ assert_raises(TypeError) do
222
+ Oil::JPEGReader.new(jpeg_io).each(opts){}
223
+ end
144
224
  end
145
225
 
146
226
  private
@@ -148,4 +228,8 @@ class TestJPEG < MiniTest::Test
148
228
  def jpeg_io
149
229
  StringIO.new(JPEG_DATA)
150
230
  end
231
+
232
+ def drain_string(str)
233
+ Oil::JPEGReader.new(StringIO.new(str)).each{ |s| }
234
+ end
151
235
  end
@@ -13,11 +13,16 @@ class TestPNG < MiniTest::Test
13
13
  \x57\xBF\xAB\xD4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82".b
14
14
 
15
15
  BIG_PNG = begin
16
- resize_string(PNG_DATA, 500, 1000)
16
+ s = ""
17
+ r = Oil::PNGReader.new(StringIO.new(PNG_DATA))
18
+ r.scale_width = 500
19
+ r.scale_height = 1000
20
+ r.each{ |a| s << a }
21
+ s
17
22
  end
18
23
 
19
24
  def test_valid
20
- o = Oil.new(png_io, 10 ,20)
25
+ o = Oil::PNGReader.new(png_io)
21
26
  assert_equal 1, o.width
22
27
  assert_equal 1, o.height
23
28
  end
@@ -25,20 +30,20 @@ class TestPNG < MiniTest::Test
25
30
  def test_bogus_header_chunk
26
31
  str = PNG_DATA.dup
27
32
  str[15] = "\x10"
28
- assert_raises(RuntimeError) { resize_string(str) }
33
+ assert_raises(RuntimeError) { drain_string(str) }
29
34
  end
30
35
 
31
36
  def test_bogus_body_chunk
32
37
  str = PNG_DATA.dup
33
38
  str[37] = "\x10"
34
- assert_raises(RuntimeError) { resize_string(str) }
39
+ assert_raises(RuntimeError) { drain_string(str) }
35
40
  end
36
41
 
37
42
  def test_bogus_end_chunk
38
43
  str = PNG_DATA.dup
39
44
  str[-6] = "\x10"
40
45
  io = StringIO.new(str)
41
- o = Oil.new(png_io, 10 ,20)
46
+ o = Oil::PNGReader.new(png_io)
42
47
  assert_equal 1, o.width
43
48
  assert_equal 1, o.height
44
49
  end
@@ -66,7 +71,7 @@ class TestPNG < MiniTest::Test
66
71
  end
67
72
 
68
73
  def resize(io)
69
- Oil.new(io, 20, 10).each{ |d| }
74
+ Oil::PNGReader.new(io).each{ |d| }
70
75
  end
71
76
 
72
77
  def test_io_too_much_data
@@ -109,33 +114,29 @@ class TestPNG < MiniTest::Test
109
114
 
110
115
  def test_raise_in_each
111
116
  assert_raises(CustomError) do
112
- Oil.new(png_io, 10, 20).each { raise CustomError }
117
+ Oil::PNGReader.new(png_io).each { raise CustomError }
113
118
  end
114
119
  end
115
120
 
116
121
  def test_throw_in_each
117
122
  catch(:foo) do
118
- Oil.new(png_io, 10, 20).each { throw :foo }
123
+ Oil::PNGReader.new(png_io).each { throw :foo }
119
124
  end
120
125
  end
121
126
 
122
127
  def test_each_in_each
123
- o = Oil.new(png_io, 10, 20)
128
+ o = Oil::PNGReader.new(png_io)
124
129
  o.each do |d|
125
130
  assert_raises(RuntimeError){ o.each { |e| } }
126
131
  end
127
132
  end
128
133
 
129
134
  def test_each_shrinks_buffer
130
- io = StringIO.new(PNG_DATA)
131
- io_out = binary_stringio
132
- Oil.new(io, 200, 200).each { |d| io_out << d; d.slice!(0, 4) }
135
+ Oil::PNGReader.new(png_io).each { |d| d.slice!(0, 4) }
133
136
  end
134
137
 
135
138
  def test_each_enlarges_buffer
136
- io = StringIO.new(PNG_DATA)
137
- io_out = binary_stringio
138
- Oil.new(io, 200, 200).each { |d| io_out << d; d << "foobar" }
139
+ Oil::PNGReader.new(png_io).each { |d| d << "foobar" }
139
140
  end
140
141
 
141
142
  private
@@ -143,4 +144,8 @@ class TestPNG < MiniTest::Test
143
144
  def png_io
144
145
  StringIO.new(PNG_DATA)
145
146
  end
147
+
148
+ def drain_string(str)
149
+ Oil::PNGReader.new(StringIO.new(str)).each{|s|}
150
+ end
146
151
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oil
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Timothy Elliott
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-15 00:00:00.000000000 Z
11
+ date: 2014-11-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Resize JPEG and PNG images, aiming for fast performance and low memory
14
14
  use.
@@ -54,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
54
  version: '0'
55
55
  requirements: []
56
56
  rubyforge_project:
57
- rubygems_version: 2.4.3
57
+ rubygems_version: 2.4.4
58
58
  signing_key:
59
59
  specification_version: 4
60
60
  summary: Resize JPEG and PNG images.