image_pack 0.2.2 → 0.2.3

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 +1 -0
  3. data/README.md +23 -4
  4. data/ext/image_pack/extconf.rb +35 -124
  5. data/ext/image_pack/image_pack.c +638 -595
  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 +65 -18
  13. metadata +13 -78
  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,10 +36,6 @@
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
@@ -48,6 +52,32 @@
48
52
  #define IP_RESTRICT
49
53
  #endif
50
54
 
55
+ #if defined(IMAGE_PACK_HAS_SIMD)
56
+ #define IP_FAST_DCT JDCT_ISLOW
57
+ #else
58
+ #define IP_FAST_DCT JDCT_FASTEST
59
+ #endif
60
+
61
+ #define IP_FAIL(ctx, st, msg) \
62
+ do { \
63
+ ip_context_set_error((ctx), (st), (msg)); \
64
+ return 0; \
65
+ } while (0)
66
+
67
+ #define IP_FAIL_NULL(ctx, st, msg) \
68
+ do { \
69
+ ip_context_set_error((ctx), (st), (msg)); \
70
+ return NULL; \
71
+ } while (0)
72
+
73
+ #define IP_FAIL_GOTO(ctx, st, msg) \
74
+ do { \
75
+ ip_context_set_error((ctx), (st), (msg)); \
76
+ goto fail; \
77
+ } while (0)
78
+
79
+ #define IP_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
80
+
51
81
  typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
52
82
 
53
83
  typedef enum {
@@ -87,11 +117,11 @@ typedef struct {
87
117
  int height;
88
118
  int channels;
89
119
  int bit_depth;
120
+ int jpeg_color_space;
90
121
  size_t decoded_bytes;
91
122
 
92
123
  unsigned char *output_data;
93
124
  size_t output_size;
94
- size_t output_capacity;
95
125
  ip_output_owner_t output_owner;
96
126
  char *output_path;
97
127
 
@@ -125,9 +155,6 @@ typedef struct {
125
155
  jmp_buf jmpbuf;
126
156
  int jmp_armed;
127
157
 
128
- unsigned char *scratch_row;
129
- size_t scratch_row_size;
130
-
131
158
  struct {
132
159
  int marker;
133
160
  unsigned char *data;
@@ -138,7 +165,6 @@ typedef struct {
138
165
 
139
166
  unsigned char *transient_jpeg_buf;
140
167
  unsigned char *transient_decode_buf;
141
- unsigned char *transient_scratch_buf;
142
168
  int source_orientation;
143
169
  } ip_context_t;
144
170
 
@@ -188,6 +214,9 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
188
214
  static void ip_validate_quality_or_raise(ip_context_t *ctx);
189
215
  static void ip_validate_min_ssim_or_raise(ip_context_t *ctx);
190
216
  static int ip_bool_value(VALUE value);
217
+ static int ip_value_negative(VALUE value);
218
+ static char *ip_strdup(const char *source);
219
+ static int ip_replace_file(const char *tmp_path, const char *output_path);
191
220
 
192
221
  static ip_algo_t ip_parse_algo(VALUE sym);
193
222
  static ip_execution_t ip_parse_execution(VALUE sym);
@@ -195,11 +224,15 @@ static ip_input_kind_t ip_parse_input_kind(VALUE sym);
195
224
  static ip_output_kind_t ip_parse_output_kind(VALUE sym);
196
225
 
197
226
  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);
227
+ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels,
228
+ int exact_size);
229
+ static int ip_ensure_owned_input_for_async(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
230
+ static int ip_ensure_owned_pixels_for_async(ip_context_t *ctx, VALUE buffer);
199
231
  static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind);
200
232
  static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind);
233
+ static void apply_configuration(VALUE self, ip_context_t *ctx);
201
234
 
202
- static int ip_inspect_jpeg_header(ip_context_t *ctx);
235
+ static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck);
203
236
  static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind);
204
237
 
205
238
  static void ip_resolve_execution(ip_context_t *ctx);
@@ -227,7 +260,7 @@ typedef struct {
227
260
 
228
261
  typedef struct {
229
262
  VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
230
- VALUE progressive, execution, cancellable, has_scheduler;
263
+ VALUE mozjpeg_trellis, progressive, exact_size, execution, cancellable, has_scheduler;
231
264
  ip_context_t *ctx;
232
265
  } ip_compress_pixels_call_t;
233
266
 
@@ -257,8 +290,9 @@ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, V
257
290
  VALUE execution, VALUE cancellable, VALUE has_scheduler);
258
291
  static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
259
292
  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);
293
+ VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
294
+ VALUE progressive, VALUE exact_size, VALUE execution,
295
+ VALUE cancellable, VALUE has_scheduler);
262
296
  static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
263
297
  VALUE output_kind, VALUE progressive, VALUE strip_metadata,
264
298
  VALUE execution, VALUE cancellable, VALUE has_scheduler);
@@ -310,10 +344,6 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
310
344
  return ip_checked_mul_size(pixels, (size_t)channels, out);
311
345
  }
312
346
 
313
- static void *ip_malloc_hot(size_t size) {
314
- return malloc(size);
315
- }
316
-
317
347
  static void ip_validate_quality_or_raise(ip_context_t *ctx) {
318
348
  if (ctx->quality >= 1 && ctx->quality <= 100)
319
349
  return;
@@ -333,6 +363,67 @@ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
333
363
  "min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
334
364
  }
335
365
 
