image_pack 0.2.2 → 0.2.4

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +108 -119
  4. data/ext/image_pack/extconf.rb +36 -125
  5. data/ext/image_pack/image_pack.c +736 -614
  6. data/ext/image_pack/mozjpeg_sources.rb +178 -0
  7. data/ext/image_pack/vendor/mozjpeg/BUILDING.md +744 -0
  8. data/ext/image_pack/vendor/mozjpeg/CODE_OF_CONDUCT.md +15 -0
  9. data/ext/image_pack/vendor/mozjpeg/ChangeLog.md +1996 -0
  10. data/lib/image_pack/configuration.rb +54 -8
  11. data/lib/image_pack/version.rb +1 -1
  12. data/lib/image_pack.rb +87 -25
  13. metadata +14 -79
  14. data/ext/image_pack/vendor/mozjpeg/README.ijg +0 -258
  15. data/ext/image_pack/vendor/mozjpeg/cdjpeg.c +0 -156
  16. data/ext/image_pack/vendor/mozjpeg/cjpeg.c +0 -961
  17. data/ext/image_pack/vendor/mozjpeg/djpeg.c +0 -855
  18. data/ext/image_pack/vendor/mozjpeg/jaricom.c +0 -157
  19. data/ext/image_pack/vendor/mozjpeg/jcarith.c +0 -972
  20. data/ext/image_pack/vendor/mozjpeg/jcstest.c +0 -126
  21. data/ext/image_pack/vendor/mozjpeg/jdarith.c +0 -782
  22. data/ext/image_pack/vendor/mozjpeg/jdatadst-tj.c +0 -198
  23. data/ext/image_pack/vendor/mozjpeg/jdatasrc-tj.c +0 -194
  24. data/ext/image_pack/vendor/mozjpeg/jpegtran.c +0 -827
  25. data/ext/image_pack/vendor/mozjpeg/jpegyuv.c +0 -172
  26. data/ext/image_pack/vendor/mozjpeg/rdbmp.c +0 -690
  27. data/ext/image_pack/vendor/mozjpeg/rdcolmap.c +0 -253
  28. data/ext/image_pack/vendor/mozjpeg/rdgif.c +0 -720
  29. data/ext/image_pack/vendor/mozjpeg/rdjpeg.c +0 -160
  30. data/ext/image_pack/vendor/mozjpeg/rdjpgcom.c +0 -494
  31. data/ext/image_pack/vendor/mozjpeg/rdpng.c +0 -194
  32. data/ext/image_pack/vendor/mozjpeg/rdppm.c +0 -781
  33. data/ext/image_pack/vendor/mozjpeg/rdswitch.c +0 -642
  34. data/ext/image_pack/vendor/mozjpeg/rdtarga.c +0 -508
  35. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jccolext-neon.c +0 -148
  36. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jchuff-neon.c +0 -334
  37. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jsimd.c +0 -976
  38. data/ext/image_pack/vendor/mozjpeg/simd/i386/jsimd.c +0 -1312
  39. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd.c +0 -1143
  40. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolext-mmi.c +0 -455
  41. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolor-mmi.c +0 -148
  42. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgray-mmi.c +0 -132
  43. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgryext-mmi.c +0 -374
  44. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcsample-mmi.c +0 -98
  45. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolext-mmi.c +0 -415
  46. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolor-mmi.c +0 -139
  47. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmerge-mmi.c +0 -149
  48. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmrgext-mmi.c +0 -615
  49. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdsample-mmi.c +0 -304
  50. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctfst-mmi.c +0 -255
  51. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctint-mmi.c +0 -398
  52. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctfst-mmi.c +0 -395
  53. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctint-mmi.c +0 -571
  54. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jquanti-mmi.c +0 -124
  55. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jsimd.c +0 -866
  56. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolext-altivec.c +0 -269
  57. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolor-altivec.c +0 -116
  58. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgray-altivec.c +0 -111
  59. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgryext-altivec.c +0 -228
  60. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcsample-altivec.c +0 -159
  61. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolext-altivec.c +0 -276
  62. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolor-altivec.c +0 -106
  63. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmerge-altivec.c +0 -130
  64. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmrgext-altivec.c +0 -329
  65. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdsample-altivec.c +0 -400
  66. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctfst-altivec.c +0 -154
  67. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctint-altivec.c +0 -258
  68. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctfst-altivec.c +0 -255
  69. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctint-altivec.c +0 -357
  70. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jquanti-altivec.c +0 -250
  71. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jsimd.c +0 -884
  72. data/ext/image_pack/vendor/mozjpeg/strtest.c +0 -170
  73. data/ext/image_pack/vendor/mozjpeg/tjbench.c +0 -1044
  74. data/ext/image_pack/vendor/mozjpeg/tjexample.c +0 -406
  75. data/ext/image_pack/vendor/mozjpeg/tjunittest.c +0 -961
  76. data/ext/image_pack/vendor/mozjpeg/tjutil.c +0 -70
  77. data/ext/image_pack/vendor/mozjpeg/transupp.c +0 -2373
  78. data/ext/image_pack/vendor/mozjpeg/turbojpeg-jni.c +0 -1259
  79. data/ext/image_pack/vendor/mozjpeg/turbojpeg.c +0 -2320
  80. data/ext/image_pack/vendor/mozjpeg/wrbmp.c +0 -552
  81. data/ext/image_pack/vendor/mozjpeg/wrgif.c +0 -580
  82. data/ext/image_pack/vendor/mozjpeg/wrjpgcom.c +0 -577
  83. data/ext/image_pack/vendor/mozjpeg/wrppm.c +0 -366
  84. data/ext/image_pack/vendor/mozjpeg/wrtarga.c +0 -258
  85. data/ext/image_pack/vendor/mozjpeg/yuvjpeg.c +0 -268
@@ -3,18 +3,26 @@
3
3
  #include <ruby/version.h>
4
4
  #include <ruby/encoding.h>
5
5
 
6
- #if RUBY_API_VERSION_MAJOR < 3 || (RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR < 4)
7
- #error "image_pack requires Ruby 3.4+ (RB_NOGVL_OFFLOAD_SAFE not available)"
6
+ #if defined(RB_NOGVL_OFFLOAD_SAFE)
7
+ #define IMAGE_PACK_HAS_OFFLOAD_SAFE 1
8
+ #else
9
+ #define IMAGE_PACK_HAS_OFFLOAD_SAFE 0
10
+ #define RB_NOGVL_OFFLOAD_SAFE 0
8
11
  #endif
9
12
 
10
13
  #include <errno.h>
11
14
  #include <setjmp.h>
12
15
  #include <stdatomic.h>
13
16
  #include <stdint.h>
17
+ #include <inttypes.h>
14
18
  #include <limits.h>
19
+ #include <sys/types.h>
15
20
  #include <stdio.h>
16
21
  #include <stdlib.h>
17
22
  #include <string.h>
23
+ #if defined(_WIN32)
24
+ #include <windows.h>
25
+ #endif
18
26
  #include <jpeglib.h>
19
27
  #include <jconfigint.h>
20
28
 
@@ -28,14 +36,14 @@
28
36
  #endif
29
37
  #endif
30
38
 
31
- #ifndef RB_NOGVL_OFFLOAD_SAFE
32
- #error "RB_NOGVL_OFFLOAD_SAFE is required by image_pack"
33
- #endif
34
-
35
39
  #ifndef TRUE
36
40
  #define TRUE 1
37
41
  #endif
38
42
 
43
+ #ifndef IMAGE_PACK_MOZJPEG_VERSION
44
+ #define IMAGE_PACK_MOZJPEG_VERSION VERSION
45
+ #endif
46
+
39
47
  #ifndef FALSE
40
48
  #define FALSE 0
41
49
  #endif
@@ -48,6 +56,32 @@
48
56
  #define IP_RESTRICT
49
57
  #endif
50
58
 
59
+ #if defined(IMAGE_PACK_HAS_SIMD)
60
+ #define IP_FAST_DCT JDCT_ISLOW
61
+ #else
62
+ #define IP_FAST_DCT JDCT_FASTEST
63
+ #endif
64
+
65
+ #define IP_FAIL(ctx, st, msg) \
66
+ do { \
67
+ ip_context_set_error((ctx), (st), (msg)); \
68
+ return 0; \
69
+ } while (0)
70
+
71
+ #define IP_FAIL_NULL(ctx, st, msg) \
72
+ do { \
73
+ ip_context_set_error((ctx), (st), (msg)); \
74
+ return NULL; \
75
+ } while (0)
76
+
77
+ #define IP_FAIL_GOTO(ctx, st, msg) \
78
+ do { \
79
+ ip_context_set_error((ctx), (st), (msg)); \
80
+ goto fail; \
81
+ } while (0)
82
+
83
+ #define IP_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
84
+
51
85
  typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
52
86
 
53
87
  typedef enum {
@@ -87,11 +121,11 @@ typedef struct {
87
121
  int height;
88
122
  int channels;
89
123
  int bit_depth;
124
+ int jpeg_color_space;
90
125
  size_t decoded_bytes;
91
126
 
92
127
  unsigned char *output_data;
93
128
  size_t output_size;
94
- size_t output_capacity;
95
129
  ip_output_owner_t output_owner;
96
130
  char *output_path;
97
131
 
@@ -121,13 +155,13 @@ typedef struct {
121
155
 
122
156
  atomic_int cancelled;
123
157
  int cancellable_requested;
158
+ int strict;
159
+ int warning_count;
160
+ char first_warning[200];
124
161
 
125
162
  jmp_buf jmpbuf;
126
163
  int jmp_armed;
127
164
 
128
- unsigned char *scratch_row;
129
- size_t scratch_row_size;
130
-
131
165
  struct {
132
166
  int marker;
133
167
  unsigned char *data;
@@ -138,7 +172,6 @@ typedef struct {
138
172
 
139
173
  unsigned char *transient_jpeg_buf;
140
174
  unsigned char *transient_decode_buf;
141
- unsigned char *transient_scratch_buf;
142
175
  int source_orientation;
143
176
  } ip_context_t;
144
177
 
@@ -148,6 +181,7 @@ typedef struct {
148
181
  } ip_jpeg_error_mgr;
149
182
 
150
183
  static VALUE rb_mImagePack;
184
+ static int ip_offload_runtime_enabled = 1;
151
185
  static VALUE rb_eImagePackError;
152
186
  static VALUE rb_eImagePackInvalidArgumentError;
153
187
  static VALUE rb_eImagePackInvalidImageError;
@@ -188,6 +222,9 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
188
222
  static void ip_validate_quality_or_raise(ip_context_t *ctx);
189
223
  static void ip_validate_min_ssim_or_raise(ip_context_t *ctx);
190
224
  static int ip_bool_value(VALUE value);
225
+ static int ip_value_negative(VALUE value);
226
+ static char *ip_strdup(const char *source);
227
+ static int ip_replace_file(const char *tmp_path, const char *output_path);
191
228
 
192
229
  static ip_algo_t ip_parse_algo(VALUE sym);
193
230
  static ip_execution_t ip_parse_execution(VALUE sym);
@@ -195,11 +232,15 @@ static ip_input_kind_t ip_parse_input_kind(VALUE sym);
195
232
  static ip_output_kind_t ip_parse_output_kind(VALUE sym);
196
233
 
197
234
  static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
198
- static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels);
235
+ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels,
236
+ int exact_size);
237
+ static int ip_ensure_owned_input_for_async(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
238
+ static int ip_ensure_owned_pixels_for_async(ip_context_t *ctx, VALUE buffer);
199
239
  static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind);
200
240
  static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind);
241
+ static void apply_configuration(VALUE self, ip_context_t *ctx);
201
242
 
202
- static int ip_inspect_jpeg_header(ip_context_t *ctx);
243
+ static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck);
203
244
  static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind);
204
245
 
205
246
  static void ip_resolve_execution(ip_context_t *ctx);
@@ -222,18 +263,20 @@ static int ip_run_optimize_context(ip_context_t *ctx);
222
263
  typedef struct {
223
264
  VALUE self, input, input_kind, output, output_kind, algo, quality, min_ssim;
224
265
  VALUE mozjpeg_trellis, progressive, strip_metadata, execution, cancellable, has_scheduler;
266
+ VALUE report, strict;
225
267
  ip_context_t *ctx;
226
268
  } ip_compress_jpeg_call_t;
227
269
 
