oil 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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.