366
+ static int ip_value_negative(VALUE value) {
367
+ return RTEST(rb_funcall(value, rb_intern("<"), 1, INT2FIX(0)));
368
+ }
369
+
370
+ static char *ip_strdup(const char *source) {
371
+ size_t len = strlen(source) + 1;
372
+ char *copy = (char *)malloc(len);
373
+ if (!copy)
374
+ return NULL;
375
+ memcpy(copy, source, len);
376
+ return copy;
377
+ }
378
+
379
+ #if defined(_WIN32)
380
+ static int ip_replace_file(const char *tmp_path, const char *output_path) {
381
+ return MoveFileExA(tmp_path, output_path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)
382
+ ? 0
383
+ : -1;
384
+ }
385
+ #else
386
+ static int ip_replace_file(const char *tmp_path, const char *output_path) {
387
+ return rename(tmp_path, output_path);
388
+ }
389
+ #endif
390
+
391
+ static int ip_file_seek_end(FILE *fp) {
392
+ #if defined(_WIN32)
393
+ return _fseeki64(fp, 0, SEEK_END);
394
+ #else
395
+ return fseeko(fp, 0, SEEK_END);
396
+ #endif
397
+ }
398
+
399
+ static int ip_file_rewind(FILE *fp) {
400
+ #if defined(_WIN32)
401
+ return _fseeki64(fp, 0, SEEK_SET);
402
+ #else
403
+ return fseeko(fp, 0, SEEK_SET);
404
+ #endif
405
+ }
406
+
407
+ static int ip_file_tell(FILE *fp, size_t *out) {
408
+ #if defined(_WIN32)
409
+ __int64 pos = _ftelli64(fp);
410
+ if (pos < 0)
411
+ return 0;
412
+ if ((unsigned long long)pos > (unsigned long long)SIZE_MAX)
413
+ return 0;
414
+ *out = (size_t)pos;
415
+ return 1;
416
+ #else
417
+ off_t pos = ftello(fp);
418
+ if (pos < 0)
419
+ return 0;
420
+ if ((uintmax_t)pos > (uintmax_t)SIZE_MAX)
421
+ return 0;
422
+ *out = (size_t)pos;
423
+ return 1;
424
+ #endif
425
+ }
426
+
336
427
  static int ip_bool_value(VALUE value) {
337
428
  if (NIL_P(value) || value == Qfalse)
338
429
  return 0;
@@ -371,10 +462,8 @@ static void ip_context_free(ip_context_t *ctx) {
371
462
  free(ctx->owned_input_data);
372
463
  free(ctx->owned_pixel_data);
373
464
  free(ctx->output_path);
374
- free(ctx->scratch_row);
375
465
  free(ctx->transient_jpeg_buf);
376
466
  free(ctx->transient_decode_buf);
377
- free(ctx->transient_scratch_buf);
378
467
 
379
468
  if (ctx->preserved_markers) {
380
469
  for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
@@ -407,46 +496,87 @@ static ID symbol_id(VALUE sym, const char *kind) {
407
496
  return SYM2ID(sym);
408
497
  }
409
498
 
499
+ typedef struct {
500
+ const ID *id;
501
+ int value;
502
+ } ip_symbol_entry;
503
+
504
+ static int ip_map_symbol(VALUE sym, const char *kind, const ip_symbol_entry *table) {
505
+ ID id = symbol_id(sym, kind);
506
+ for (; table->id != NULL; table++) {
507
+ if (id == *table->id)
508
+ return table->value;
509
+ }
510
+ rb_raise(rb_eImagePackInvalidArgumentError, "unknown %s", kind);
511
+ }
512
+
410
513
  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");
514
+ static const ip_symbol_entry table[] = {
515
+ {&id_jpeg_turbo, IP_ALGO_JPEG_TURBO}, {&id_mozjpeg, IP_ALGO_MOZJPEG}, {NULL, 0}};
516
+ return (ip_algo_t)ip_map_symbol(sym, "algo", table);
417
517
  }
418
518
 
419
519
  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");
520
+ static const ip_symbol_entry table[] = {{&id_direct, IP_EXEC_DIRECT},
521
+ {&id_nogvl, IP_EXEC_NOGVL},
522
+ {&id_offload, IP_EXEC_OFFLOAD},
523
+ {&id_auto, IP_EXEC_AUTO},
524
+ {NULL, 0}};
525
+ return (ip_execution_t)ip_map_symbol(sym, "execution", table);
430
526
  }
431
527
 
432
528
  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");
529
+ static const ip_symbol_entry table[] = {{&id_bytes, IP_INPUT_BYTES},
530
+ {&id_path, IP_INPUT_PATH},
531
+ {&id_io_buffer, IP_INPUT_IO_BUFFER},
532
+ {NULL, 0}};
533
+ return (ip_input_kind_t)ip_map_symbol(sym, "input kind", table);
441
534
  }
442
535
 
443
536
  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");
537
+ static const ip_symbol_entry table[] = {
538
+ {&id_return_string, IP_OUTPUT_RETURN_STRING}, {&id_path, IP_OUTPUT_PATH}, {NULL, 0}};
539
+ return (ip_output_kind_t)ip_map_symbol(sym, "output kind", table);
540
+ }
541
+
542
+ static VALUE ip_sym(const char *name) {
543
+ return ID2SYM(rb_intern(name));
544
+ }
545
+
546
+ static struct jpeg_error_mgr *ip_use_error(ip_jpeg_error_mgr *jerr, ip_context_t *ctx,
547
+ void (*handler)(j_common_ptr)) {
548
+ struct jpeg_error_mgr *base = jpeg_std_error(&jerr->pub);
549
+ jerr->pub.error_exit = handler;
550
+ jerr->ctx = ctx;
551
+ return base;
552
+ }
553
+
554
+ static void ip_apply_fast_decode(struct jpeg_decompress_struct *cinfo) {
555
+ cinfo->dct_method = IP_FAST_DCT;
556
+ cinfo->do_fancy_upsampling = FALSE;
557
+ cinfo->do_block_smoothing = FALSE;
558
+ cinfo->quantize_colors = FALSE;
559
+ cinfo->two_pass_quantize = FALSE;
560
+ cinfo->dither_mode = JDITHER_NONE;
561
+ }
562
+
563
+ static void ip_disable_mozjpeg_trellis(struct jpeg_compress_struct *cinfo) {
564
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
565
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
566
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
567
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
568
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
569
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
570
+ }
571
+
572
+ static int ip_check_max_dimension_limits(ip_context_t *ctx) {
573
+ if (ctx->max_width > 0 && ctx->width > ctx->max_width)
574
+ IP_FAIL(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
575
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height)
576
+ IP_FAIL(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
577
+ if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels)
578
+ IP_FAIL(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
579
+ return 1;
450
580
  }
451
581
 
452
582
  static VALUE pathname_to_s(VALUE object) {
@@ -464,36 +594,41 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
464
594
  return 0;
465
595
  }
466
596
 
467
- if (fseek(fp, 0, SEEK_END) != 0) {
597
+ if (ip_file_seek_end(fp) != 0) {
468
598
  fclose(fp);
469
599
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to seek input path");
470
600
  return 0;
471
601
  }
472
602
 
473
- long size = ftell(fp);
474
- if (size < 0) {
603
+ size_t size = 0;
604
+ if (!ip_file_tell(fp, &size)) {
475
605
  fclose(fp);
476
606
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to determine input size");
477
607
  return 0;
478
608
  }
479
- rewind(fp);
480
609
 
481
- if (ctx->max_input_size > 0 && (size_t)size > ctx->max_input_size) {
610
+ if (ip_file_rewind(fp) != 0) {
611
+ fclose(fp);
612
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to rewind input path");
613
+ return 0;
614
+ }
615
+
616
+ if (ctx->max_input_size > 0 && size > ctx->max_input_size) {
482
617
  fclose(fp);
483
618
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input file exceeds max_input_size");
484
619
  return 0;
485
620
  }
486
621
 
487
- unsigned char *data = (unsigned char *)malloc((size_t)size);
622
+ unsigned char *data = (unsigned char *)malloc(size);
488
623
  if (!data && size > 0) {
489
624
  fclose(fp);
490
625
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate input file buffer");
491
626
  return 0;
492
627
  }
493
628
 
494
- size_t read_size = fread(data, 1, (size_t)size, fp);
629
+ size_t read_size = fread(data, 1, size, fp);
495
630
  fclose(fp);
496
- if (read_size != (size_t)size) {
631
+ if (read_size != size) {
497
632
  free(data);
498
633
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to read input path");
499
634
  return 0;
@@ -505,9 +640,36 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
505
640
  return 1;
506
641
  }
507
642
 
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);
643
+ static size_t io_buffer_size_or_raise(VALUE buffer) {
644
+ VALUE size_value = rb_funcall(buffer, rb_intern("size"), 0);
645
+ if (!RB_INTEGER_TYPE_P(size_value) || ip_value_negative(size_value))
646
+ rb_raise(rb_eImagePackInvalidArgumentError, "IO::Buffer#size must return Integer >= 0");
647
+ return NUM2SIZET(size_value);
648
+ }
649
+
650
+ static VALUE io_buffer_to_string_slice(VALUE buffer, size_t len) {
651
+ return rb_funcall(buffer, rb_intern("get_string"), 2, LONG2NUM(0), SIZET2NUM(len));
652
+ }
653
+
654
+ static int ip_copy_string_to_owned_input(ip_context_t *ctx, VALUE input) {
655
+ Check_Type(input, T_STRING);
656
+ size_t len = (size_t)RSTRING_LEN(input);
657
+ if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
658
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
659
+ return 0;
660
+ }
661
+
662
+ unsigned char *copy = (unsigned char *)malloc(len);
663
+ if (!copy && len > 0) {
664
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
665
+ return 0;
666
+ }
667
+ if (len > 0)
668
+ memcpy(copy, RSTRING_PTR(input), len);
669
+ ctx->owned_input_data = copy;
670
+ ctx->input_data = copy;
671
+ ctx->input_size = len;
672
+ return 1;
511
673
  }
512
674
 
513
675
  static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
@@ -518,33 +680,32 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
518
680
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
519
681
  return 0;
520
682
  }
521
- if (ctx->requested_execution == IP_EXEC_DIRECT) {
683
+
684
+ if (ctx->requested_execution == IP_EXEC_DIRECT ||
685
+ ctx->requested_execution == IP_EXEC_AUTO) {
522
686
  ctx->input_data = (const unsigned char *)RSTRING_PTR(input);
523
687
  ctx->input_size = len;
524
688
  return 1;
525
689
  }
526
690
 
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;
691
+ return ip_copy_string_to_owned_input(ctx, input);
538
692
  }
539
693
 
540
694
  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);
695
+ size_t len = io_buffer_size_or_raise(input);
544
696
  if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
545
697
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input IO::Buffer exceeds max_input_size");
546
698
  return 0;
547
699
  }