228
270
  typedef struct {
229
271
  VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
230
- VALUE progressive, execution, cancellable, has_scheduler;
272
+ VALUE mozjpeg_trellis, progressive, exact_size, execution, cancellable, has_scheduler;
273
+ VALUE report, strict;
231
274
  ip_context_t *ctx;
232
275
  } ip_compress_pixels_call_t;
233
276
 
234
277
  typedef struct {
235
278
  VALUE self, input, input_kind, output, output_kind, progressive, strip_metadata;
236
- VALUE execution, cancellable, has_scheduler;
279
+ VALUE execution, cancellable, has_scheduler, strict;
237
280
  ip_context_t *ctx;
238
281
  } ip_optimize_jpeg_call_t;
239
282
 
@@ -254,14 +297,13 @@ static VALUE ip_call_cleanup(VALUE ptr) {
254
297
  static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
255
298
  VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
256
299
  VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
257
- VALUE execution, VALUE cancellable, VALUE has_scheduler);
258
- static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
259
- VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
260
- VALUE quality, VALUE min_ssim, VALUE progressive,
261
- VALUE execution, VALUE cancellable, VALUE has_scheduler);
300
+ VALUE execution, VALUE cancellable, VALUE has_scheduler,
301
+ VALUE report, VALUE strict);
302
+ static VALUE ip_compress_pixels_entry(int argc, VALUE *argv, VALUE self);
262
303
  static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
263
304
  VALUE output_kind, VALUE progressive, VALUE strip_metadata,
264
- VALUE execution, VALUE cancellable, VALUE has_scheduler);
305
+ VALUE execution, VALUE cancellable, VALUE has_scheduler,
306
+ VALUE strict);
265
307
 
266
308
  static VALUE ip_status_to_exception(ip_status_t status) {
267
309
  switch (status) {
@@ -310,10 +352,6 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
310
352
  return ip_checked_mul_size(pixels, (size_t)channels, out);
311
353
  }
312
354
 
313
- static void *ip_malloc_hot(size_t size) {
314
- return malloc(size);
315
- }
316
-
317
355
  static void ip_validate_quality_or_raise(ip_context_t *ctx) {
318
356
  if (ctx->quality >= 1 && ctx->quality <= 100)
319
357
  return;
@@ -333,6 +371,67 @@ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
333
371
  "min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
334
372
  }
335
373
 
374
+ static int ip_value_negative(VALUE value) {
375
+ return RTEST(rb_funcall(value, rb_intern("<"), 1, INT2FIX(0)));
376
+ }
377
+
378
+ static char *ip_strdup(const char *source) {
379
+ size_t len = strlen(source) + 1;
380
+ char *copy = (char *)malloc(len);
381
+ if (!copy)
382
+ return NULL;
383
+ memcpy(copy, source, len);
384
+ return copy;
385
+ }
386
+
387
+ #if defined(_WIN32)
388
+ static int ip_replace_file(const char *tmp_path, const char *output_path) {
389
+ return MoveFileExA(tmp_path, output_path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)
390
+ ? 0
391
+ : -1;
392
+ }
393
+ #else
394
+ static int ip_replace_file(const char *tmp_path, const char *output_path) {
395
+ return rename(tmp_path, output_path);
396
+ }
397
+ #endif
398
+
399
+ static int ip_file_seek_end(FILE *fp) {
400
+ #if defined(_WIN32)
401
+ return _fseeki64(fp, 0, SEEK_END);
402
+ #else
403
+ return fseeko(fp, 0, SEEK_END);
404
+ #endif
405
+ }
406
+
407
+ static int ip_file_rewind(FILE *fp) {
408
+ #if defined(_WIN32)
409
+ return _fseeki64(fp, 0, SEEK_SET);
410
+ #else
411
+ return fseeko(fp, 0, SEEK_SET);
412
+ #endif
413
+ }
414
+
415
+ static int ip_file_tell(FILE *fp, size_t *out) {
416
+ #if defined(_WIN32)
417
+ __int64 pos = _ftelli64(fp);
418
+ if (pos < 0)
419
+ return 0;
420
+ if ((unsigned long long)pos > (unsigned long long)SIZE_MAX)
421
+ return 0;
422
+ *out = (size_t)pos;
423
+ return 1;
424
+ #else
425
+ off_t pos = ftello(fp);
426
+ if (pos < 0)
427
+ return 0;
428
+ if ((uintmax_t)pos > (uintmax_t)SIZE_MAX)
429
+ return 0;
430
+ *out = (size_t)pos;
431
+ return 1;
432
+ #endif
433
+ }
434
+
336
435
  static int ip_bool_value(VALUE value) {
337
436
  if (NIL_P(value) || value == Qfalse)
338
437
  return 0;
@@ -371,10 +470,8 @@ static void ip_context_free(ip_context_t *ctx) {
371
470
  free(ctx->owned_input_data);
372
471
  free(ctx->owned_pixel_data);
373
472
  free(ctx->output_path);
374
- free(ctx->scratch_row);
375
473
  free(ctx->transient_jpeg_buf);
376
474
  free(ctx->transient_decode_buf);
377
- free(ctx->transient_scratch_buf);
378
475
 
379
476
  if (ctx->preserved_markers) {
380
477
  for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
@@ -407,46 +504,120 @@ static ID symbol_id(VALUE sym, const char *kind) {
407
504
  return SYM2ID(sym);
408
505
  }
409
506
 
507
+ typedef struct {
508
+ const ID *id;
509
+ int value;
510
+ } ip_symbol_entry;
511
+
512
+ static int ip_map_symbol(VALUE sym, const char *kind, const ip_symbol_entry *table) {
513
+ ID id = symbol_id(sym, kind);
514
+ for (; table->id != NULL; table++) {
515
+ if (id == *table->id)
516
+ return table->value;
517
+ }
518
+ rb_raise(rb_eImagePackInvalidArgumentError, "unknown %s", kind);
519
+ }
520
+
410
521
  static ip_algo_t ip_parse_algo(VALUE sym) {
411
- ID id = symbol_id(sym, "algo");
412
- if (id == id_jpeg_turbo)
413
- return IP_ALGO_JPEG_TURBO;
414
- if (id == id_mozjpeg)
415
- return IP_ALGO_MOZJPEG;
416
- rb_raise(rb_eImagePackInvalidArgumentError, "unknown algo");
522
+ static const ip_symbol_entry table[] = {
523
+ {&id_jpeg_turbo, IP_ALGO_JPEG_TURBO}, {&id_mozjpeg, IP_ALGO_MOZJPEG}, {NULL, 0}};
524
+ return (ip_algo_t)ip_map_symbol(sym, "algo", table);
417
525
  }
418
526
 
419
527
  static ip_execution_t ip_parse_execution(VALUE sym) {
420
- ID id = symbol_id(sym, "execution");
421
- if (id == id_direct)
422
- return IP_EXEC_DIRECT;
423
- if (id == id_nogvl)
424
- return IP_EXEC_NOGVL;
425
- if (id == id_offload)
426
- return IP_EXEC_OFFLOAD;
427
- if (id == id_auto)
428
- return IP_EXEC_AUTO;
429
- rb_raise(rb_eImagePackInvalidArgumentError, "unknown execution");
528
+ static const ip_symbol_entry table[] = {{&id_direct, IP_EXEC_DIRECT},
529
+ {&id_nogvl, IP_EXEC_NOGVL},
530
+ {&id_offload, IP_EXEC_OFFLOAD},
531
+ {&id_auto, IP_EXEC_AUTO},
532
+ {NULL, 0}};
533
+ return (ip_execution_t)ip_map_symbol(sym, "execution", table);
430
534
  }
431
535
 
432
536
  static ip_input_kind_t ip_parse_input_kind(VALUE sym) {
433
- ID id = symbol_id(sym, "input kind");
434
- if (id == id_bytes)
435
- return IP_INPUT_BYTES;
436
- if (id == id_path)
437
- return IP_INPUT_PATH;
438
- if (id == id_io_buffer)
439
- return IP_INPUT_IO_BUFFER;
440
- rb_raise(rb_eImagePackInvalidArgumentError, "unknown input kind");
537
+ static const ip_symbol_entry table[] = {{&id_bytes, IP_INPUT_BYTES},
538
+ {&id_path, IP_INPUT_PATH},
539
+ {&id_io_buffer, IP_INPUT_IO_BUFFER},
540
+ {NULL, 0}};
541
+ return (ip_input_kind_t)ip_map_symbol(sym, "input kind", table);
441
542
  }
442
543
 
443
544
  static ip_output_kind_t ip_parse_output_kind(VALUE sym) {
444
- ID id = symbol_id(sym, "output kind");
445
- if (id == id_return_string)
446
- return IP_OUTPUT_RETURN_STRING;
447
- if (id == id_path)
448
- return IP_OUTPUT_PATH;
449
- rb_raise(rb_eImagePackInvalidArgumentError, "unknown output kind");
545
+ static const ip_symbol_entry table[] = {
546
+ {&id_return_string, IP_OUTPUT_RETURN_STRING}, {&id_path, IP_OUTPUT_PATH}, {NULL, 0}};
547
+ return (ip_output_kind_t)ip_map_symbol(sym, "output kind", table);
548
+ }
549
+
550
+ static VALUE ip_sym(const char *name) {
551
+ return ID2SYM(rb_intern(name));
552
+ }
553
+
554
+ static void ip_jpeg_emit_message_collect(j_common_ptr cinfo, int msg_level) {
555
+ if (msg_level >= 0)
556
+ return;
557
+
558
+ ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
559
+ ip_context_t *ctx = err->ctx;
560
+ if (!ctx)
561
+ return;
562
+
563
+ ctx->warning_count++;
564
+ if (ctx->first_warning[0] == '\0') {
565
+ char buffer[JMSG_LENGTH_MAX];
566
+ (*cinfo->err->format_message)(cinfo, buffer);
567
+ snprintf(ctx->first_warning, sizeof(ctx->first_warning), "%s", buffer);
568
+ }
569
+
570
+ if (ctx->strict) {
571
+ if (ctx->status == IP_OK) {
572
+ char buffer[JMSG_LENGTH_MAX];
573
+ (*cinfo->err->format_message)(cinfo, buffer);
574
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, buffer);
575
+ }
576
+ if (ctx->jmp_armed)
577
+ longjmp(ctx->jmpbuf, 1);
578
+ }
579
+ }
580
+
581
+ static void ip_jpeg_output_message_silent(j_common_ptr cinfo) {
582
+ (void)cinfo;
583
+ }
584
+
585
+ static struct jpeg_error_mgr *ip_use_error(ip_jpeg_error_mgr *jerr, ip_context_t *ctx,
586
+ void (*handler)(j_common_ptr)) {
587
+ struct jpeg_error_mgr *base = jpeg_std_error(&jerr->pub);
588
+ jerr->pub.error_exit = handler;
589
+ jerr->pub.emit_message = ip_jpeg_emit_message_collect;
590
+ jerr->pub.output_message = ip_jpeg_output_message_silent;
591
+ jerr->ctx = ctx;
592
+ return base;
593
+ }
594
+
595
+ static void ip_apply_fast_decode(struct jpeg_decompress_struct *cinfo) {
596
+ cinfo->dct_method = IP_FAST_DCT;
597
+ cinfo->do_fancy_upsampling = FALSE;
598
+ cinfo->do_block_smoothing = FALSE;
599
+ cinfo->quantize_colors = FALSE;
600
+ cinfo->two_pass_quantize = FALSE;
601
+ cinfo->dither_mode = JDITHER_NONE;
602
+ }
603
+
604
+ static void ip_disable_mozjpeg_trellis(struct jpeg_compress_struct *cinfo) {
605
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
606
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
607
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
608
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
609
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
610
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
611
+ }
612
+
613
+ static int ip_check_max_dimension_limits(ip_context_t *ctx) {
614
+ if (ctx->max_width > 0 && ctx->width > ctx->max_width)
615
+ IP_FAIL(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
616
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height)
617
+ IP_FAIL(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
618
+ if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels)
619
+ IP_FAIL(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
620
+ return 1;
450
621
  }
451
622
 
452
623
  static VALUE pathname_to_s(VALUE object) {
@@ -464,36 +635,41 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
464
635
  return 0;
465
636
  }
466
637
 
467
- if (fseek(fp, 0, SEEK_END) != 0) {
638
+ if (ip_file_seek_end(fp) != 0) {
468
639
  fclose(fp);
469
640
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to seek input path");
470
641
  return 0;
471
642
  }
472
643
 
473
- long size = ftell(fp);
474
- if (size < 0) {
644
+ size_t size = 0;
645
+ if (!ip_file_tell(fp, &size)) {
475
646
  fclose(fp);
476
647
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to determine input size");
477
648
  return 0;
478
649
  }
479
- rewind(fp);
480
650
 
481
- if (ctx->max_input_size > 0 && (size_t)size > ctx->max_input_size) {
651
+ if (ip_file_rewind(fp) != 0) {
652
+ fclose(fp);
653
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to rewind input path");
654
+ return 0;
655
+ }
656
+
657
+ if (ctx->max_input_size > 0 && size > ctx->max_input_size) {
482
658
  fclose(fp);
483
659
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input file exceeds max_input_size");
484
660
  return 0;
485
661
  }
486
662
 
487
- unsigned char *data = (unsigned char *)malloc((size_t)size);
663
+ unsigned char *data = (unsigned char *)malloc(size);
488
664
  if (!data && size > 0) {
489
665
  fclose(fp);
490
666
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate input file buffer");
491
667
  return 0;
492
668
  }
493
669
 
494
- size_t read_size = fread(data, 1, (size_t)size, fp);
670
+ size_t read_size = fread(data, 1, size, fp);
495
671
  fclose(fp);
496
- if (read_size != (size_t)size) {
672
+ if (read_size != size) {
497
673
  free(data);
498
674
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to read input path");
499
675
  return 0;
@@ -505,9 +681,36 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
505
681
  return 1;
506
682
  }
507
683
 
508
- static VALUE io_buffer_to_string(VALUE buffer) {
509
- VALUE size = rb_funcall(buffer, rb_intern("size"), 0);
510
- return rb_funcall(buffer, rb_intern("get_string"), 2, LONG2NUM(0), size);
684
+ static size_t io_buffer_size_or_raise(VALUE buffer) {
685
+ VALUE size_value = rb_funcall(buffer, rb_intern("size"), 0);
686
+ if (!RB_INTEGER_TYPE_P(size_value) || ip_value_negative(size_value))
687
+ rb_raise(rb_eImagePackInvalidArgumentError, "IO::Buffer#size must return Integer >= 0");
688
+ return NUM2SIZET(size_value);
689
+ }
690
+
691
+ static VALUE io_buffer_to_string_slice(VALUE buffer, size_t len) {
692
+ return rb_funcall(buffer, rb_intern("get_string"), 2, LONG2NUM(0), SIZET2NUM(len));
693
+ }
694
+
695
+ static int ip_copy_string_to_owned_input(ip_context_t *ctx, VALUE input) {
696
+ Check_Type(input, T_STRING);
697
+ size_t len = (size_t)RSTRING_LEN(input);
698
+ if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
699
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
700
+ return 0;
701
+ }
702
+
703
+ unsigned char *copy = (unsigned char *)malloc(len);
704
+ if (!copy && len > 0) {
705
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
706
+ return 0;
707
+ }
708
+ if (len > 0)
709
+ memcpy(copy, RSTRING_PTR(input), len);
710
+ ctx->owned_input_data = copy;
711
+ ctx->input_data = copy;
712
+ ctx->input_size = len;
713
+ return 1;
511
714
  }
512
715
 
513
716
  static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
@@ -518,33 +721,32 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
518
721
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
519
722
  return 0;
520
723
  }
521
- if (ctx->requested_execution == IP_EXEC_DIRECT) {
724
+
725
+ if (ctx->requested_execution == IP_EXEC_DIRECT ||
726
+ ctx->requested_execution == IP_EXEC_AUTO) {
522
727
  ctx->input_data = (const unsigned char *)RSTRING_PTR(input);
523
728
  ctx->input_size = len;
524
729
  return 1;
525
730
  }
526
731
 
527
- unsigned char *copy = (unsigned char *)malloc(len);
528
- if (!copy && len > 0) {
529
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
530
- return 0;
531
- }
532
- if (len > 0)
533
- memcpy(copy, RSTRING_PTR(input), len);
534
- ctx->owned_input_data = copy;
535
- ctx->input_data = copy;
536
- ctx->input_size = len;
537
- return 1;
732
+ return ip_copy_string_to_owned_input(ctx, input);
538
733
  }
539
734
 
540
735
  if (kind == IP_INPUT_IO_BUFFER) {
541
- VALUE str = io_buffer_to_string(input);
542
- StringValue(str);
543
- size_t len = (size_t)RSTRING_LEN(str);
736
+ size_t len = io_buffer_size_or_raise(input);
544
737
  if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
545
738
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input IO::Buffer exceeds max_input_size");
546
739
  return 0;
547
740
  }
741
+
742
+ VALUE str = io_buffer_to_string_slice(input, len);
743
+ StringValue(str);
744
+ if ((size_t)RSTRING_LEN(str) != len) {
745
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
746
+ "IO::Buffer#get_string returned unexpected size");
747
+ return 0;
748
+ }
749
+
548
750
  unsigned char *copy = (unsigned char *)malloc(len);
549
751
  if (!copy && len > 0) {
550
752
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy IO::Buffer input");
@@ -563,15 +765,31 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
563
765
  return read_file_to_owned_buffer(ctx, StringValueCStr(path_value));
564
766
  }
565
767
 
566
- static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels) {
567
- VALUE str;
568
- if (RB_TYPE_P(buffer, T_STRING)) {
569
- str = buffer;
570
- } else {
571
- str = io_buffer_to_string(buffer);
768
+ static int ip_ensure_owned_input_for_async(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
769
+ if (ctx->resolved_execution == IP_EXEC_DIRECT)
770
+ return 1;
771
+ if (kind != IP_INPUT_BYTES || ctx->owned_input_data)
772
+ return 1;
773
+ return ip_copy_string_to_owned_input(ctx, input);
774
+ }
775
+
776
+ static int ip_copy_string_to_owned_pixels(ip_context_t *ctx, VALUE str, size_t expected) {
777
+ unsigned char *copy = (unsigned char *)malloc(expected);
778
+ if (!copy && expected > 0) {
779
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
780
+ return 0;
572
781
  }
573
782
 
574
- StringValue(str);
783
+ if (expected > 0)
784
+ memcpy(copy, RSTRING_PTR(str), expected);
785
+ ctx->owned_pixel_data = copy;
786
+ ctx->pixel_data = copy;
787
+ ctx->pixel_size = expected;
788
+ return 1;
789
+ }
790
+
791
+ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels,
792
+ int exact_size) {
575
793
  size_t expected = 0;
576
794
  if (!ip_checked_image_size(width, height, channels, &expected)) {
577
795
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
@@ -579,34 +797,49 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
579
797
  return 0;
580
798
  }
581
799
 
582
- if ((size_t)RSTRING_LEN(str) < expected) {
800
+ VALUE str = Qnil;
801
+ size_t actual = 0;
802
+ int buffer_is_string = RB_TYPE_P(buffer, T_STRING);
803
+
804
+ if (buffer_is_string) {
805
+ str = buffer;
806
+ actual = (size_t)RSTRING_LEN(str);
807
+ } else {
808
+ actual = io_buffer_size_or_raise(buffer);
809
+ }
810
+
811
+ if (actual < expected) {
583
812
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
584
813
  "pixel buffer is smaller than width * height * channels");
585
814
  return 0;
586
815
  }
587
816
 
588
- if (ctx->requested_execution == IP_EXEC_DIRECT && RB_TYPE_P(buffer, T_STRING)) {
589
- ctx->pixel_data = (const unsigned char *)RSTRING_PTR(str);
590
- ctx->pixel_size = expected;
591
- ctx->width = width;
592
- ctx->height = height;
593
- ctx->channels = channels;
594
- ctx->bit_depth = 8;
595
- ctx->decoded_bytes = expected;
596
- return 1;
817
+ if (exact_size && actual != expected) {
818
+ ip_context_set_error(
819
+ ctx, IP_ERR_INVALID_ARGUMENT,
820
+ "pixel buffer size must equal width * height * channels when exact_size is true");
821
+ return 0;
597
822
  }
598
823
 
599
- unsigned char *copy = (unsigned char *)ip_malloc_hot(expected);
600
- if (!copy && expected > 0) {
601
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
602
- return 0;
824
+ if (!buffer_is_string) {
825
+ str = io_buffer_to_string_slice(buffer, expected);
826
+ StringValue(str);
827
+ if ((size_t)RSTRING_LEN(str) != expected) {
828
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
829
+ "IO::Buffer#get_string returned unexpected pixel size");
830
+ return 0;
831
+ }
832
+ }
833
+
834
+ if ((ctx->requested_execution == IP_EXEC_DIRECT || ctx->requested_execution == IP_EXEC_AUTO) &&
835
+ buffer_is_string) {
836
+ ctx->pixel_data = (const unsigned char *)RSTRING_PTR(str);
837
+ ctx->pixel_size = expected;
838
+ } else {
839
+ if (!ip_copy_string_to_owned_pixels(ctx, str, expected))
840
+ return 0;
603
841
  }
604
842
 
605
- if (expected > 0)
606
- memcpy(copy, RSTRING_PTR(str), expected);
607
- ctx->owned_pixel_data = copy;
608
- ctx->pixel_data = copy;
609
- ctx->pixel_size = expected;
610
843
  ctx->width = width;
611
844
  ctx->height = height;
612
845
  ctx->channels = channels;
@@ -615,6 +848,14 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
615
848
  return 1;
616
849
  }
617
850
 
851
+ static int ip_ensure_owned_pixels_for_async(ip_context_t *ctx, VALUE buffer) {
852
+ if (ctx->resolved_execution == IP_EXEC_DIRECT)
853
+ return 1;
854
+ if (!RB_TYPE_P(buffer, T_STRING) || ctx->owned_pixel_data)
855
+ return 1;
856
+ return ip_copy_string_to_owned_pixels(ctx, buffer, ctx->pixel_size);
857
+ }
858
+
618
859
  static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind) {
619
860
  if (kind == IP_OUTPUT_RETURN_STRING)
620
861
  return 1;
@@ -622,7 +863,7 @@ static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kin
622
863
  VALUE path_value = pathname_to_s(output);
623
864
  StringValue(path_value);
624
865
  const char *path = StringValueCStr(path_value);
625
- ctx->output_path = strdup(path);
866
+ ctx->output_path = ip_strdup(path);
626
867
  if (!ctx->output_path) {
627
868
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy output path");
628
869
  return 0;
@@ -666,7 +907,7 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
666
907
  rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
667
908
  }
668
909
 
669
- if (rename(tmp_path, ctx->output_path) != 0) {
910
+ if (ip_replace_file(tmp_path, ctx->output_path) != 0) {
670
911
  remove(tmp_path);
671
912
  free(tmp_path);
672
913
  rb_raise(rb_eImagePackEncodeError, "failed to move temporary JPEG output into place");
@@ -676,11 +917,32 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
676
917
  return Qtrue;
677
918
  }
678
919
 
920
+ static VALUE ip_build_report(ip_context_t *ctx, VALUE output_value) {
921
+ VALUE hash = rb_hash_new();
922
+ rb_hash_aset(hash, ip_sym("output"), output_value);
923
+ rb_hash_aset(hash, ip_sym("quality"), INT2NUM(ctx->selected_quality));
924
+ rb_hash_aset(hash, ip_sym("ssim"),
925
+ ctx->ssim_guard_enabled ? DBL2NUM(ctx->measured_ssim) : Qnil);
926
+ rb_hash_aset(hash, ip_sym("algo"),
927
+ ctx->algo == IP_ALGO_MOZJPEG ? ip_sym("mozjpeg") : ip_sym("jpeg_turbo"));
928
+ rb_hash_aset(hash, ip_sym("bytesize"), SIZET2NUM(ctx->output_size));
929
+ rb_hash_aset(hash, ip_sym("input_bytesize"),
930
+ SIZET2NUM(ctx->input_size > 0 ? ctx->input_size : ctx->pixel_size));
931
+ rb_hash_aset(hash, ip_sym("warning_count"), INT2NUM(ctx->warning_count));
932
+ rb_hash_aset(hash, ip_sym("warning"),
933
+ ctx->first_warning[0] ? rb_str_new_cstr(ctx->first_warning) : Qnil);
934
+ return hash;
935
+ }
936
+
679
937
  static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
680
938
  unsigned int len) {
681
939
  if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
940
+ if (ctx->preserved_marker_capacity > SIZE_MAX / 2)
941
+ return 0;
682
942
  size_t new_cap =
683
943
  ctx->preserved_marker_capacity == 0 ? 4 : ctx->preserved_marker_capacity * 2;
944
+ if (new_cap > SIZE_MAX / sizeof(*ctx->preserved_markers))
945
+ return 0;
684
946
  void *new_buf = realloc(ctx->preserved_markers, new_cap * sizeof(*ctx->preserved_markers));
685
947
  if (!new_buf)
686
948
  return 0;
@@ -812,7 +1074,7 @@ static int ip_transform_pixels_for_orientation(ip_context_t *ctx, unsigned char
812
1074
  }
813
1075
 
814
1076
  unsigned char *src = *pixels;
815
- unsigned char *dst = (unsigned char *)ip_malloc_hot(out_size);
1077
+ unsigned char *dst = (unsigned char *)malloc(out_size);
816
1078
  if (!dst && out_size > 0) {
817
1079
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate EXIF-oriented pixel buffer");
818
1080
  return 0;
@@ -894,82 +1156,71 @@ static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
894
1156
  longjmp(err->ctx->jmpbuf, 1);
895
1157
  }
896
1158
 
897
- static int ip_inspect_jpeg_header(ip_context_t *ctx) {
898
- if (!ctx->input_data || ctx->input_size < 2 || ctx->input_data[0] != 0xFF ||
899
- ctx->input_data[1] != 0xD8) {
900
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "input is not a JPEG image");
901
- return 0;
1159
+ static const char *ip_jpeg_color_space_name(int color_space) {
1160
+ switch ((J_COLOR_SPACE)color_space) {
1161
+ case JCS_GRAYSCALE:
1162
+ return "grayscale";
1163
+ case JCS_RGB:
1164
+ return "rgb";
1165
+ case JCS_YCbCr:
1166
+ return "ycbcr";
1167
+ case JCS_CMYK:
1168
+ return "cmyk";
1169
+ case JCS_YCCK:
1170
+ return "ycck";
1171
+ default:
1172
+ return "unknown";
902
1173
  }
1174
+ }
1175
+
1176
+ static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck) {
1177
+ if (!ctx->input_data || ctx->input_size < 2 || ctx->input_data[0] != 0xFF ||
1178
+ ctx->input_data[1] != 0xD8)
1179
+ IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "input is not a JPEG image");
903
1180
 
904
1181
  struct jpeg_decompress_struct cinfo;
905
1182
  ip_jpeg_error_mgr jerr;
906
1183
  memset(&cinfo, 0, sizeof(cinfo));
907
1184
  memset(&jerr, 0, sizeof(jerr));
908
-
909
- cinfo.err = jpeg_std_error(&jerr.pub);
910
- jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
911
- jerr.ctx = ctx;
1185
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
912
1186
 
913
1187
  ctx->jmp_armed = 1;
914
- if (setjmp(ctx->jmpbuf)) {
915
- ctx->jmp_armed = 0;
916
- jpeg_destroy_decompress(&cinfo);
917
- return 0;
918
- }
1188
+ if (setjmp(ctx->jmpbuf))
1189
+ goto fail;
919
1190
 
920
1191
  jpeg_create_decompress(&cinfo);
921
1192
  jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
922
- int rc = jpeg_read_header(&cinfo, TRUE);
923
- if (rc != JPEG_HEADER_OK) {
924
- jpeg_destroy_decompress(&cinfo);
925
- ctx->jmp_armed = 0;
926
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
927
- return 0;
928
- }
1193
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1194
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
929
1195
 
930
- if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
931
- cinfo.jpeg_color_space == JCS_YCCK) {
932
- jpeg_destroy_decompress(&cinfo);
933
- ctx->jmp_armed = 0;
934
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
935
- "CMYK/YCCK JPEG input is not supported in this release");
936
- return 0;
937
- }
1196
+ if (!allow_cmyk_ycck && (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1197
+ cinfo.jpeg_color_space == JCS_YCCK))
1198
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1199
+ "CMYK/YCCK JPEG input is not supported for pixel recompression");
938
1200
 
939
1201
  ctx->width = (int)cinfo.image_width;
940
1202
  ctx->height = (int)cinfo.image_height;
941
1203
  ctx->channels = cinfo.num_components;
942
- if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4) {
943
- jpeg_destroy_decompress(&cinfo);
944
- ctx->jmp_armed = 0;
945
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED, "JPEG component count is not supported");
946
- return 0;
947
- }
948
- ctx->bit_depth = 8;
949
- ctx->decoded_bytes = (size_t)ctx->width * (size_t)ctx->height * (size_t)ctx->channels;
1204
+ ctx->jpeg_color_space = (int)cinfo.jpeg_color_space;
950
1205
 
951
1206
  jpeg_destroy_decompress(&cinfo);
952
1207
  ctx->jmp_armed = 0;
953
1208
 
954
- if (ctx->width <= 0 || ctx->height <= 0) {
955
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG dimensions");
956
- return 0;
957
- }
1209
+ if (ctx->width <= 0 || ctx->height <= 0)
1210
+ IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG dimensions");
1211
+ if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4)
1212
+ IP_FAIL(ctx, IP_ERR_UNSUPPORTED, "JPEG component count is not supported");
958
1213
 
959
- if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
960
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
961
- return 0;
962
- }
963
- if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
964
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
965
- return 0;
966
- }
967
- if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
968
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
969
- return 0;
970
- }
1214
+ ctx->bit_depth = 8;
1215
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
1216
+ IP_FAIL(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
971
1217
 
972
- return 1;
1218
+ return ip_check_max_dimension_limits(ctx);
1219
+
1220
+ fail:
1221
+ jpeg_destroy_decompress(&cinfo);
1222
+ ctx->jmp_armed = 0;
1223
+ return 0;
973
1224
  }