700
+
701
+ VALUE str = io_buffer_to_string_slice(input, len);
702
+ StringValue(str);
703
+ if ((size_t)RSTRING_LEN(str) != len) {
704
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
705
+ "IO::Buffer#get_string returned unexpected size");
706
+ return 0;
707
+ }
708
+
548
709
  unsigned char *copy = (unsigned char *)malloc(len);
549
710
  if (!copy && len > 0) {
550
711
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy IO::Buffer input");
@@ -563,15 +724,31 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
563
724
  return read_file_to_owned_buffer(ctx, StringValueCStr(path_value));
564
725
  }
565
726
 
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);
727
+ static int ip_ensure_owned_input_for_async(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
728
+ if (ctx->resolved_execution == IP_EXEC_DIRECT)
729
+ return 1;
730
+ if (kind != IP_INPUT_BYTES || ctx->owned_input_data)
731
+ return 1;
732
+ return ip_copy_string_to_owned_input(ctx, input);
733
+ }
734
+
735
+ static int ip_copy_string_to_owned_pixels(ip_context_t *ctx, VALUE str, size_t expected) {
736
+ unsigned char *copy = (unsigned char *)malloc(expected);
737
+ if (!copy && expected > 0) {
738
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
739
+ return 0;
572
740
  }
573
741
 
574
- StringValue(str);
742
+ if (expected > 0)
743
+ memcpy(copy, RSTRING_PTR(str), expected);
744
+ ctx->owned_pixel_data = copy;
745
+ ctx->pixel_data = copy;
746
+ ctx->pixel_size = expected;
747
+ return 1;
748
+ }
749
+
750
+ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels,
751
+ int exact_size) {
575
752
  size_t expected = 0;
576
753
  if (!ip_checked_image_size(width, height, channels, &expected)) {
577
754
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
@@ -579,34 +756,49 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
579
756
  return 0;
580
757
  }
581
758
 
582
- if ((size_t)RSTRING_LEN(str) < expected) {
759
+ VALUE str = Qnil;
760
+ size_t actual = 0;
761
+ int buffer_is_string = RB_TYPE_P(buffer, T_STRING);
762
+
763
+ if (buffer_is_string) {
764
+ str = buffer;
765
+ actual = (size_t)RSTRING_LEN(str);
766
+ } else {
767
+ actual = io_buffer_size_or_raise(buffer);
768
+ }
769
+
770
+ if (actual < expected) {
583
771
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
584
772
  "pixel buffer is smaller than width * height * channels");
585
773
  return 0;
586
774
  }
587
775
 
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;
776
+ if (exact_size && actual != expected) {
777
+ ip_context_set_error(
778
+ ctx, IP_ERR_INVALID_ARGUMENT,
779
+ "pixel buffer size must equal width * height * channels when exact_size is true");
780
+ return 0;
597
781
  }
598
782
 
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;
783
+ if (!buffer_is_string) {
784
+ str = io_buffer_to_string_slice(buffer, expected);
785
+ StringValue(str);
786
+ if ((size_t)RSTRING_LEN(str) != expected) {
787
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
788
+ "IO::Buffer#get_string returned unexpected pixel size");
789
+ return 0;
790
+ }
791
+ }
792
+
793
+ if ((ctx->requested_execution == IP_EXEC_DIRECT || ctx->requested_execution == IP_EXEC_AUTO) &&
794
+ buffer_is_string) {
795
+ ctx->pixel_data = (const unsigned char *)RSTRING_PTR(str);
796
+ ctx->pixel_size = expected;
797
+ } else {
798
+ if (!ip_copy_string_to_owned_pixels(ctx, str, expected))
799
+ return 0;
603
800
  }
604
801
 
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
802
  ctx->width = width;
611
803
  ctx->height = height;
612
804
  ctx->channels = channels;
@@ -615,6 +807,14 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
615
807
  return 1;
616
808
  }
617
809
 