974
1225
 
975
1226
  static VALUE ip_inspect_image_entry_body(VALUE ptr) {
@@ -979,8 +1230,10 @@ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
979
1230
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
980
1231
  call->ctx = ctx;
981
1232
 
1233
+ apply_configuration(call->self, ctx);
1234
+
982
1235
  if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
983
- !ip_inspect_jpeg_header(ctx)) {
1236
+ !ip_inspect_jpeg_header(ctx, 1)) {
984
1237
  ip_raise_for_status(ctx);
985
1238
  rb_raise(rb_eImagePackInvalidImageError, "failed to inspect JPEG image");
986
1239
  }
@@ -989,18 +1242,20 @@ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
989
1242
  int height = ctx->height;
990
1243
  int channels = ctx->channels;
991
1244
  int bit_depth = ctx->bit_depth;
1245
+ int color_space = ctx->jpeg_color_space;
992
1246
  size_t decoded_bytes = ctx->decoded_bytes;
993
1247
 
994
1248
  ip_context_free(ctx);
995
1249
  call->ctx = NULL;
996
1250
 
997
1251
  VALUE hash = rb_hash_new();
998
- rb_hash_aset(hash, ID2SYM(rb_intern("format")), ID2SYM(rb_intern("jpeg")));
999
- rb_hash_aset(hash, ID2SYM(rb_intern("width")), INT2NUM(width));
1000
- rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(height));
1001
- rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(channels));
1002
- rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(bit_depth));
1003
- rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(decoded_bytes));
1252
+ rb_hash_aset(hash, ip_sym("format"), ip_sym("jpeg"));
1253
+ rb_hash_aset(hash, ip_sym("width"), INT2NUM(width));
1254
+ rb_hash_aset(hash, ip_sym("height"), INT2NUM(height));
1255
+ rb_hash_aset(hash, ip_sym("channels"), INT2NUM(channels));
1256
+ rb_hash_aset(hash, ip_sym("bit_depth"), INT2NUM(bit_depth));
1257
+ rb_hash_aset(hash, ip_sym("color_space"), ip_sym(ip_jpeg_color_space_name(color_space)));
1258
+ rb_hash_aset(hash, ip_sym("decoded_bytes"), SIZET2NUM(decoded_bytes));
1004
1259
  return hash;
1005
1260
  }
1006
1261
 
@@ -1035,32 +1290,17 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
1035
1290
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1036
1291
  }
1037
1292
 
1038
- if (!mozjpeg_trellis_enabled) {
1039
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
1040
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
1041
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
1042
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
1043
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
1044
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
1045
- }
1293
+ if (!mozjpeg_trellis_enabled)
1294
+ ip_disable_mozjpeg_trellis(cinfo);
1046
1295
 
1047
1296
  return;
1048
1297
  }
1049
1298
 
1050
1299
  cinfo->optimize_coding = FALSE;
1051
- #if defined(IMAGE_PACK_HAS_SIMD)
1052
- cinfo->dct_method = JDCT_ISLOW;
1053
- #else
1054
- cinfo->dct_method = JDCT_FASTEST;
1055
- #endif
1300
+ cinfo->dct_method = IP_FAST_DCT;
1056
1301
  cinfo->smoothing_factor = 0;
1057
1302
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1058
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
1059
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
1060
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
1061
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
1062
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
1063
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
1303
+ ip_disable_mozjpeg_trellis(cinfo);
1064
1304
 
1065
1305
  if (progressive_requested) {
1066
1306
  jpeg_simple_progression(cinfo);
@@ -1068,73 +1308,20 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
1068
1308
  }
1069
1309
  }
1070
1310
 
1071
- static int prepare_encode_rows(ip_context_t *ctx, JDIMENSION start, JDIMENSION batch,
1072
- JSAMPROW *rows) {
1073
- if (ctx->channels != 4) {
1074
- for (JDIMENSION i = 0; i < batch; i++) {
1075
- JDIMENSION y = start + i;
1076
- rows[i] = (JSAMPROW)(ctx->pixel_data +
1077
- ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
1078
- }
1079
- return 1;
1080
- }
1081
-
1082
- size_t rgb_row_size = 0;
1083
- size_t scratch_size = 0;
1084
- if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size) ||
1085
- !ip_checked_mul_size(rgb_row_size, (size_t)batch, &scratch_size)) {
1086
- ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
1087
- return 0;
1088
- }
1089
-
1090
- if (ctx->scratch_row_size < scratch_size) {
1091
- unsigned char *new_rows = (unsigned char *)realloc(ctx->scratch_row, scratch_size);
1092
- if (!new_rows) {
1093
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch rows");
1094
- return 0;
1095
- }
1096
- ctx->scratch_row = new_rows;
1097
- ctx->scratch_row_size = scratch_size;
1098
- }
1099
-
1100
- for (JDIMENSION i = 0; i < batch; i++) {
1101
- JDIMENSION y = start + i;
1102
- const unsigned char *IP_RESTRICT src =
1103
- ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
1104
- unsigned char *IP_RESTRICT dst = ctx->scratch_row + ((size_t)i * rgb_row_size);
1105
- const int w = ctx->width;
1106
- for (int x = 0; x < w; x++) {
1107
- dst[x * 3 + 0] = src[x * 4 + 0];
1108
- dst[x * 3 + 1] = src[x * 4 + 1];
1109
- dst[x * 3 + 2] = src[x * 4 + 2];
1110
- }
1111
- rows[i] = dst;
1112
- }
1113
-
1114
- return 1;
1115
- }
1116
-
1117
1311
  static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
1118
1312
  struct jpeg_compress_struct cinfo;
1119
1313
  ip_jpeg_error_mgr jerr;
1120
1314
  unsigned long jpeg_size = 0;
1121
1315
  memset(&cinfo, 0, sizeof(cinfo));
1122
1316
  memset(&jerr, 0, sizeof(jerr));
1123
-
1124
- cinfo.err = jpeg_std_error(&jerr.pub);
1125
- jerr.pub.error_exit = ip_jpeg_encode_error_exit;
1126
- jerr.ctx = ctx;
1317
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_encode_error_exit);
1127
1318
  ctx->transient_jpeg_buf = NULL;
1128
1319
 
1129
1320
  ctx->jmp_armed = 1;
1130
1321
  if (setjmp(ctx->jmpbuf)) {
1131
- ctx->jmp_armed = 0;
1132
- jpeg_destroy_compress(&cinfo);
1133
- free(ctx->transient_jpeg_buf);
1134
- ctx->transient_jpeg_buf = NULL;
1135
1322
  if (ctx->status == IP_OK)
1136
1323
  ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
1137
- return 0;
1324
+ goto fail;
1138
1325
  }
1139
1326
 
1140
1327
  jpeg_create_compress(&cinfo);
@@ -1142,8 +1329,9 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
1142
1329
 
1143
1330
  cinfo.image_width = (JDIMENSION)ctx->width;
1144
1331
  cinfo.image_height = (JDIMENSION)ctx->height;
1145
- cinfo.input_components = ctx->channels == 4 ? 3 : ctx->channels;
1146
- cinfo.in_color_space = color_space_for_channels(cinfo.input_components);
1332
+ cinfo.input_components = ctx->channels;
1333
+ cinfo.in_color_space =
1334
+ ctx->channels == 4 ? JCS_EXT_RGBA : color_space_for_channels(ctx->channels);
1147
1335
 
1148
1336
  configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
1149
1337
  jpeg_set_defaults(&cinfo);
@@ -1153,20 +1341,12 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
1153
1341
 
1154
1342
  jpeg_start_compress(&cinfo, TRUE);
1155
1343
 
1156
- if (!ctx->strip_metadata) {
1344
+ if (!ctx->strip_metadata)
1157
1345
  ip_write_preserved_markers(ctx, &cinfo);
1158
- }
1159
1346
 
1160
1347
  while (cinfo.next_scanline < cinfo.image_height) {
1161
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1162
- ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
1163
- jpeg_abort_compress(&cinfo);
1164
- jpeg_destroy_compress(&cinfo);
1165
- free(ctx->transient_jpeg_buf);
1166
- ctx->transient_jpeg_buf = NULL;
1167
- ctx->jmp_armed = 0;
1168
- return 0;
1169
- }
1348
+ if (atomic_load(&ctx->cancelled))
1349
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
1170
1350
 
1171
1351
  JSAMPROW rows[16];
1172
1352
  JDIMENSION start_scanline = cinfo.next_scanline;
@@ -1174,35 +1354,33 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
1174
1354
  if (batch > 16)
1175
1355
  batch = 16;
1176
1356
 
1177
- if (!prepare_encode_rows(ctx, start_scanline, batch, rows)) {
1178
- jpeg_abort_compress(&cinfo);
1179
- jpeg_destroy_compress(&cinfo);
1180
- free(ctx->transient_jpeg_buf);
1181
- ctx->transient_jpeg_buf = NULL;
1182
- ctx->jmp_armed = 0;
1183
- return 0;
1184
- }
1357
+ for (JDIMENSION i = 0; i < batch; i++)
1358
+ rows[i] = (JSAMPROW)(ctx->pixel_data + ((size_t)(start_scanline + i) *
1359
+ (size_t)ctx->width * (size_t)ctx->channels));
1185
1360
 
1186
1361
  jpeg_write_scanlines(&cinfo, rows, batch);
1187
1362
  }
1188
1363
 
1189
1364
  jpeg_finish_compress(&cinfo);
1365
+
1366
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
1367
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
1368
+
1190
1369
  jpeg_destroy_compress(&cinfo);
1191
1370
  ctx->jmp_armed = 0;
1192
1371
 
1193
- if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size) {
1194
- free(ctx->transient_jpeg_buf);
1195
- ctx->transient_jpeg_buf = NULL;
1196
- ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
1197
- return 0;
1198
- }
1199
-
1200
1372
  ctx->output_data = ctx->transient_jpeg_buf;
1201
1373
  ctx->transient_jpeg_buf = NULL;
1202
1374
  ctx->output_size = (size_t)jpeg_size;
1203
- ctx->output_capacity = (size_t)jpeg_size;
1204
1375
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1205
1376
  return 1;
1377
+
1378
+ fail:
1379
+ jpeg_destroy_compress(&cinfo);
1380
+ free(ctx->transient_jpeg_buf);
1381
+ ctx->transient_jpeg_buf = NULL;
1382
+ ctx->jmp_armed = 0;
1383
+ return 0;
1206
1384
  }
1207
1385
 
1208
1386
  static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
@@ -1211,22 +1389,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1211
1389
  ip_jpeg_error_mgr jerr;
1212
1390
  memset(&cinfo, 0, sizeof(cinfo));
1213
1391
  memset(&jerr, 0, sizeof(jerr));
1214
-
1215
- cinfo.err = jpeg_std_error(&jerr.pub);
1216
- jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1217
- jerr.ctx = ctx;
1392
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1218
1393
  ctx->transient_decode_buf = NULL;
1219
1394
  ctx->source_orientation = 1;
1220
1395
 
1221
1396
  ctx->jmp_armed = 1;
1222
1397
  if (setjmp(ctx->jmpbuf)) {
1223
- ctx->jmp_armed = 0;
1224
- jpeg_destroy_decompress(&cinfo);
1225
- free(ctx->transient_decode_buf);
1226
- ctx->transient_decode_buf = NULL;
1227
1398
  if (ctx->status == IP_OK)
1228
1399
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
1229
- return 0;
1400
+ goto fail;
1230
1401
  }
1231
1402
 
1232
1403
  jpeg_create_decompress(&cinfo);
@@ -1235,118 +1406,67 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1235
1406
  jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
1236
1407
  if (!ctx->strip_metadata) {
1237
1408
  jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
1238
- for (int app = 2; app < 16; app++) {
1409
+ for (int app = 2; app < 16; app++)
1239
1410
  jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
1240
- }
1241
1411
  }
1242
1412
 
1243
- int rc = jpeg_read_header(&cinfo, TRUE);
1244
- if (rc != JPEG_HEADER_OK) {
1245
- jpeg_destroy_decompress(&cinfo);
1246
- ctx->jmp_armed = 0;
1247
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1248
- return 0;
1249
- }
1413
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1414
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1250
1415
 
1251
1416
  ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
1252
1417
 
1253
- if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
1254
- jpeg_destroy_decompress(&cinfo);
1255
- ctx->jmp_armed = 0;
1256
- ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1257
- return 0;
1258
- }
1418
+ if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
1419
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1259
1420
 
1260
1421
  if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1261
- cinfo.jpeg_color_space == JCS_YCCK) {
1262
- jpeg_destroy_decompress(&cinfo);
1263
- ctx->jmp_armed = 0;
1264
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1265
- "CMYK/YCCK JPEG input is not supported in this release");
1266
- return 0;
1267
- }
1422
+ cinfo.jpeg_color_space == JCS_YCCK)
1423
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1424
+ "CMYK/YCCK JPEG input is not supported in this release");
1268
1425
 
1269
1426
  int ch = cinfo.num_components == 1 ? 1 : 3;
1270
1427
 
1271
1428
  ctx->width = (int)cinfo.image_width;
1272
1429
  ctx->height = (int)cinfo.image_height;
1273
1430
  ctx->channels = ch;
1274
- if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes)) {
1275
- jpeg_destroy_decompress(&cinfo);
1276
- ctx->jmp_armed = 0;
1277
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1278
- return 0;
1279
- }
1431
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
1432
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1433
+
1280
1434
  validate_limits_for_pixels(ctx);
1281
- if (ctx->status != IP_OK) {
1282
- jpeg_destroy_decompress(&cinfo);
1283
- ctx->jmp_armed = 0;
1284
- return 0;
1285
- }
1435
+ if (ctx->status != IP_OK)
1436
+ goto fail;
1286
1437
 
1287
1438
  cinfo.out_color_space = ch == 1 ? JCS_GRAYSCALE : JCS_RGB;
1288
-
1289
- if (fast_decode_mode) {
1290
- #if defined(IMAGE_PACK_HAS_SIMD)
1291
- cinfo.dct_method = JDCT_ISLOW;
1292
- #else
1293
- cinfo.dct_method = JDCT_FASTEST;
1294
- #endif
1295
- cinfo.do_fancy_upsampling = FALSE;
1296
- cinfo.do_block_smoothing = FALSE;
1297
- cinfo.quantize_colors = FALSE;
1298
- cinfo.two_pass_quantize = FALSE;
1299
- cinfo.dither_mode = JDITHER_NONE;
1300
- }
1439
+ if (fast_decode_mode)
1440
+ ip_apply_fast_decode(&cinfo);
1301
1441
 
1302
1442
  jpeg_start_decompress(&cinfo);
1303
1443
 
1304
- if (!ctx->strip_metadata) {
1305
- if (!ip_save_markers_from_decompress(ctx, &cinfo)) {
1306
- jpeg_destroy_decompress(&cinfo);
1307
- ctx->jmp_armed = 0;
1308
- return 0;
1309
- }
1310
- }
1444
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &cinfo))
1445
+ goto fail;
1311
1446
 
1312
1447
  size_t row_stride = 0;
1313
1448
  size_t size = 0;
1314
1449
  if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
1315
1450
  &row_stride) ||
1316
- !ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size)) {
1317
- jpeg_destroy_decompress(&cinfo);
1318
- ctx->jmp_armed = 0;
1319
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
1320
- return 0;
1321
- }
1322
- ctx->transient_decode_buf = (unsigned char *)ip_malloc_hot(size);
1323
- if (!ctx->transient_decode_buf && size > 0) {
1324
- jpeg_destroy_decompress(&cinfo);
1325
- ctx->jmp_armed = 0;
1326
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
1327
- return 0;
1328
- }
1451
+ !ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size))
1452
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
1453
+
1454
+ ctx->transient_decode_buf = (unsigned char *)malloc(size);
1455
+ if (!ctx->transient_decode_buf && size > 0)
1456
+ IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
1329
1457
 
1330
1458
  while (cinfo.output_scanline < cinfo.output_height) {
1331
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1332
- ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
1333
- jpeg_abort_decompress(&cinfo);
1334
- jpeg_destroy_decompress(&cinfo);
1335
- free(ctx->transient_decode_buf);
1336
- ctx->transient_decode_buf = NULL;
1337
- ctx->jmp_armed = 0;
1338
- return 0;
1339
- }
1459
+ if (atomic_load(&ctx->cancelled))
1460
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
1340
1461
 
1341
1462
  JSAMPROW rows[16];
1342
1463
  JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1343
1464
  if (batch > 16)
1344
1465
  batch = 16;
1345
1466
 
1346
- for (JDIMENSION i = 0; i < batch; i++) {
1467
+ for (JDIMENSION i = 0; i < batch; i++)
1347
1468
  rows[i] =
1348
1469
  ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1349
- }
1350
1470
 
1351
1471
  jpeg_read_scanlines(&cinfo, rows, batch);
1352
1472
  }
@@ -1366,6 +1486,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1366
1486
  free(buf);
1367
1487
  return 0;
1368
1488
  }
1489
+
1490
+ ctx->width = out_width;
1491
+ ctx->height = out_height;
1492
+ ctx->channels = ch;
1493
+ validate_limits_for_pixels(ctx);
1494
+ if (ctx->status != IP_OK) {
1495
+ free(buf);
1496
+ return 0;
1497
+ }
1369
1498
  }
1370
1499
 
1371
1500
  *pixels = buf;
@@ -1373,6 +1502,13 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1373
1502
  *height = out_height;
1374
1503
  *channels = ch;
1375
1504
  return 1;
1505
+
1506
+ fail:
1507
+ jpeg_destroy_decompress(&cinfo);
1508
+ free(ctx->transient_decode_buf);
1509
+ ctx->transient_decode_buf = NULL;
1510
+ ctx->jmp_armed = 0;
1511
+ return 0;
1376
1512
  }
1377
1513
 
1378
1514
  static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
@@ -1407,7 +1543,6 @@ static void ip_clear_output_buffer(ip_context_t *ctx) {
1407
1543
 
1408
1544
  ctx->output_data = NULL;
1409
1545
  ctx->output_size = 0;
1410
- ctx->output_capacity = 0;
1411
1546
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1412
1547
  }
1413
1548
 
@@ -1417,49 +1552,29 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1417
1552
  ip_jpeg_error_mgr jerr;
1418
1553
  memset(&cinfo, 0, sizeof(cinfo));
1419
1554
  memset(&jerr, 0, sizeof(jerr));
1420
-
1421
- cinfo.err = jpeg_std_error(&jerr.pub);
1422
- jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1423
- jerr.ctx = ctx;
1555
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1424
1556
  ctx->transient_decode_buf = NULL;
1425
1557
 
1426
1558
  ctx->jmp_armed = 1;