810
+ static int ip_ensure_owned_pixels_for_async(ip_context_t *ctx, VALUE buffer) {
811
+ if (ctx->resolved_execution == IP_EXEC_DIRECT)
812
+ return 1;
813
+ if (!RB_TYPE_P(buffer, T_STRING) || ctx->owned_pixel_data)
814
+ return 1;
815
+ return ip_copy_string_to_owned_pixels(ctx, buffer, ctx->pixel_size);
816
+ }
817
+
618
818
  static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind) {
619
819
  if (kind == IP_OUTPUT_RETURN_STRING)
620
820
  return 1;
@@ -622,7 +822,7 @@ static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kin
622
822
  VALUE path_value = pathname_to_s(output);
623
823
  StringValue(path_value);
624
824
  const char *path = StringValueCStr(path_value);
625
- ctx->output_path = strdup(path);
825
+ ctx->output_path = ip_strdup(path);
626
826
  if (!ctx->output_path) {
627
827
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy output path");
628
828
  return 0;
@@ -666,7 +866,7 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
666
866
  rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
667
867
  }
668
868
 
669
- if (rename(tmp_path, ctx->output_path) != 0) {
869
+ if (ip_replace_file(tmp_path, ctx->output_path) != 0) {
670
870
  remove(tmp_path);
671
871
  free(tmp_path);
672
872
  rb_raise(rb_eImagePackEncodeError, "failed to move temporary JPEG output into place");
@@ -679,8 +879,12 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
679
879
  static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
680
880
  unsigned int len) {
681
881
  if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
882
+ if (ctx->preserved_marker_capacity > SIZE_MAX / 2)
883
+ return 0;
682
884
  size_t new_cap =
683
885
  ctx->preserved_marker_capacity == 0 ? 4 : ctx->preserved_marker_capacity * 2;
886
+ if (new_cap > SIZE_MAX / sizeof(*ctx->preserved_markers))
887
+ return 0;
684
888
  void *new_buf = realloc(ctx->preserved_markers, new_cap * sizeof(*ctx->preserved_markers));
685
889
  if (!new_buf)
686
890
  return 0;
@@ -812,7 +1016,7 @@ static int ip_transform_pixels_for_orientation(ip_context_t *ctx, unsigned char
812
1016
  }
813
1017
 
814
1018
  unsigned char *src = *pixels;
815
- unsigned char *dst = (unsigned char *)ip_malloc_hot(out_size);
1019
+ unsigned char *dst = (unsigned char *)malloc(out_size);
816
1020
  if (!dst && out_size > 0) {
817
1021
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate EXIF-oriented pixel buffer");
818
1022
  return 0;
@@ -894,82 +1098,71 @@ static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
894
1098
  longjmp(err->ctx->jmpbuf, 1);
895
1099
  }
896
1100
 
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;
1101
+ static const char *ip_jpeg_color_space_name(int color_space) {
1102
+ switch ((J_COLOR_SPACE)color_space) {
1103
+ case JCS_GRAYSCALE:
1104
+ return "grayscale";
1105
+ case JCS_RGB:
1106
+ return "rgb";
1107
+ case JCS_YCbCr:
1108
+ return "ycbcr";
1109
+ case JCS_CMYK:
1110
+ return "cmyk";
1111
+ case JCS_YCCK:
1112
+ return "ycck";
1113
+ default:
1114
+ return "unknown";
902
1115
  }
1116
+ }
1117
+
1118
+ static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck) {
1119
+ if (!ctx->input_data || ctx->input_size < 2 || ctx->input_data[0] != 0xFF ||
1120
+ ctx->input_data[1] != 0xD8)
1121
+ IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "input is not a JPEG image");
903
1122
 
904
1123
  struct jpeg_decompress_struct cinfo;
905
1124
  ip_jpeg_error_mgr jerr;
906
1125
  memset(&cinfo, 0, sizeof(cinfo));
907
1126
  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;
1127
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
912
1128
 
913
1129
  ctx->jmp_armed = 1;
914
- if (setjmp(ctx->jmpbuf)) {
915
- ctx->jmp_armed = 0;
916
- jpeg_destroy_decompress(&cinfo);
917
- return 0;
918
- }
1130
+ if (setjmp(ctx->jmpbuf))
1131
+ goto fail;
919
1132
 
920
1133
  jpeg_create_decompress(&cinfo);
921
1134
  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
- }
1135
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1136
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
929
1137
 
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
- }
1138
+ if (!allow_cmyk_ycck && (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1139
+ cinfo.jpeg_color_space == JCS_YCCK))
1140
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1141
+ "CMYK/YCCK JPEG input is not supported for pixel recompression");
938
1142
 
939
1143
  ctx->width = (int)cinfo.image_width;
940
1144
  ctx->height = (int)cinfo.image_height;
941
1145
  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;
1146
+ ctx->jpeg_color_space = (int)cinfo.jpeg_color_space;
950
1147
 
951
1148
  jpeg_destroy_decompress(&cinfo);
952
1149
  ctx->jmp_armed = 0;
953
1150
 
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
- }
1151
+ if (ctx->width <= 0 || ctx->height <= 0)
1152
+ IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG dimensions");
1153
+ if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4)
1154
+ IP_FAIL(ctx, IP_ERR_UNSUPPORTED, "JPEG component count is not supported");
958
1155
 
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
- }
1156
+ ctx->bit_depth = 8;
1157
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
1158
+ IP_FAIL(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
971
1159
 
972
- return 1;
1160
+ return ip_check_max_dimension_limits(ctx);
1161
+
1162
+ fail:
1163
+ jpeg_destroy_decompress(&cinfo);
1164
+ ctx->jmp_armed = 0;
1165
+ return 0;
973
1166
  }
974
1167
 
975
1168
  static VALUE ip_inspect_image_entry_body(VALUE ptr) {
@@ -979,8 +1172,10 @@ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
979
1172
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
980
1173
  call->ctx = ctx;
981
1174
 
1175
+ apply_configuration(call->self, ctx);
1176
+
982
1177
  if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
983
- !ip_inspect_jpeg_header(ctx)) {
1178
+ !ip_inspect_jpeg_header(ctx, 1)) {
984
1179
  ip_raise_for_status(ctx);
985
1180
  rb_raise(rb_eImagePackInvalidImageError, "failed to inspect JPEG image");
986
1181
  }
@@ -989,18 +1184,20 @@ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
989
1184
  int height = ctx->height;
990
1185
  int channels = ctx->channels;
991
1186
  int bit_depth = ctx->bit_depth;
1187
+ int color_space = ctx->jpeg_color_space;
992
1188
  size_t decoded_bytes = ctx->decoded_bytes;
993
1189
 
994
1190
  ip_context_free(ctx);
995
1191
  call->ctx = NULL;
996
1192
 
997
1193
  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));
1194
+ rb_hash_aset(hash, ip_sym("format"), ip_sym("jpeg"));
1195
+ rb_hash_aset(hash, ip_sym("width"), INT2NUM(width));
1196
+ rb_hash_aset(hash, ip_sym("height"), INT2NUM(height));
1197
+ rb_hash_aset(hash, ip_sym("channels"), INT2NUM(channels));
1198
+ rb_hash_aset(hash, ip_sym("bit_depth"), INT2NUM(bit_depth));
1199
+ rb_hash_aset(hash, ip_sym("color_space"), ip_sym(ip_jpeg_color_space_name(color_space)));
1200
+ rb_hash_aset(hash, ip_sym("decoded_bytes"), SIZET2NUM(decoded_bytes));
1004
1201
  return hash;
1005
1202
  }
1006
1203
 
@@ -1035,32 +1232,17 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
1035
1232
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1036
1233
  }
1037
1234
 
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
- }
1235
+ if (!mozjpeg_trellis_enabled)
1236
+ ip_disable_mozjpeg_trellis(cinfo);
1046
1237
 
1047
1238
  return;
1048
1239
  }
1049
1240
 
1050
1241
  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
1242
+ cinfo->dct_method = IP_FAST_DCT;
1056
1243
  cinfo->smoothing_factor = 0;
1057
1244
  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);
1245
+ ip_disable_mozjpeg_trellis(cinfo);
1064
1246
 
1065
1247
  if (progressive_requested) {
1066
1248
  jpeg_simple_progression(cinfo);
@@ -1068,73 +1250,20 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
1068
1250
  }
1069
1251
  }
1070
1252
 
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
1253
  static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
1118
1254
  struct jpeg_compress_struct cinfo;
1119
1255
  ip_jpeg_error_mgr jerr;
1120
1256
  unsigned long jpeg_size = 0;
1121
1257
  memset(&cinfo, 0, sizeof(cinfo));
1122
1258
  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;
1259
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_encode_error_exit);
1127
1260
  ctx->transient_jpeg_buf = NULL;
1128
1261
 
1129
1262
  ctx->jmp_armed = 1;
1130
1263
  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
1264
  if (ctx->status == IP_OK)
1136
1265
  ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
1137
- return 0;
1266
+ goto fail;
1138
1267
  }
1139
1268
 
1140
1269
  jpeg_create_compress(&cinfo);
@@ -1142,8 +1271,9 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
1142
1271
 
1143
1272
  cinfo.image_width = (JDIMENSION)ctx->width;
1144
1273
  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);
1274
+ cinfo.input_components = ctx->channels;
1275
+ cinfo.in_color_space =
1276
+ ctx->channels == 4 ? JCS_EXT_RGBA : color_space_for_channels(ctx->channels);
1147
1277
 
1148
1278
  configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
1149
1279
  jpeg_set_defaults(&cinfo);
@@ -1153,20 +1283,12 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
1153
1283
 
1154
1284
  jpeg_start_compress(&cinfo, TRUE);
1155
1285
 
1156
- if (!ctx->strip_metadata) {
1286
+ if (!ctx->strip_metadata)
1157
1287
  ip_write_preserved_markers(ctx, &cinfo);
1158
- }
1159
1288
 
1160
1289
  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
- }
1290
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1291
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
1170
1292
 
1171
1293
  JSAMPROW rows[16];
1172
1294
  JDIMENSION start_scanline = cinfo.next_scanline;
@@ -1174,35 +1296,33 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
1174
1296
  if (batch > 16)
1175
1297
  batch = 16;
1176
1298
 
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
- }
1299
+ for (JDIMENSION i = 0; i < batch; i++)
1300
+ rows[i] = (JSAMPROW)(ctx->pixel_data + ((size_t)(start_scanline + i) *
1301
+ (size_t)ctx->width * (size_t)ctx->channels));
1185
1302
 
1186
1303
  jpeg_write_scanlines(&cinfo, rows, batch);
1187
1304
  }
1188
1305
 
1189
1306
  jpeg_finish_compress(&cinfo);
1307
+
1308
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
1309
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
1310
+
1190
1311
  jpeg_destroy_compress(&cinfo);
1191
1312
  ctx->jmp_armed = 0;
1192
1313
 
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
1314
  ctx->output_data = ctx->transient_jpeg_buf;
1201
1315
  ctx->transient_jpeg_buf = NULL;
1202
1316
  ctx->output_size = (size_t)jpeg_size;
1203
- ctx->output_capacity = (size_t)jpeg_size;
1204
1317
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1205
1318
  return 1;
1319
+
1320
+ fail:
1321
+ jpeg_destroy_compress(&cinfo);
1322
+ free(ctx->transient_jpeg_buf);
1323
+ ctx->transient_jpeg_buf = NULL;
1324
+ ctx->jmp_armed = 0;
1325
+ return 0;
1206
1326
  }
1207
1327
 
1208
1328
  static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
@@ -1211,22 +1331,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1211
1331
  ip_jpeg_error_mgr jerr;
1212
1332
  memset(&cinfo, 0, sizeof(cinfo));
1213
1333
  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;
1334
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1218
1335
  ctx->transient_decode_buf = NULL;
1219
1336
  ctx->source_orientation = 1;
1220
1337
 
1221
1338
  ctx->jmp_armed = 1;
1222
1339
  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
1340
  if (ctx->status == IP_OK)
1228
1341
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
1229
- return 0;
1342
+ goto fail;
1230
1343
  }
1231
1344
 
1232
1345
  jpeg_create_decompress(&cinfo);
@@ -1235,118 +1348,67 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1235
1348
  jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
1236
1349
  if (!ctx->strip_metadata) {
1237
1350
  jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
1238
- for (int app = 2; app < 16; app++) {
1351
+ for (int app = 2; app < 16; app++)
1239
1352
  jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
1240
- }
1241
1353
  }
1242
1354
 
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
- }
1355
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1356
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1250
1357
 
1251
1358
  ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
1252
1359
 
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
- }
1360
+ if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
1361
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1259
1362
 
1260
1363
  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
- }
1364
+ cinfo.jpeg_color_space == JCS_YCCK)
1365
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1366
+ "CMYK/YCCK JPEG input is not supported in this release");
1268
1367
 
1269
1368
  int ch = cinfo.num_components == 1 ? 1 : 3;
1270
1369
 
1271
1370
  ctx->width = (int)cinfo.image_width;
1272
1371
  ctx->height = (int)cinfo.image_height;
1273
1372
  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
- }
1373
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
1374
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1375
+
1280
1376
  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
- }
1377
+ if (ctx->status != IP_OK)
1378
+ goto fail;
1286
1379
 
1287
1380
  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
- }
1381
+ if (fast_decode_mode)
1382
+ ip_apply_fast_decode(&cinfo);
1301
1383
 
1302
1384
  jpeg_start_decompress(&cinfo);
1303
1385
 
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
- }
1386
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &cinfo))
1387
+ goto fail;
1311
1388
 
1312
1389
  size_t row_stride = 0;
1313
1390
  size_t size = 0;
1314
1391
  if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
1315
1392
  &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
- }
1393
+ !ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size))
1394
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
1395
+
1396
+ ctx->transient_decode_buf = (unsigned char *)malloc(size);
1397
+ if (!ctx->transient_decode_buf && size > 0)
1398
+ IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
1329
1399
 
1330
1400
  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
- }
1401
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1402
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
1340
1403
 
1341
1404
  JSAMPROW rows[16];
1342
1405
  JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1343
1406
  if (batch > 16)
1344
1407
  batch = 16;
1345
1408
 
1346
- for (JDIMENSION i = 0; i < batch; i++) {
1409
+ for (JDIMENSION i = 0; i < batch; i++)
1347
1410
  rows[i] =
1348
1411
  ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1349
- }
1350
1412
 
1351
1413
  jpeg_read_scanlines(&cinfo, rows, batch);
1352
1414
  }
@@ -1366,6 +1428,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1366
1428
  free(buf);
1367
1429
  return 0;
1368
1430
  }
1431
+
1432
+ ctx->width = out_width;
1433
+ ctx->height = out_height;
1434
+ ctx->channels = ch;
1435
+ validate_limits_for_pixels(ctx);
1436
+ if (ctx->status != IP_OK) {
1437
+ free(buf);
1438
+ return 0;
1439
+ }
1369
1440
  }
1370
1441
 
1371
1442
  *pixels = buf;
@@ -1373,6 +1444,13 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1373
1444
  *height = out_height;
1374
1445
  *channels = ch;
1375
1446
  return 1;
1447
+
1448
+ fail:
1449
+ jpeg_destroy_decompress(&cinfo);
1450
+ free(ctx->transient_decode_buf);
1451
+ ctx->transient_decode_buf = NULL;
1452
+ ctx->jmp_armed = 0;
1453
+ return 0;
1376
1454
  }
1377
1455
 
1378
1456
  static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
@@ -1407,7 +1485,6 @@ static void ip_clear_output_buffer(ip_context_t *ctx) {
1407
1485
 
1408
1486
  ctx->output_data = NULL;
1409
1487
  ctx->output_size = 0;
1410
- ctx->output_capacity = 0;
1411
1488
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1412
1489
  }
1413
1490
 
@@ -1417,49 +1494,29 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1417
1494
  ip_jpeg_error_mgr jerr;
1418
1495
  memset(&cinfo, 0, sizeof(cinfo));
1419
1496
  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;
1497
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1424
1498
  ctx->transient_decode_buf = NULL;
1425
1499
 
1426
1500
  ctx->jmp_armed = 1;
1427
1501
  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
1502
  if (ctx->status == IP_OK)
1433
1503
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG luma decode failed");
1434
- return 0;
1504
+ goto fail;
1435
1505
  }
1436
1506
 
1437
1507
  jpeg_create_decompress(&cinfo);