1427
1559
  if (setjmp(ctx->jmpbuf)) {
1428
- ctx->jmp_armed = 0;
1429
- jpeg_destroy_decompress(&cinfo);
1430
- free(ctx->transient_decode_buf);
1431
- ctx->transient_decode_buf = NULL;
1432
1560
  if (ctx->status == IP_OK)
1433
1561
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG luma decode failed");
1434
- return 0;
1562
+ goto fail;
1435
1563
  }
1436
1564
 
1437
1565
  jpeg_create_decompress(&cinfo);
1438
1566
  jpeg_mem_src(&cinfo, data, (unsigned long)size);
1439
1567
 
1440
- int rc = jpeg_read_header(&cinfo, TRUE);
1441
- if (rc != JPEG_HEADER_OK) {
1442
- jpeg_destroy_decompress(&cinfo);
1443
- ctx->jmp_armed = 0;
1444
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1445
- return 0;
1446
- }
1568
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1569
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1447
1570
 
1448
- if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
1449
- jpeg_destroy_decompress(&cinfo);
1450
- ctx->jmp_armed = 0;
1451
- ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1452
- return 0;
1453
- }
1571
+ if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
1572
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1454
1573
 
1455
1574
  if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1456
- cinfo.jpeg_color_space == JCS_YCCK) {
1457
- jpeg_destroy_decompress(&cinfo);
1458
- ctx->jmp_armed = 0;
1459
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1460
- "CMYK/YCCK JPEG input is not supported in this release");
1461
- return 0;
1462
- }
1575
+ cinfo.jpeg_color_space == JCS_YCCK)
1576
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1577
+ "CMYK/YCCK JPEG input is not supported in this release");
1463
1578
 
1464
1579
  int old_width = ctx->width;
1465
1580
  int old_height = ctx->height;
@@ -1474,10 +1589,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1474
1589
  ctx->height = old_height;
1475
1590
  ctx->channels = old_channels;
1476
1591
  ctx->decoded_bytes = old_decoded_bytes;
1477
- jpeg_destroy_decompress(&cinfo);
1478
- ctx->jmp_armed = 0;
1479
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
1480
- return 0;
1592
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
1481
1593
  }
1482
1594
  validate_limits_for_pixels(ctx);
1483
1595
  ctx->width = old_width;
@@ -1486,63 +1598,35 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1486
1598
  size_t luma_size = ctx->decoded_bytes;
1487
1599
  ctx->decoded_bytes = old_decoded_bytes;
1488
1600
 
1489
- if (ctx->status != IP_OK) {
1490
- jpeg_destroy_decompress(&cinfo);
1491
- ctx->jmp_armed = 0;
1492
- return 0;
1493
- }
1601
+ if (ctx->status != IP_OK)
1602
+ goto fail;
1494
1603
 
1495
1604
  cinfo.out_color_space = JCS_GRAYSCALE;
1496
- #if defined(IMAGE_PACK_HAS_SIMD)
1497
- cinfo.dct_method = JDCT_ISLOW;
1498
- #else
1499
- cinfo.dct_method = JDCT_FASTEST;
1500
- #endif
1501
- cinfo.do_fancy_upsampling = FALSE;
1502
- cinfo.do_block_smoothing = FALSE;
1503
- cinfo.quantize_colors = FALSE;
1504
- cinfo.two_pass_quantize = FALSE;
1505
- cinfo.dither_mode = JDITHER_NONE;
1605
+ ip_apply_fast_decode(&cinfo);
1506
1606
 
1507
1607
  jpeg_start_decompress(&cinfo);
1508
1608
 
1509
1609
  size_t luma_stride = (size_t)cinfo.output_width;
1510
1610
  if (cinfo.output_components != 1 || luma_stride == 0 ||
1511
- luma_size != luma_stride * (size_t)cinfo.output_height) {
1512
- jpeg_destroy_decompress(&cinfo);
1513
- ctx->jmp_armed = 0;
1514
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1515
- return 0;
1516
- }
1611
+ luma_size != luma_stride * (size_t)cinfo.output_height)
1612
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1517
1613
 
1518
- ctx->transient_decode_buf = (unsigned char *)ip_malloc_hot(luma_size);
1519
- if (!ctx->transient_decode_buf && luma_size > 0) {
1520
- jpeg_destroy_decompress(&cinfo);
1521
- ctx->jmp_armed = 0;
1522
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1523
- return 0;
1524
- }
1614
+ ctx->transient_decode_buf = (unsigned char *)malloc(luma_size);
1615
+ if (!ctx->transient_decode_buf && luma_size > 0)
1616
+ IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1525
1617
 
1526
1618
  while (cinfo.output_scanline < cinfo.output_height) {
1527
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1528
- ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
1529
- jpeg_abort_decompress(&cinfo);
1530
- jpeg_destroy_decompress(&cinfo);
1531
- free(ctx->transient_decode_buf);
1532
- ctx->transient_decode_buf = NULL;
1533
- ctx->jmp_armed = 0;
1534
- return 0;
1535
- }
1619
+ if (atomic_load(&ctx->cancelled))
1620
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
1536
1621
 
1537
1622
  JSAMPROW rows[16];
1538
1623
  JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1539
1624
  if (batch > 16)
1540
1625
  batch = 16;
1541
1626
 
1542
- for (JDIMENSION i = 0; i < batch; i++) {
1627
+ for (JDIMENSION i = 0; i < batch; i++)
1543
1628
  rows[i] =
1544
1629
  ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * luma_stride);
1545
- }
1546
1630
 
1547
1631
  jpeg_read_scanlines(&cinfo, rows, batch);
1548
1632
  }
@@ -1559,6 +1643,13 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1559
1643
  *width = out_width;
1560
1644
  *height = out_height;
1561
1645
  return 1;
1646
+
1647
+ fail:
1648
+ jpeg_destroy_decompress(&cinfo);
1649
+ free(ctx->transient_decode_buf);
1650
+ ctx->transient_decode_buf = NULL;
1651
+ ctx->jmp_armed = 0;
1652
+ return 0;
1562
1653
  }
1563
1654
 
1564
1655
  static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned char *pixels,
@@ -1569,7 +1660,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1569
1660
  return NULL;
1570
1661
  }
1571
1662
 
1572
- unsigned char *luma = (unsigned char *)ip_malloc_hot(count);
1663
+ unsigned char *luma = (unsigned char *)malloc(count);
1573
1664
  if (!luma) {
1574
1665
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1575
1666
  return NULL;
@@ -1588,7 +1679,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1588
1679
  unsigned int r = src[i * 3 + 0];
1589
1680
  unsigned int g = src[i * 3 + 1];
1590
1681
  unsigned int b = src[i * 3 + 2];
1591
- dst[i] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1682
+ dst[i] = (unsigned char)((19595u * r + 38470u * g + 7471u * b + 32768u) >> 16);
1592
1683
  }
1593
1684
  return luma;
1594
1685
  }
@@ -1597,15 +1688,15 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1597
1688
  unsigned int r = src[i * 4 + 0];
1598
1689
  unsigned int g = src[i * 4 + 1];
1599
1690
  unsigned int b = src[i * 4 + 2];
1600
- dst[i] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1691
+ dst[i] = (unsigned char)((19595u * r + 38470u * g + 7471u * b + 32768u) >> 16);
1601
1692
  }
1602
1693
  return luma;
1603
1694
  }
1604
1695
 
1605
1696
  static double ip_ssim_window_score_double(int32_t n, int32_t sum_a, int32_t sum_b, int32_t sum_a2,
1606
1697
  int32_t sum_b2, int32_t sum_ab) {
1607
- const double c1 = 6.5025; /* (0.01 * 255)^2 */
1608
- const double c2 = 58.5225; /* (0.03 * 255)^2 */
1698
+ const double c1 = 6.5025;
1699
+ const double c2 = 58.5225;
1609
1700
 
1610
1701
  double inv_n = 1.0 / (double)n;
1611
1702
  double mean_a = (double)sum_a * inv_n;
@@ -1758,8 +1849,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1758
1849
  }
1759
1850
 
1760
1851
  if (reference_channels == 4) {
1761
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1762
- "min_ssim is not supported for RGBA input in v0.2.2");
1852
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED, "min_ssim is not supported for RGBA input");
1763
1853
  return 0;
1764
1854
  }
1765
1855
 
@@ -1778,7 +1868,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1778
1868
  int best_seen_quality = 0;
1779
1869
 
1780
1870
  while (search_low <= search_high) {
1781
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1871
+ if (atomic_load(&ctx->cancelled)) {
1782
1872
  free(reference_luma);
1783
1873
  free(best_jpeg);
1784
1874
  ip_context_set_error(ctx, IP_ERR_CANCELLED, "SSIM-guarded JPEG encode cancelled");
@@ -1799,7 +1889,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1799
1889
  size_t candidate_jpeg_size = ctx->output_size;
1800
1890
  ctx->output_data = NULL;
1801
1891
  ctx->output_size = 0;
1802
- ctx->output_capacity = 0;
1803
1892
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1804
1893
 
1805
1894
  unsigned char *candidate_luma = NULL;
@@ -1865,7 +1954,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1865
1954
 
1866
1955
  ctx->output_data = best_jpeg;
1867
1956
  ctx->output_size = best_jpeg_size;
1868
- ctx->output_capacity = best_jpeg_size;
1869
1957
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1870
1958
  return 1;
1871
1959
  }
@@ -1896,20 +1984,7 @@ static int ip_validate_lossless_optimize_header(ip_context_t *ctx,
1896
1984
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
1897
1985
  return 0;
1898
1986
  }
1899
- if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
1900
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
1901
- return 0;
1902
- }
1903
- if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
1904
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
1905
- return 0;
1906
- }
1907
- if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
1908
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1909
- return 0;
1910
- }
1911
-
1912
- return 1;
1987
+ return ip_check_max_dimension_limits(ctx);
1913
1988
  }
1914
1989
 
1915
1990
  static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
@@ -1926,24 +2001,14 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1926
2001
  memset(&dsterr, 0, sizeof(dsterr));
1927
2002
  ctx->transient_jpeg_buf = NULL;
1928
2003
 
1929
- srcinfo.err = jpeg_std_error(&srcerr.pub);
1930
- srcerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1931
- srcerr.ctx = ctx;
1932
-
1933
- dstinfo.err = jpeg_std_error(&dsterr.pub);
1934
- dsterr.pub.error_exit = ip_jpeg_encode_error_exit;
1935
- dsterr.ctx = ctx;
2004
+ srcinfo.err = ip_use_error(&srcerr, ctx, ip_jpeg_invalid_error_exit);
2005
+ dstinfo.err = ip_use_error(&dsterr, ctx, ip_jpeg_encode_error_exit);
1936
2006
 
1937
2007
  ctx->jmp_armed = 1;
1938
2008
  if (setjmp(ctx->jmpbuf)) {
1939
- ctx->jmp_armed = 0;
1940
- jpeg_destroy_compress(&dstinfo);
1941
- jpeg_destroy_decompress(&srcinfo);
1942
- free(ctx->transient_jpeg_buf);
1943
- ctx->transient_jpeg_buf = NULL;
1944
2009
  if (ctx->status == IP_OK)
1945
2010
  ip_context_set_error(ctx, IP_ERR_ENCODE, "lossless JPEG optimize failed");
1946
- return 0;
2011
+ goto fail;
1947
2012
  }
1948
2013
 
1949
2014
  jpeg_create_decompress(&srcinfo);
@@ -1951,47 +2016,23 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1951
2016
  jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
1952
2017
  ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
1953
2018
 
1954
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1955
- ip_context_set_error(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1956
- jpeg_destroy_compress(&dstinfo);
1957
- jpeg_destroy_decompress(&srcinfo);
1958
- ctx->jmp_armed = 0;
1959
- return 0;
1960
- }
2019
+ if (atomic_load(&ctx->cancelled))
2020
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1961
2021
 
1962
- int rc = jpeg_read_header(&srcinfo, TRUE);
1963
- if (rc != JPEG_HEADER_OK) {
1964
- jpeg_destroy_compress(&dstinfo);
1965
- jpeg_destroy_decompress(&srcinfo);
1966
- ctx->jmp_armed = 0;
1967
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1968
- return 0;
1969
- }
2022
+ if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK)
2023
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1970
2024
 
1971
2025
  ctx->source_orientation = ip_read_exif_orientation_from_decompress(&srcinfo);