1438
1508
  jpeg_mem_src(&cinfo, data, (unsigned long)size);
1439
1509
 
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
- }
1510
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1511
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1447
1512
 
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
- }
1513
+ if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
1514
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1454
1515
 
1455
1516
  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
- }
1517
+ cinfo.jpeg_color_space == JCS_YCCK)
1518
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1519
+ "CMYK/YCCK JPEG input is not supported in this release");
1463
1520
 
1464
1521
  int old_width = ctx->width;
1465
1522
  int old_height = ctx->height;
@@ -1474,10 +1531,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1474
1531
  ctx->height = old_height;
1475
1532
  ctx->channels = old_channels;
1476
1533
  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;
1534
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
1481
1535
  }
1482
1536
  validate_limits_for_pixels(ctx);
1483
1537
  ctx->width = old_width;
@@ -1486,63 +1540,35 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1486
1540
  size_t luma_size = ctx->decoded_bytes;
1487
1541
  ctx->decoded_bytes = old_decoded_bytes;
1488
1542
 
1489
- if (ctx->status != IP_OK) {
1490
- jpeg_destroy_decompress(&cinfo);
1491
- ctx->jmp_armed = 0;
1492
- return 0;
1493
- }
1543
+ if (ctx->status != IP_OK)
1544
+ goto fail;
1494
1545
 
1495
1546
  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;
1547
+ ip_apply_fast_decode(&cinfo);
1506
1548
 
1507
1549
  jpeg_start_decompress(&cinfo);
1508
1550
 
1509
1551
  size_t luma_stride = (size_t)cinfo.output_width;
1510
1552
  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
- }
1553
+ luma_size != luma_stride * (size_t)cinfo.output_height)
1554
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1517
1555
 
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
- }
1556
+ ctx->transient_decode_buf = (unsigned char *)malloc(luma_size);
1557
+ if (!ctx->transient_decode_buf && luma_size > 0)
1558
+ IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1525
1559
 
1526
1560
  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
- }
1561
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1562
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
1536
1563
 
1537
1564
  JSAMPROW rows[16];
1538
1565
  JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1539
1566
  if (batch > 16)
1540
1567
  batch = 16;
1541
1568
 
1542
- for (JDIMENSION i = 0; i < batch; i++) {
1569
+ for (JDIMENSION i = 0; i < batch; i++)
1543
1570
  rows[i] =
1544
1571
  ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * luma_stride);
1545
- }
1546
1572
 
1547
1573
  jpeg_read_scanlines(&cinfo, rows, batch);
1548
1574
  }
@@ -1559,6 +1585,13 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1559
1585
  *width = out_width;
1560
1586
  *height = out_height;
1561
1587
  return 1;
1588
+
1589
+ fail:
1590
+ jpeg_destroy_decompress(&cinfo);
1591
+ free(ctx->transient_decode_buf);
1592
+ ctx->transient_decode_buf = NULL;
1593
+ ctx->jmp_armed = 0;
1594
+ return 0;
1562
1595
  }
1563
1596
 
1564
1597
  static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned char *pixels,
@@ -1569,7 +1602,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1569
1602
  return NULL;
1570
1603
  }
1571
1604
 
1572
- unsigned char *luma = (unsigned char *)ip_malloc_hot(count);
1605
+ unsigned char *luma = (unsigned char *)malloc(count);
1573
1606
  if (!luma) {
1574
1607
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1575
1608
  return NULL;
@@ -1588,7 +1621,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1588
1621
  unsigned int r = src[i * 3 + 0];
1589
1622
  unsigned int g = src[i * 3 + 1];
1590
1623
  unsigned int b = src[i * 3 + 2];
1591
- dst[i] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1624
+ dst[i] = (unsigned char)((19595u * r + 38470u * g + 7471u * b + 32768u) >> 16);
1592
1625
  }
1593
1626
  return luma;
1594
1627
  }
@@ -1597,15 +1630,15 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1597
1630
  unsigned int r = src[i * 4 + 0];
1598
1631
  unsigned int g = src[i * 4 + 1];
1599
1632
  unsigned int b = src[i * 4 + 2];
1600
- dst[i] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1633
+ dst[i] = (unsigned char)((19595u * r + 38470u * g + 7471u * b + 32768u) >> 16);
1601
1634
  }
1602
1635
  return luma;
1603
1636
  }
1604
1637
 
1605
1638
  static double ip_ssim_window_score_double(int32_t n, int32_t sum_a, int32_t sum_b, int32_t sum_a2,
1606
1639
  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 */
1640
+ const double c1 = 6.5025;
1641
+ const double c2 = 58.5225;
1609
1642
 
1610
1643
  double inv_n = 1.0 / (double)n;
1611
1644
  double mean_a = (double)sum_a * inv_n;
@@ -1799,7 +1832,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1799
1832
  size_t candidate_jpeg_size = ctx->output_size;
1800
1833
  ctx->output_data = NULL;
1801
1834
  ctx->output_size = 0;
1802
- ctx->output_capacity = 0;
1803
1835
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1804
1836
 
1805
1837
  unsigned char *candidate_luma = NULL;
@@ -1865,7 +1897,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1865
1897
 
1866
1898
  ctx->output_data = best_jpeg;
1867
1899
  ctx->output_size = best_jpeg_size;
1868
- ctx->output_capacity = best_jpeg_size;
1869
1900
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1870
1901
  return 1;
1871
1902
  }
@@ -1896,20 +1927,7 @@ static int ip_validate_lossless_optimize_header(ip_context_t *ctx,
1896
1927
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
1897
1928
  return 0;
1898
1929
  }
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;
1930
+ return ip_check_max_dimension_limits(ctx);
1913
1931
  }
1914
1932
 
1915
1933
  static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
@@ -1926,24 +1944,14 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1926
1944
  memset(&dsterr, 0, sizeof(dsterr));
1927
1945
  ctx->transient_jpeg_buf = NULL;
1928
1946
 
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;
1947
+ srcinfo.err = ip_use_error(&srcerr, ctx, ip_jpeg_invalid_error_exit);
1948
+ dstinfo.err = ip_use_error(&dsterr, ctx, ip_jpeg_encode_error_exit);
1936
1949
 
1937
1950
  ctx->jmp_armed = 1;
1938
1951
  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
1952
  if (ctx->status == IP_OK)
1945
1953
  ip_context_set_error(ctx, IP_ERR_ENCODE, "lossless JPEG optimize failed");
1946
- return 0;
1954
+ goto fail;
1947
1955
  }
1948
1956
 
1949
1957
  jpeg_create_decompress(&srcinfo);
@@ -1951,47 +1959,23 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1951
1959
  jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
1952
1960
  ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
1953
1961
 
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
- }
1962
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1963
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1961
1964
 
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
- }
1965
+ if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK)
1966
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1970
1967
 
1971
1968
  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
- }
1969
+ if (ctx->strip_metadata && ctx->source_orientation > 1)
1970
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1971
+ "lossless optimize cannot strip EXIF Orientation without changing "
1972
+ "visual orientation; use strip_metadata: false or ImagePack.compress");
1981
1973
 
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
- }
1974
+ if (!ip_validate_lossless_optimize_header(ctx, &srcinfo))
1975
+ goto fail;
1988
1976
 
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
- }
1977
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo))
1978
+ goto fail;
1995
1979
 
1996
1980
  coef_arrays = jpeg_read_coefficients(&srcinfo);
1997
1981
  jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
@@ -2005,15 +1989,8 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
2005
1989
 
2006
1990
  jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
2007
1991
 
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
- }
1992
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1993
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
2017
1994
 
2018
1995
  jpeg_write_coefficients(&dstinfo, coef_arrays);
2019
1996
  if (!ctx->strip_metadata)
@@ -2021,23 +1998,27 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
2021
1998
 
2022
1999
  jpeg_finish_compress(&dstinfo);
2023
2000
  jpeg_finish_decompress(&srcinfo);
2001
+
2002
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
2003
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
2004
+
2024
2005
  jpeg_destroy_compress(&dstinfo);
2025
2006
  jpeg_destroy_decompress(&srcinfo);
2026
2007
  ctx->jmp_armed = 0;
2027
2008
 
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
2009
  ctx->output_data = ctx->transient_jpeg_buf;
2036
2010
  ctx->transient_jpeg_buf = NULL;
2037
2011
  ctx->output_size = (size_t)jpeg_size;
2038
- ctx->output_capacity = (size_t)jpeg_size;
2039
2012
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
2040
2013
  return 1;
2014
+
2015
+ fail:
2016
+ jpeg_destroy_compress(&dstinfo);
2017
+ jpeg_destroy_decompress(&srcinfo);
2018
+ free(ctx->transient_jpeg_buf);
2019
+ ctx->transient_jpeg_buf = NULL;
2020
+ ctx->jmp_armed = 0;
2021
+ return 0;
2041
2022
  }
2042
2023
 
2043
2024
  static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
@@ -2052,6 +2033,15 @@ static int ip_mozjpeg_compress(ip_context_t *ctx) {
2052
2033
  return compress_jpeg_input_with_mode(ctx, 1);
2053
2034
  }
2054
2035
 
2036
+ static ip_execution_t ip_async_execution(const ip_context_t *ctx) {
2037
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2038
+ return ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2039
+ #else
2040
+ (void)ctx;
2041
+ return IP_EXEC_NOGVL;
2042
+ #endif
2043
+ }
2044
+
2055
2045
  static void ip_resolve_execution(ip_context_t *ctx) {
2056
2046
  if (ctx->requested_execution != IP_EXEC_AUTO) {
2057
2047
  ctx->resolved_execution = ctx->requested_execution;
@@ -2059,12 +2049,12 @@ static void ip_resolve_execution(ip_context_t *ctx) {
2059
2049
  }
2060
2050
 
2061
2051
  if (ctx->cancellable_requested) {
2062
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2052
+ ctx->resolved_execution = ip_async_execution(ctx);
2063
2053
  return;
2064
2054
  }
2065
2055
 
2066
2056
  if (ctx->ssim_guard_enabled) {
2067
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2057
+ ctx->resolved_execution = ip_async_execution(ctx);
2068
2058
  return;
2069
2059
  }
2070
2060
 
@@ -2074,7 +2064,7 @@ static void ip_resolve_execution(ip_context_t *ctx) {
2074
2064
  return;
2075
2065
  }
2076
2066
 
2077
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2067
+ ctx->resolved_execution = ip_async_execution(ctx);
2078
2068
  }
2079
2069
 
2080
2070
  static void ip_unblock_function(void *data) {
@@ -2111,7 +2101,12 @@ static int ip_run_context(ip_context_t *ctx) {
2111
2101
  } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2112
2102
  rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, 0);
2113
2103
  } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2104
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2114
2105
  rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2106
+ #else
2107
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
2108
+ "offload execution requires Ruby >= 3.4; use :nogvl or :auto");
2109
+ #endif
2115
2110
  } else {
2116
2111
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2117
2112
  }
@@ -2134,7 +2129,12 @@ static int ip_run_optimize_context(ip_context_t *ctx) {
2134
2129
  } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2135
2130
  rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, 0);
2136
2131
  } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2132
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2137
2133
  rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2134
+ #else
2135
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
2136
+ "offload execution requires Ruby >= 3.4; use :nogvl or :auto");
2137
+ #endif
2138
2138
  } else {
2139
2139
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2140
2140
  }
@@ -2142,31 +2142,40 @@ static int ip_run_optimize_context(ip_context_t *ctx) {
2142
2142
  return ctx->status == IP_OK;
2143
2143
  }
2144
2144
 
2145
- static size_t config_size_value(VALUE config, ID id, size_t fallback) {
2145
+ static size_t config_size_value(VALUE config, ID id, size_t fallback, const char *name) {
2146
2146
  VALUE value = rb_funcall(config, id, 0);
2147
2147
  if (NIL_P(value))
2148
2148
  return fallback;
2149
+ if (!RB_INTEGER_TYPE_P(value) || ip_value_negative(value)) {
2150
+ rb_raise(rb_eImagePackInvalidArgumentError, "%s must be an Integer >= 0", name);
2151
+ }
2149
2152
  return NUM2SIZET(value);
2150
2153
  }
2151
2154
 
2152
- static int config_int_value(VALUE config, ID id, int fallback) {
2155
+ static int config_int_value(VALUE config, ID id, int fallback, const char *name) {
2153
2156
  VALUE value = rb_funcall(config, id, 0);
2154
2157
  if (NIL_P(value))
2155
2158
  return fallback;
2159
+ if (!RB_INTEGER_TYPE_P(value) || ip_value_negative(value)) {
2160
+ rb_raise(rb_eImagePackInvalidArgumentError, "%s must be an Integer >= 0", name);
2161
+ }
2156
2162
  return NUM2INT(value);
2157
2163
  }
2158
2164
 
2159
2165
  static void apply_configuration(VALUE self, ip_context_t *ctx) {
2160
2166
  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);
2167
+ ctx->direct_input_threshold = config_size_value(
2168
+ config, id_direct_input_threshold, ctx->direct_input_threshold, "direct_input_threshold");
2169
+ ctx->direct_pixel_threshold = config_size_value(
2170
+ config, id_direct_pixel_threshold, ctx->direct_pixel_threshold, "direct_pixel_threshold");
2171
+ ctx->max_pixels =
2172
+ (uint64_t)config_size_value(config, id_max_pixels, (size_t)ctx->max_pixels, "max_pixels");
2173
+ ctx->max_width = config_int_value(config, id_max_width, ctx->max_width, "max_width");
2174
+ ctx->max_height = config_int_value(config, id_max_height, ctx->max_height, "max_height");
2175
+ ctx->max_output_size =
2176
+ config_size_value(config, id_max_output_size, ctx->max_output_size, "max_output_size");
2177
+ ctx->max_input_size =
2178
+ config_size_value(config, id_max_input_size, ctx->max_input_size, "max_input_size");
2170
2179
  }
2171
2180
 
2172
2181
  static void validate_limits_for_pixels(ip_context_t *ctx) {
@@ -2180,18 +2189,8 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
2180
2189
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
2181
2190
  return;
2182
2191
  }
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");
2185
- 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");
2192
+ if (!ip_check_max_dimension_limits(ctx))
2189
2193
  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
2194
  if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &decoded_bytes)) {
2196
2195
  ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
2197
2196
  return;
@@ -2223,18 +2222,25 @@ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
2223
2222
  ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2224
2223
  apply_configuration(call->self, ctx);
2225
2224
 
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)) {
2225
+ ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
2226
+ if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2227
+ !ip_prepare_input_bytes(ctx, call->input, in_kind)) {
2228
2228
  ip_raise_for_status(ctx);
2229
2229
  rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2230
2230
  }