1972
- if (ctx->strip_metadata && ctx->source_orientation > 1) {
1973
- jpeg_destroy_compress(&dstinfo);
1974
- jpeg_destroy_decompress(&srcinfo);
1975
- ctx->jmp_armed = 0;
1976
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1977
- "lossless optimize cannot strip EXIF Orientation without changing "
1978
- "visual orientation; use strip_metadata: false or ImagePack.compress");
1979
- return 0;
1980
- }
2026
+ if (ctx->strip_metadata && ctx->source_orientation > 1)
2027
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
2028
+ "lossless optimize cannot strip EXIF Orientation without changing "
2029
+ "visual orientation; use strip_metadata: false or ImagePack.compress");
1981
2030
 
1982
- if (!ip_validate_lossless_optimize_header(ctx, &srcinfo)) {
1983
- jpeg_destroy_compress(&dstinfo);
1984
- jpeg_destroy_decompress(&srcinfo);
1985
- ctx->jmp_armed = 0;
1986
- return 0;
1987
- }
2031
+ if (!ip_validate_lossless_optimize_header(ctx, &srcinfo))
2032
+ goto fail;
1988
2033
 
1989
- if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo)) {
1990
- jpeg_destroy_compress(&dstinfo);
1991
- jpeg_destroy_decompress(&srcinfo);
1992
- ctx->jmp_armed = 0;
1993
- return 0;
1994
- }
2034
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo))
2035
+ goto fail;
1995
2036
 
1996
2037
  coef_arrays = jpeg_read_coefficients(&srcinfo);
1997
2038
  jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
@@ -2005,15 +2046,8 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
2005
2046
 
2006
2047
  jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
2007
2048
 
2008
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
2009
- ip_context_set_error(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
2010
- jpeg_destroy_compress(&dstinfo);
2011
- jpeg_destroy_decompress(&srcinfo);
2012
- free(ctx->transient_jpeg_buf);
2013
- ctx->transient_jpeg_buf = NULL;
2014
- ctx->jmp_armed = 0;
2015
- return 0;
2016
- }
2049
+ if (atomic_load(&ctx->cancelled))
2050
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
2017
2051
 
2018
2052
  jpeg_write_coefficients(&dstinfo, coef_arrays);
2019
2053
  if (!ctx->strip_metadata)
@@ -2021,23 +2055,27 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
2021
2055
 
2022
2056
  jpeg_finish_compress(&dstinfo);
2023
2057
  jpeg_finish_decompress(&srcinfo);
2058
+
2059
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
2060
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
2061
+
2024
2062
  jpeg_destroy_compress(&dstinfo);
2025
2063
  jpeg_destroy_decompress(&srcinfo);
2026
2064
  ctx->jmp_armed = 0;
2027
2065
 
2028
- if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size) {
2029
- free(ctx->transient_jpeg_buf);
2030
- ctx->transient_jpeg_buf = NULL;
2031
- ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
2032
- return 0;
2033
- }
2034
-
2035
2066
  ctx->output_data = ctx->transient_jpeg_buf;
2036
2067
  ctx->transient_jpeg_buf = NULL;
2037
2068
  ctx->output_size = (size_t)jpeg_size;
2038
- ctx->output_capacity = (size_t)jpeg_size;
2039
2069
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
2040
2070
  return 1;
2071
+
2072
+ fail:
2073
+ jpeg_destroy_compress(&dstinfo);
2074
+ jpeg_destroy_decompress(&srcinfo);
2075
+ free(ctx->transient_jpeg_buf);
2076
+ ctx->transient_jpeg_buf = NULL;
2077
+ ctx->jmp_armed = 0;
2078
+ return 0;
2041
2079
  }
2042
2080
 
2043
2081
  static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
@@ -2052,6 +2090,15 @@ static int ip_mozjpeg_compress(ip_context_t *ctx) {
2052
2090
  return compress_jpeg_input_with_mode(ctx, 1);
2053
2091
  }
2054
2092
 
2093
+ static ip_execution_t ip_async_execution(const ip_context_t *ctx) {
2094
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2095
+ return (ip_offload_runtime_enabled && ctx->has_scheduler) ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2096
+ #else
2097
+ (void)ctx;
2098
+ return IP_EXEC_NOGVL;
2099
+ #endif
2100
+ }
2101
+
2055
2102
  static void ip_resolve_execution(ip_context_t *ctx) {
2056
2103
  if (ctx->requested_execution != IP_EXEC_AUTO) {
2057
2104
  ctx->resolved_execution = ctx->requested_execution;
@@ -2059,12 +2106,12 @@ static void ip_resolve_execution(ip_context_t *ctx) {
2059
2106
  }
2060
2107
 
2061
2108
  if (ctx->cancellable_requested) {
2062
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2109
+ ctx->resolved_execution = ip_async_execution(ctx);
2063
2110
  return;
2064
2111
  }
2065
2112
 
2066
2113
  if (ctx->ssim_guard_enabled) {
2067
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2114
+ ctx->resolved_execution = ip_async_execution(ctx);
2068
2115
  return;
2069
2116
  }
2070
2117
 
@@ -2074,7 +2121,7 @@ static void ip_resolve_execution(ip_context_t *ctx) {
2074
2121
  return;
2075
2122
  }
2076
2123
 
2077
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2124
+ ctx->resolved_execution = ip_async_execution(ctx);
2078
2125
  }
2079
2126
 
2080
2127
  static void ip_unblock_function(void *data) {
@@ -2111,11 +2158,20 @@ static int ip_run_context(ip_context_t *ctx) {
2111
2158
  } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2112
2159
  rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, 0);
2113
2160
  } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2161
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2114
2162
  rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2163
+ #else
2164
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
2165
+ "offload execution is unavailable in this runtime; it requires Ruby "
2166
+ ">= 3.4 and IMAGE_PACK_DISABLE_OFFLOAD must not be set");
2167
+ #endif
2115
2168
  } else {
2116
2169
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2117
2170
  }
2118
2171
 
2172
+ if (ctx->resolved_execution != IP_EXEC_DIRECT)
2173
+ rb_thread_check_ints();
2174
+
2119
2175
  return ctx->status == IP_OK;
2120
2176
  }
2121
2177
 
@@ -2134,39 +2190,57 @@ static int ip_run_optimize_context(ip_context_t *ctx) {
2134
2190
  } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2135
2191
  rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, 0);
2136
2192
  } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2193
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2137
2194
  rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2195
+ #else
2196
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
2197
+ "offload execution is unavailable in this runtime; it requires Ruby "
2198
+ ">= 3.4 and IMAGE_PACK_DISABLE_OFFLOAD must not be set");
2199
+ #endif
2138
2200
  } else {
2139
2201
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2140
2202
  }
2141
2203
 
2204
+ if (ctx->resolved_execution != IP_EXEC_DIRECT)
2205
+ rb_thread_check_ints();
2206
+
2142
2207
  return ctx->status == IP_OK;
2143
2208
  }
2144
2209
 
2145
- static size_t config_size_value(VALUE config, ID id, size_t fallback) {
2210
+ static size_t config_size_value(VALUE config, ID id, size_t fallback, const char *name) {
2146
2211
  VALUE value = rb_funcall(config, id, 0);
2147
2212
  if (NIL_P(value))
2148
2213
  return fallback;
2214
+ if (!RB_INTEGER_TYPE_P(value) || ip_value_negative(value)) {
2215
+ rb_raise(rb_eImagePackInvalidArgumentError, "%s must be an Integer >= 0", name);
2216
+ }
2149
2217
  return NUM2SIZET(value);
2150
2218
  }
2151
2219
 
2152
- static int config_int_value(VALUE config, ID id, int fallback) {
2220
+ static int config_int_value(VALUE config, ID id, int fallback, const char *name) {
2153
2221
  VALUE value = rb_funcall(config, id, 0);
2154
2222
  if (NIL_P(value))
2155
2223
  return fallback;
2224
+ if (!RB_INTEGER_TYPE_P(value) || ip_value_negative(value)) {
2225
+ rb_raise(rb_eImagePackInvalidArgumentError, "%s must be an Integer >= 0", name);
2226
+ }
2156
2227
  return NUM2INT(value);
2157
2228
  }
2158
2229
 
2159
2230
  static void apply_configuration(VALUE self, ip_context_t *ctx) {
2160
2231
  VALUE config = rb_funcall(self, id_configuration, 0);
2161
- ctx->direct_input_threshold =
2162
- config_size_value(config, id_direct_input_threshold, ctx->direct_input_threshold);
2163
- ctx->direct_pixel_threshold =
2164
- config_size_value(config, id_direct_pixel_threshold, ctx->direct_pixel_threshold);
2165
- ctx->max_pixels = (uint64_t)config_size_value(config, id_max_pixels, (size_t)ctx->max_pixels);
2166
- ctx->max_width = config_int_value(config, id_max_width, ctx->max_width);
2167
- ctx->max_height = config_int_value(config, id_max_height, ctx->max_height);
2168
- ctx->max_output_size = config_size_value(config, id_max_output_size, ctx->max_output_size);
2169
- ctx->max_input_size = config_size_value(config, id_max_input_size, ctx->max_input_size);
2232
+ ctx->direct_input_threshold = config_size_value(
2233
+ config, id_direct_input_threshold, ctx->direct_input_threshold, "direct_input_threshold");
2234
+ ctx->direct_pixel_threshold = config_size_value(
2235
+ config, id_direct_pixel_threshold, ctx->direct_pixel_threshold, "direct_pixel_threshold");
2236
+ ctx->max_pixels =
2237
+ (uint64_t)config_size_value(config, id_max_pixels, (size_t)ctx->max_pixels, "max_pixels");
2238
+ ctx->max_width = config_int_value(config, id_max_width, ctx->max_width, "max_width");
2239
+ ctx->max_height = config_int_value(config, id_max_height, ctx->max_height, "max_height");
2240
+ ctx->max_output_size =
2241
+ config_size_value(config, id_max_output_size, ctx->max_output_size, "max_output_size");
2242
+ ctx->max_input_size =
2243
+ config_size_value(config, id_max_input_size, ctx->max_input_size, "max_input_size");
2170
2244
  }
2171
2245
 
2172
2246
  static void validate_limits_for_pixels(ip_context_t *ctx) {
@@ -2180,18 +2254,8 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
2180
2254
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
2181
2255
  return;
2182
2256
  }
2183
- if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
2184
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
2257
+ if (!ip_check_max_dimension_limits(ctx))
2185
2258
  return;
2186
- }
2187
- if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
2188
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
2189
- return;
2190
- }
2191
- if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
2192
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
2193
- return;
2194
- }
2195
2259
  if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &decoded_bytes)) {
2196
2260
  ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
2197
2261
  return;
@@ -2221,32 +2285,46 @@ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
2221
2285
  ctx->requested_execution = ip_parse_execution(call->execution);
2222
2286
  ctx->cancellable_requested = ip_bool_value(call->cancellable);
2223
2287
  ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2288
+ ctx->strict = ip_bool_value(call->strict);
2224
2289
  apply_configuration(call->self, ctx);
2225
2290
 
2226
- if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
2227
- !ip_prepare_output_path(ctx, call->output, out_kind)) {
2291
+ ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
2292
+ if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2293
+ !ip_prepare_input_bytes(ctx, call->input, in_kind)) {
2228
2294
  ip_raise_for_status(ctx);
2229
2295
  rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2230
2296
  }
2231
2297
 
2232
2298
  if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2233
- !ip_inspect_jpeg_header(ctx)) {
2299
+ !ip_inspect_jpeg_header(ctx, 0)) {
2234
2300
  ip_raise_for_status(ctx);
2235
2301
  rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2236
2302
  }
2237
2303
 
2304
+ ip_resolve_execution(ctx);
2305
+ if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2306
+ ip_raise_for_status(ctx);
2307
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2308
+ }
2309
+
2238
2310
  ip_run_context(ctx);
2311
+ if (ip_bool_value(call->report)) {
2312
+ VALUE output_value = ip_finish_output(ctx, out_kind);
2313
+ return ip_build_report(ctx, output_value);
2314
+ }
2239
2315
  return ip_finish_output(ctx, out_kind);
2240
2316
  }
2241
2317
 
2242
2318
  static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
2243
2319
  VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
2244
2320
  VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
2245
- VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2321
+ VALUE execution, VALUE cancellable, VALUE has_scheduler,
2322
+ VALUE report, VALUE strict) {
2246
2323
  ip_compress_jpeg_call_t call = {
2247
2324
  self, input, input_kind, output, output_kind,
2248
2325
  algo, quality, min_ssim, mozjpeg_trellis, progressive,
2249
- strip_metadata, execution, cancellable, has_scheduler, NULL};
2326
+ strip_metadata, execution, cancellable, has_scheduler, report,
2327
+ strict, NULL};
2250
2328
  return rb_ensure(ip_compress_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
2251
2329
  }
2252
2330
 
@@ -2265,16 +2343,18 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2265
2343
  ctx->min_ssim = NUM2DBL(call->min_ssim);
2266
2344
  ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
2267
2345
  ip_validate_min_ssim_or_raise(ctx);
2346
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2268
2347
  ctx->progressive = ip_bool_value(call->progressive);
2269
2348
  ctx->strip_metadata = 1;
2270
2349
  ctx->requested_execution = ip_parse_execution(call->execution);
2271
2350
  ctx->cancellable_requested = ip_bool_value(call->cancellable);
2272
2351
  ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2352
+ ctx->strict = ip_bool_value(call->strict);
2273
2353
  apply_configuration(call->self, ctx);
2274
2354
 
2275
- if (!ip_prepare_pixels(ctx, call->buffer, NUM2INT(call->width), NUM2INT(call->height),
2276
- NUM2INT(call->channels)) ||
2277
- !ip_prepare_output_path(ctx, call->output, out_kind)) {
2355
+ if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2356
+ !ip_prepare_pixels(ctx, call->buffer, NUM2INT(call->width), NUM2INT(call->height),
2357
+ NUM2INT(call->channels), ip_bool_value(call->exact_size))) {
2278
2358
  ip_raise_for_status(ctx);
2279
2359
  rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
2280
2360
  }
@@ -2283,17 +2363,26 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2283
2363
  if (ctx->status != IP_OK)
2284
2364
  ip_raise_for_status(ctx);
2285
2365
 
2366
+ ip_resolve_execution(ctx);
2367
+ if (!ip_ensure_owned_pixels_for_async(ctx, call->buffer)) {
2368
+ ip_raise_for_status(ctx);
2369
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
2370
+ }
2371
+
2286
2372
  ip_run_context(ctx);
2373
+ if (ip_bool_value(call->report)) {
2374
+ VALUE output_value = ip_finish_output(ctx, out_kind);
2375
+ return ip_build_report(ctx, output_value);
2376
+ }
2287
2377
  return ip_finish_output(ctx, out_kind);
2288
2378
  }
2289
2379
 
2290
- static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
2291
- VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
2292
- VALUE quality, VALUE min_ssim, VALUE progressive,
2293
- VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2294
- ip_compress_pixels_call_t call = {
2295
- self, buffer, width, height, channels, output, output_kind, algo,
2296
- quality, min_ssim, progressive, execution, cancellable, has_scheduler, NULL};
2380
+ static VALUE ip_compress_pixels_entry(int argc, VALUE *argv, VALUE self) {
2381
+ rb_check_arity(argc, 17, 17);
2382
+ ip_compress_pixels_call_t call = {self, argv[0], argv[1], argv[2], argv[3],
2383
+ argv[4], argv[5], argv[6], argv[7], argv[8],
2384
+ argv[9], argv[10], argv[11], argv[12], argv[13],
2385
+ argv[14], argv[15], argv[16], NULL};
2297
2386
  return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
2298
2387
  (VALUE)&call.ctx);
2299
2388
  }
@@ -2311,11 +2400,25 @@ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2311
2400
  ctx->requested_execution = ip_parse_execution(call->execution);
2312
2401
  ctx->cancellable_requested = ip_bool_value(call->cancellable);
2313
2402
  ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2403
+ ctx->strict = ip_bool_value(call->strict);
2314
2404
  ctx->ssim_guard_enabled = 0;
2315
2405
  apply_configuration(call->self, ctx);
2316
2406
 
2317
- if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
2318
- !ip_prepare_output_path(ctx, call->output, out_kind)) {
2407
+ ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
2408
+ if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2409
+ !ip_prepare_input_bytes(ctx, call->input, in_kind)) {
2410
+ ip_raise_for_status(ctx);
2411
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2412
+ }
2413
+
2414
+ if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2415
+ !ip_inspect_jpeg_header(ctx, 1)) {
2416
+ ip_raise_for_status(ctx);
2417
+ rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2418
+ }
2419
+
2420
+ ip_resolve_execution(ctx);
2421
+ if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2319
2422
  ip_raise_for_status(ctx);
2320
2423
  rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2321
2424
  }
@@ -2326,63 +2429,82 @@ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2326
2429
 
2327
2430
  static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
2328
2431
  VALUE output_kind, VALUE progressive, VALUE strip_metadata,
2329
- VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2330
- ip_optimize_jpeg_call_t call = {
2331
- self, input, input_kind, output, output_kind, progressive,
2332
- strip_metadata, execution, cancellable, has_scheduler, NULL};
2432
+ VALUE execution, VALUE cancellable, VALUE has_scheduler,
2433
+ VALUE strict) {
2434
+ ip_optimize_jpeg_call_t call = {self, input, input_kind, output,
2435
+ output_kind, progressive, strip_metadata, execution,
2436
+ cancellable, has_scheduler, strict, NULL};
2333
2437
  return rb_ensure(ip_optimize_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
2334
2438
  }
2335
2439
 
2336
2440
  IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
2337
- id_jpeg_turbo = rb_intern("jpeg_turbo");
2338
- id_mozjpeg = rb_intern("mozjpeg");
2339
- id_direct = rb_intern("direct");
2340
- id_nogvl = rb_intern("nogvl");
2341
- id_offload = rb_intern("offload");
2342
- id_auto = rb_intern("auto");
2343
- id_bytes = rb_intern("bytes");
2344
- id_path = rb_intern("path");
2345
- id_io_buffer = rb_intern("io_buffer");
2346
- id_return_string = rb_intern("return_string");
2347
- id_configuration = rb_intern("configuration");
2348
- id_direct_input_threshold = rb_intern("direct_input_threshold");
2349
- id_direct_pixel_threshold = rb_intern("direct_pixel_threshold");
2350
- id_max_pixels = rb_intern("max_pixels");
2351
- id_max_width = rb_intern("max_width");
2352
- id_max_height = rb_intern("max_height");
2353
- id_max_output_size = rb_intern("max_output_size");
2354
- id_max_input_size = rb_intern("max_input_size");
2441
+ static const struct {
2442
+ ID *slot;
2443
+ const char *name;
2444
+ } ids[] = {{&id_jpeg_turbo, "jpeg_turbo"},
2445
+ {&id_mozjpeg, "mozjpeg"},
2446
+ {&id_direct, "direct"},
2447
+ {&id_nogvl, "nogvl"},
2448
+ {&id_offload, "offload"},
2449
+ {&id_auto, "auto"},
2450
+ {&id_bytes, "bytes"},
2451
+ {&id_path, "path"},
2452
+ {&id_io_buffer, "io_buffer"},
2453
+ {&id_return_string, "return_string"},
2454
+ {&id_configuration, "configuration"},
2455
+ {&id_direct_input_threshold, "direct_input_threshold"},
2456
+ {&id_direct_pixel_threshold, "direct_pixel_threshold"},
2457
+ {&id_max_pixels, "max_pixels"},
2458
+ {&id_max_width, "max_width"},
2459
+ {&id_max_height, "max_height"},
2460
+ {&id_max_output_size, "max_output_size"},
2461
+ {&id_max_input_size, "max_input_size"}};
2462
+ for (size_t i = 0; i < IP_ARRAY_LEN(ids); i++)
2463
+ *ids[i].slot = rb_intern(ids[i].name);
2355
2464
 
2356
2465
  rb_mImagePack = rb_define_module("ImagePack");
2357
- rb_eImagePackError = rb_const_get(rb_mImagePack, rb_intern("Error"));
2358
- rb_eImagePackInvalidArgumentError =
2359
- rb_const_get(rb_mImagePack, rb_intern("InvalidArgumentError"));
2360
- rb_eImagePackInvalidImageError = rb_const_get(rb_mImagePack, rb_intern("InvalidImageError"));
2361
- rb_eImagePackUnsupportedError = rb_const_get(rb_mImagePack, rb_intern("UnsupportedError"));
2362
- rb_eImagePackLimitExceededError = rb_const_get(rb_mImagePack, rb_intern("LimitExceededError"));
2363
- rb_eImagePackEncodeError = rb_const_get(rb_mImagePack, rb_intern("EncodeError"));
2364
- rb_eImagePackQualityConstraintError =
2365
- rb_const_get(rb_mImagePack, rb_intern("QualityConstraintError"));
2366
- rb_eImagePackOutOfMemoryError = rb_const_get(rb_mImagePack, rb_intern("OutOfMemoryError"));
2367
- rb_eImagePackCancelledError = rb_const_get(rb_mImagePack, rb_intern("CancelledError"));
2368
-
2369
- rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION", rb_str_new_cstr(VERSION));
2466
+
2467
+ static const struct {
2468
+ VALUE *slot;
2469
+ const char *name;
2470
+ } exceptions[] = {{&rb_eImagePackError, "Error"},
2471
+ {&rb_eImagePackInvalidArgumentError, "InvalidArgumentError"},
2472
+ {&rb_eImagePackInvalidImageError, "InvalidImageError"},
2473
+ {&rb_eImagePackUnsupportedError, "UnsupportedError"},
2474
+ {&rb_eImagePackLimitExceededError, "LimitExceededError"},
2475
+ {&rb_eImagePackEncodeError, "EncodeError"},
2476
+ {&rb_eImagePackQualityConstraintError, "QualityConstraintError"},
2477
+ {&rb_eImagePackOutOfMemoryError, "OutOfMemoryError"},
2478
+ {&rb_eImagePackCancelledError, "CancelledError"}};
2479
+ for (size_t i = 0; i < IP_ARRAY_LEN(exceptions); i++)
2480
+ *exceptions[i].slot = rb_const_get(rb_mImagePack, rb_intern(exceptions[i].name));
2481
+
2482
+ rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION",
2483
+ rb_str_new_cstr(IMAGE_PACK_MOZJPEG_VERSION));
2370
2484
  #if defined(IMAGE_PACK_HAS_SIMD)
2371
2485
  rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qtrue);
2372
2486
  #else
2373
2487
  rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qfalse);
2374
2488
  #endif
2489
+ ip_offload_runtime_enabled = (getenv("IMAGE_PACK_DISABLE_OFFLOAD") == NULL) ? 1 : 0;
2490
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2491
+ rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE",
2492
+ ip_offload_runtime_enabled ? Qtrue : Qfalse);
2493
+ #else
2494
+ rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE", Qfalse);
2495
+ #endif
2375
2496
 
2376
- rb_define_singleton_method(rb_mImagePack, "__compress_jpeg", ip_compress_jpeg_entry, 13);
2377
- rb_define_singleton_method(rb_mImagePack, "__compress_pixels", ip_compress_pixels_entry, 13);
2378
- rb_define_singleton_method(rb_mImagePack, "__optimize_jpeg", ip_optimize_jpeg_entry, 9);
2379
- rb_define_singleton_method(rb_mImagePack, "__inspect_image", ip_inspect_image_entry, 2);
2380
- rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2381
- ID2SYM(rb_intern("__compress_jpeg")));
2382
- rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2383
- ID2SYM(rb_intern("__compress_pixels")));
2384
- rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2385
- ID2SYM(rb_intern("__optimize_jpeg")));
2386
- rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2387
- ID2SYM(rb_intern("__inspect_image")));
2497
+ static const struct {
2498
+ const char *name;
2499
+ VALUE (*fn)(ANYARGS);
2500
+ int arity;
2501
+ } methods[] = {{"__compress_jpeg", (VALUE (*)(ANYARGS))ip_compress_jpeg_entry, 15},
2502
+ {"__compress_pixels", (VALUE (*)(ANYARGS))ip_compress_pixels_entry, -1},
2503
+ {"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry, 10},
2504
+ {"__inspect_image", (VALUE (*)(ANYARGS))ip_inspect_image_entry, 2}};
2505
+ for (size_t i = 0; i < IP_ARRAY_LEN(methods); i++) {
2506
+ rb_define_singleton_method(rb_mImagePack, methods[i].name, methods[i].fn, methods[i].arity);
2507
+ rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2508
+ ID2SYM(rb_intern(methods[i].name)));
2509
+ }
2388
2510
  }