2231
2231
 
2232
2232
  if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2233
- !ip_inspect_jpeg_header(ctx)) {
2233
+ !ip_inspect_jpeg_header(ctx, 0)) {
2234
2234
  ip_raise_for_status(ctx);
2235
2235
  rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2236
2236
  }
2237
2237
 
2238
+ ip_resolve_execution(ctx);
2239
+ if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2240
+ ip_raise_for_status(ctx);
2241
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2242
+ }
2243
+
2238
2244
  ip_run_context(ctx);
2239
2245
  return ip_finish_output(ctx, out_kind);
2240
2246
  }
@@ -2265,6 +2271,7 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2265
2271
  ctx->min_ssim = NUM2DBL(call->min_ssim);
2266
2272
  ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
2267
2273
  ip_validate_min_ssim_or_raise(ctx);
2274
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2268
2275
  ctx->progressive = ip_bool_value(call->progressive);
2269
2276
  ctx->strip_metadata = 1;
2270
2277
  ctx->requested_execution = ip_parse_execution(call->execution);
@@ -2272,9 +2279,9 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2272
2279
  ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2273
2280
  apply_configuration(call->self, ctx);
2274
2281
 
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)) {
2282
+ if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2283
+ !ip_prepare_pixels(ctx, call->buffer, NUM2INT(call->width), NUM2INT(call->height),
2284
+ NUM2INT(call->channels), ip_bool_value(call->exact_size))) {
2278
2285
  ip_raise_for_status(ctx);
2279
2286
  rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
2280
2287
  }
@@ -2283,17 +2290,25 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2283
2290
  if (ctx->status != IP_OK)
2284
2291
  ip_raise_for_status(ctx);
2285
2292
 
2293
+ ip_resolve_execution(ctx);
2294
+ if (!ip_ensure_owned_pixels_for_async(ctx, call->buffer)) {
2295
+ ip_raise_for_status(ctx);
2296
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
2297
+ }
2298
+
2286
2299
  ip_run_context(ctx);
2287
2300
  return ip_finish_output(ctx, out_kind);
2288
2301
  }
2289
2302
 
2290
2303
  static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
2291
2304
  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) {
2305
+ VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
2306
+ VALUE progressive, VALUE exact_size, VALUE execution,
2307
+ VALUE cancellable, VALUE has_scheduler) {
2294
2308
  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};
2309
+ self, buffer, width, height, channels, output, output_kind,
2310
+ algo, quality, min_ssim, mozjpeg_trellis, progressive, exact_size, execution,
2311
+ cancellable, has_scheduler, NULL};
2297
2312
  return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
2298
2313
  (VALUE)&call.ctx);
2299
2314
  }
@@ -2314,8 +2329,21 @@ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2314
2329
  ctx->ssim_guard_enabled = 0;
2315
2330
  apply_configuration(call->self, ctx);
2316
2331
 
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)) {
2332
+ ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
2333
+ if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2334
+ !ip_prepare_input_bytes(ctx, call->input, in_kind)) {
2335
+ ip_raise_for_status(ctx);
2336
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2337
+ }
2338
+
2339
+ if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2340
+ !ip_inspect_jpeg_header(ctx, 1)) {
2341
+ ip_raise_for_status(ctx);
2342
+ rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2343
+ }
2344
+
2345
+ ip_resolve_execution(ctx);
2346
+ if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2319
2347
  ip_raise_for_status(ctx);
2320
2348
  rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2321
2349
  }
@@ -2334,37 +2362,46 @@ static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, V
2334
2362
  }
2335
2363
 
2336
2364
  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");
2365
+ static const struct {
2366
+ ID *slot;
2367
+ const char *name;
2368
+ } ids[] = {{&id_jpeg_turbo, "jpeg_turbo"},
2369
+ {&id_mozjpeg, "mozjpeg"},
2370
+ {&id_direct, "direct"},
2371
+ {&id_nogvl, "nogvl"},
2372
+ {&id_offload, "offload"},
2373
+ {&id_auto, "auto"},
2374
+ {&id_bytes, "bytes"},
2375
+ {&id_path, "path"},
2376
+ {&id_io_buffer, "io_buffer"},
2377
+ {&id_return_string, "return_string"},
2378
+ {&id_configuration, "configuration"},
2379
+ {&id_direct_input_threshold, "direct_input_threshold"},
2380
+ {&id_direct_pixel_threshold, "direct_pixel_threshold"},
2381
+ {&id_max_pixels, "max_pixels"},
2382
+ {&id_max_width, "max_width"},
2383
+ {&id_max_height, "max_height"},
2384
+ {&id_max_output_size, "max_output_size"},
2385
+ {&id_max_input_size, "max_input_size"}};
2386
+ for (size_t i = 0; i < IP_ARRAY_LEN(ids); i++)
2387
+ *ids[i].slot = rb_intern(ids[i].name);
2355
2388
 
2356
2389
  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"));
2390
+
2391
+ static const struct {
2392
+ VALUE *slot;
2393
+ const char *name;
2394
+ } exceptions[] = {{&rb_eImagePackError, "Error"},
2395
+ {&rb_eImagePackInvalidArgumentError, "InvalidArgumentError"},
2396
+ {&rb_eImagePackInvalidImageError, "InvalidImageError"},
2397
+ {&rb_eImagePackUnsupportedError, "UnsupportedError"},
2398
+ {&rb_eImagePackLimitExceededError, "LimitExceededError"},
2399
+ {&rb_eImagePackEncodeError, "EncodeError"},
2400
+ {&rb_eImagePackQualityConstraintError, "QualityConstraintError"},
2401
+ {&rb_eImagePackOutOfMemoryError, "OutOfMemoryError"},
2402
+ {&rb_eImagePackCancelledError, "CancelledError"}};
2403
+ for (size_t i = 0; i < IP_ARRAY_LEN(exceptions); i++)
2404
+ *exceptions[i].slot = rb_const_get(rb_mImagePack, rb_intern(exceptions[i].name));
2368
2405
 
2369
2406
  rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION", rb_str_new_cstr(VERSION));
2370
2407
  #if defined(IMAGE_PACK_HAS_SIMD)
@@ -2372,17 +2409,23 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
2372
2409
  #else
2373
2410
  rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qfalse);
2374
2411
  #endif
2412
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2413
+ rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE", Qtrue);
2414
+ #else
2415
+ rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE", Qfalse);
2416
+ #endif
2375
2417
 
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")));
2418
+ static const struct {
2419
+ const char *name;
2420
+ VALUE (*fn)(ANYARGS);
2421
+ int arity;
2422
+ } methods[] = {{"__compress_jpeg", (VALUE (*)(ANYARGS))ip_compress_jpeg_entry, 13},
2423
+ {"__compress_pixels", (VALUE (*)(ANYARGS))ip_compress_pixels_entry, 15},
2424
+ {"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry, 9},
2425
+ {"__inspect_image", (VALUE (*)(ANYARGS))ip_inspect_image_entry, 2}};
2426
+ for (size_t i = 0; i < IP_ARRAY_LEN(methods); i++) {
2427
+ rb_define_singleton_method(rb_mImagePack, methods[i].name, methods[i].fn, methods[i].arity);
2428
+ rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2429
+ ID2SYM(rb_intern(methods[i].name)));
2430
+ }
2388
2431
  }