image_pack 0.2.1 → 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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +75 -16
  4. data/ext/image_pack/extconf.rb +41 -126
  5. data/ext/image_pack/image_pack.c +1151 -596
  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 +124 -41
  13. metadata +13 -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
  86. data/lib/image_pack/backend.rb +0 -8
@@ -3,23 +3,28 @@
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>
18
- #include <jpeglib.h>
19
-
20
- #if defined(__linux__)
21
- #include <sys/mman.h>
23
+ #if defined(_WIN32)
24
+ #include <windows.h>
22
25
  #endif
26
+ #include <jpeglib.h>
27
+ #include <jconfigint.h>
23
28
 
24
29
  #ifndef IMAGE_PACK_INIT_EXPORT
25
30
  #if defined(_WIN32)
@@ -31,10 +36,6 @@
31
36
  #endif
32
37
  #endif
33
38
 
34
- #ifndef RB_NOGVL_OFFLOAD_SAFE
35
- #error "RB_NOGVL_OFFLOAD_SAFE is required by image_pack"
36
- #endif
37
-
38
39
  #ifndef TRUE
39
40
  #define TRUE 1
40
41
  #endif
@@ -51,6 +52,32 @@
51
52
  #define IP_RESTRICT
52
53
  #endif
53
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
+
54
81
  typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
55
82
 
56
83
  typedef enum {
@@ -90,11 +117,11 @@ typedef struct {
90
117
  int height;
91
118
  int channels;
92
119
  int bit_depth;
120
+ int jpeg_color_space;
93
121
  size_t decoded_bytes;
94
122
 
95
123
  unsigned char *output_data;
96
124
  size_t output_size;
97
- size_t output_capacity;
98
125
  ip_output_owner_t output_owner;
99
126
  char *output_path;
100
127
 
@@ -128,9 +155,6 @@ typedef struct {
128
155
  jmp_buf jmpbuf;
129
156
  int jmp_armed;
130
157
 
131
- unsigned char *scratch_row;
132
- size_t scratch_row_size;
133
-
134
158
  struct {
135
159
  int marker;
136
160
  unsigned char *data;
@@ -138,6 +162,10 @@ typedef struct {
138
162
  } *preserved_markers;
139
163
  size_t preserved_marker_count;
140
164
  size_t preserved_marker_capacity;
165
+
166
+ unsigned char *transient_jpeg_buf;
167
+ unsigned char *transient_decode_buf;
168
+ int source_orientation;
141
169
  } ip_context_t;
142
170
 
143
171
  typedef struct {
@@ -186,6 +214,9 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
186
214
  static void ip_validate_quality_or_raise(ip_context_t *ctx);
187
215
  static void ip_validate_min_ssim_or_raise(ip_context_t *ctx);
188
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);
189
220
 
190
221
  static ip_algo_t ip_parse_algo(VALUE sym);
191
222
  static ip_execution_t ip_parse_execution(VALUE sym);
@@ -193,11 +224,15 @@ static ip_input_kind_t ip_parse_input_kind(VALUE sym);
193
224
  static ip_output_kind_t ip_parse_output_kind(VALUE sym);
194
225
 
195
226
  static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
196
- 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);
197
231
  static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind);
198
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);
199
234
 
200
- 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);
201
236
  static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind);
202
237
 
203
238
  static void ip_resolve_execution(ip_context_t *ctx);
@@ -214,6 +249,40 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
214
249
  static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode);
215
250
  static int ip_jpeg_turbo_compress(ip_context_t *ctx);
216
251
  static int ip_mozjpeg_compress(ip_context_t *ctx);
252
+ static int ip_lossless_optimize_jpeg(ip_context_t *ctx);
253
+ static int ip_run_optimize_context(ip_context_t *ctx);
254
+
255
+ typedef struct {
256
+ VALUE self, input, input_kind, output, output_kind, algo, quality, min_ssim;
257
+ VALUE mozjpeg_trellis, progressive, strip_metadata, execution, cancellable, has_scheduler;
258
+ ip_context_t *ctx;
259
+ } ip_compress_jpeg_call_t;
260
+
261
+ typedef struct {
262
+ VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
263
+ VALUE mozjpeg_trellis, progressive, exact_size, execution, cancellable, has_scheduler;
264
+ ip_context_t *ctx;
265
+ } ip_compress_pixels_call_t;
266
+
267
+ typedef struct {
268
+ VALUE self, input, input_kind, output, output_kind, progressive, strip_metadata;
269
+ VALUE execution, cancellable, has_scheduler;
270
+ ip_context_t *ctx;
271
+ } ip_optimize_jpeg_call_t;
272
+
273
+ typedef struct {
274
+ VALUE self, input, input_kind;
275
+ ip_context_t *ctx;
276
+ } ip_inspect_call_t;
277
+
278
+ static VALUE ip_call_cleanup(VALUE ptr) {
279
+ ip_context_t **ctx_ptr = (ip_context_t **)ptr;
280
+ if (ctx_ptr && *ctx_ptr) {
281
+ ip_context_free(*ctx_ptr);
282
+ *ctx_ptr = NULL;
283
+ }
284
+ return Qnil;
285
+ }
217
286
 
218
287
  static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
219
288
  VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
@@ -221,8 +290,12 @@ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, V
221
290
  VALUE execution, VALUE cancellable, VALUE has_scheduler);
222
291
  static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
223
292
  VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
224
- VALUE quality, VALUE progressive, VALUE execution,
293
+ VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
294
+ VALUE progressive, VALUE exact_size, VALUE execution,
225
295
  VALUE cancellable, VALUE has_scheduler);
296
+ static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
297
+ VALUE output_kind, VALUE progressive, VALUE strip_metadata,
298
+ VALUE execution, VALUE cancellable, VALUE has_scheduler);
226
299
 
227
300
  static VALUE ip_status_to_exception(ip_status_t status) {
228
301
  switch (status) {
@@ -271,24 +344,11 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
271
344
  return ip_checked_mul_size(pixels, (size_t)channels, out);
272
345
  }
273
346
 
274
- static void *ip_malloc_hot(size_t size) {
275
- void *p = malloc(size);
276
- if (!p)
277
- return NULL;
278
- #if defined(__linux__) && defined(MADV_HUGEPAGE)
279
- if (size >= (256u * 1024u)) {
280
- (void)madvise(p, size, MADV_HUGEPAGE);
281
- }
282
- #endif
283
- return p;
284
- }
285
-
286
347
  static void ip_validate_quality_or_raise(ip_context_t *ctx) {
287
348
  if (ctx->quality >= 1 && ctx->quality <= 100)
288
349
  return;
289
350
 
290
351
  int quality = ctx->quality;
291
- ip_context_free(ctx);
292
352
  rb_raise(rb_eImagePackInvalidArgumentError, "quality must be Integer 1..100, got: %d", quality);
293
353
  }
294
354
 
@@ -299,11 +359,71 @@ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
299
359
  return;
300
360
 
301
361
  double min_ssim = ctx->min_ssim;
302
- ip_context_free(ctx);
303
362
  rb_raise(rb_eImagePackInvalidArgumentError,
304
363
  "min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
305
364
  }
306
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
+
307
427
  static int ip_bool_value(VALUE value) {
308
428
  if (NIL_P(value) || value == Qfalse)
309
429
  return 0;
@@ -330,6 +450,7 @@ static ip_context_t *ip_context_new(void) {
330
450
  ctx->max_height = 30000;
331
451
  ctx->max_output_size = 256 * 1024 * 1024;
332
452
  ctx->max_input_size = 256 * 1024 * 1024;
453
+ ctx->source_orientation = 1;
333
454
  atomic_init(&ctx->cancelled, 0);
334
455
  return ctx;
335
456
  }
@@ -341,7 +462,8 @@ static void ip_context_free(ip_context_t *ctx) {
341
462
  free(ctx->owned_input_data);
342
463
  free(ctx->owned_pixel_data);
343
464
  free(ctx->output_path);
344
- free(ctx->scratch_row);
465
+ free(ctx->transient_jpeg_buf);
466
+ free(ctx->transient_decode_buf);
345
467
 
346
468
  if (ctx->preserved_markers) {
347
469
  for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
@@ -374,46 +496,87 @@ static ID symbol_id(VALUE sym, const char *kind) {
374
496
  return SYM2ID(sym);
375
497
  }
376
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
+
377
513
  static ip_algo_t ip_parse_algo(VALUE sym) {
378
- ID id = symbol_id(sym, "algo");
379
- if (id == id_jpeg_turbo)
380
- return IP_ALGO_JPEG_TURBO;
381
- if (id == id_mozjpeg)
382
- return IP_ALGO_MOZJPEG;
383
- 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);
384
517
  }
385
518
 
386
519
  static ip_execution_t ip_parse_execution(VALUE sym) {
387
- ID id = symbol_id(sym, "execution");
388
- if (id == id_direct)
389
- return IP_EXEC_DIRECT;
390
- if (id == id_nogvl)
391
- return IP_EXEC_NOGVL;
392
- if (id == id_offload)
393
- return IP_EXEC_OFFLOAD;
394
- if (id == id_auto)
395
- return IP_EXEC_AUTO;
396
- 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);
397
526
  }
398
527
 
399
528
  static ip_input_kind_t ip_parse_input_kind(VALUE sym) {
400
- ID id = symbol_id(sym, "input kind");
401
- if (id == id_bytes)
402
- return IP_INPUT_BYTES;
403
- if (id == id_path)
404
- return IP_INPUT_PATH;
405
- if (id == id_io_buffer)
406
- return IP_INPUT_IO_BUFFER;
407
- 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);
408
534
  }
409
535
 
410
536
  static ip_output_kind_t ip_parse_output_kind(VALUE sym) {
411
- ID id = symbol_id(sym, "output kind");
412
- if (id == id_return_string)
413
- return IP_OUTPUT_RETURN_STRING;
414
- if (id == id_path)
415
- return IP_OUTPUT_PATH;
416
- 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;
417
580
  }
418
581
 
419
582
  static VALUE pathname_to_s(VALUE object) {
@@ -431,36 +594,41 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
431
594
  return 0;
432
595
  }
433
596
 
434
- if (fseek(fp, 0, SEEK_END) != 0) {
597
+ if (ip_file_seek_end(fp) != 0) {
435
598
  fclose(fp);
436
599
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to seek input path");
437
600
  return 0;
438
601
  }
439
602
 
440
- long size = ftell(fp);
441
- if (size < 0) {
603
+ size_t size = 0;
604
+ if (!ip_file_tell(fp, &size)) {
442
605
  fclose(fp);
443
606
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to determine input size");
444
607
  return 0;
445
608
  }
446
- rewind(fp);
447
609
 
448
- 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) {
449
617
  fclose(fp);
450
618
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input file exceeds max_input_size");
451
619
  return 0;
452
620
  }
453
621
 
454
- unsigned char *data = (unsigned char *)malloc((size_t)size);
622
+ unsigned char *data = (unsigned char *)malloc(size);
455
623
  if (!data && size > 0) {
456
624
  fclose(fp);
457
625
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate input file buffer");
458
626
  return 0;
459
627
  }
460
628
 
461
- size_t read_size = fread(data, 1, (size_t)size, fp);
629
+ size_t read_size = fread(data, 1, size, fp);
462
630
  fclose(fp);
463
- if (read_size != (size_t)size) {
631
+ if (read_size != size) {
464
632
  free(data);
465
633
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to read input path");
466
634
  return 0;
@@ -472,9 +640,36 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
472
640
  return 1;
473
641
  }
474
642
 
475
- static VALUE io_buffer_to_string(VALUE buffer) {
476
- VALUE size = rb_funcall(buffer, rb_intern("size"), 0);
477
- 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;
478
673
  }
479
674
 
480
675
  static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
@@ -485,27 +680,32 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
485
680
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
486
681
  return 0;
487
682
  }
488
- unsigned char *copy = (unsigned char *)malloc(len);
489
- if (!copy && len > 0) {
490
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
491
- return 0;
683
+
684
+ if (ctx->requested_execution == IP_EXEC_DIRECT ||
685
+ ctx->requested_execution == IP_EXEC_AUTO) {
686
+ ctx->input_data = (const unsigned char *)RSTRING_PTR(input);
687
+ ctx->input_size = len;
688
+ return 1;
492
689
  }
493
- if (len > 0)
494
- memcpy(copy, RSTRING_PTR(input), len);
495
- ctx->owned_input_data = copy;
496
- ctx->input_data = copy;
497
- ctx->input_size = len;
498
- return 1;
690
+
691
+ return ip_copy_string_to_owned_input(ctx, input);
499
692
  }
500
693
 
501
694
  if (kind == IP_INPUT_IO_BUFFER) {
502
- VALUE str = io_buffer_to_string(input);
503
- StringValue(str);
504
- size_t len = (size_t)RSTRING_LEN(str);
695
+ size_t len = io_buffer_size_or_raise(input);
505
696
  if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
506
697
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input IO::Buffer exceeds max_input_size");
507
698
  return 0;
508
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
+
509
709
  unsigned char *copy = (unsigned char *)malloc(len);
510
710
  if (!copy && len > 0) {
511
711
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy IO::Buffer input");
@@ -524,15 +724,31 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
524
724
  return read_file_to_owned_buffer(ctx, StringValueCStr(path_value));
525
725
  }
526
726
 
527
- static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels) {
528
- VALUE str;
529
- if (RB_TYPE_P(buffer, T_STRING)) {
530
- str = buffer;
531
- } else {
532
- 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;
533
740
  }
534
741
 
535
- 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) {
536
752
  size_t expected = 0;
537
753
  if (!ip_checked_image_size(width, height, channels, &expected)) {
538
754
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
@@ -540,23 +756,49 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
540
756
  return 0;
541
757
  }
542
758
 
543
- 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) {
544
771
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
545
772
  "pixel buffer is smaller than width * height * channels");
546
773
  return 0;
547
774
  }
548
775
 
549
- unsigned char *copy = (unsigned char *)ip_malloc_hot(expected);
550
- if (!copy && expected > 0) {
551
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
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");
552
780
  return 0;
553
781
  }
554
782
 
555
- if (expected > 0)
556
- memcpy(copy, RSTRING_PTR(str), expected);
557
- ctx->owned_pixel_data = copy;
558
- ctx->pixel_data = copy;
559
- ctx->pixel_size = expected;
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;
800
+ }
801
+
560
802
  ctx->width = width;
561
803
  ctx->height = height;
562
804
  ctx->channels = channels;
@@ -565,6 +807,14 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
565
807
  return 1;
566
808
  }
567
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
+
568
818
  static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind) {
569
819
  if (kind == IP_OUTPUT_RETURN_STRING)
570
820
  return 1;
@@ -572,7 +822,7 @@ static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kin
572
822
  VALUE path_value = pathname_to_s(output);
573
823
  StringValue(path_value);
574
824
  const char *path = StringValueCStr(path_value);
575
- ctx->output_path = strdup(path);
825
+ ctx->output_path = ip_strdup(path);
576
826
  if (!ctx->output_path) {
577
827
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy output path");
578
828
  return 0;
@@ -584,31 +834,57 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
584
834
  ip_raise_for_status(ctx);
585
835
 
586
836
  if (kind == IP_OUTPUT_RETURN_STRING) {
837
+ if (ctx->output_size > (size_t)LONG_MAX)
838
+ rb_raise(rb_eImagePackLimitExceededError, "output is too large for a Ruby String");
587
839
  VALUE out = rb_str_new((const char *)ctx->output_data, (long)ctx->output_size);
588
840
  rb_enc_associate(out, rb_ascii8bit_encoding());
589
841
  return out;
590
842
  }
591
843
 
592
- FILE *fp = fopen(ctx->output_path, "wb");
844
+ size_t path_len = strlen(ctx->output_path);
845
+ char suffix[64];
846
+ snprintf(suffix, sizeof(suffix), ".tmp.image_pack.%p", (void *)ctx);
847
+ size_t tmp_len = path_len + strlen(suffix) + 1;
848
+ char *tmp_path = (char *)malloc(tmp_len);
849
+ if (!tmp_path)
850
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate temporary output path");
851
+ snprintf(tmp_path, tmp_len, "%s%s", ctx->output_path, suffix);
852
+
853
+ FILE *fp = fopen(tmp_path, "wb");
593
854
  if (!fp) {
855
+ free(tmp_path);
594
856
  rb_raise(rb_eImagePackInvalidArgumentError, "failed to open output path: %s",
595
857
  ctx->output_path);
596
858
  }
597
859
 
598
860
  size_t written = fwrite(ctx->output_data, 1, ctx->output_size, fp);
599
- fclose(fp);
600
- if (written != ctx->output_size) {
861
+ int write_failed = written != ctx->output_size || ferror(fp);
862
+ int close_failed = fclose(fp) != 0;
863
+ if (write_failed || close_failed) {
864
+ remove(tmp_path);
865
+ free(tmp_path);
601
866
  rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
602
867
  }
603
868
 
869
+ if (ip_replace_file(tmp_path, ctx->output_path) != 0) {
870
+ remove(tmp_path);
871
+ free(tmp_path);
872
+ rb_raise(rb_eImagePackEncodeError, "failed to move temporary JPEG output into place");
873
+ }
874
+
875
+ free(tmp_path);
604
876
  return Qtrue;
605
877
  }
606
878
 
607
879
  static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
608
880
  unsigned int len) {
609
881
  if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
882
+ if (ctx->preserved_marker_capacity > SIZE_MAX / 2)
883
+ return 0;
610
884
  size_t new_cap =
611
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;
612
888
  void *new_buf = realloc(ctx->preserved_markers, new_cap * sizeof(*ctx->preserved_markers));
613
889
  if (!new_buf)
614
890
  return 0;
@@ -629,17 +905,171 @@ static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *da
629
905
  return 1;
630
906
  }
631
907
 
632
- static void ip_save_markers_from_decompress(ip_context_t *ctx,
633
- struct jpeg_decompress_struct *cinfo) {
908
+ static int ip_save_markers_from_decompress(ip_context_t *ctx,
909
+ struct jpeg_decompress_struct *cinfo) {
634
910
  jpeg_saved_marker_ptr m;
635
911
  for (m = cinfo->marker_list; m != NULL; m = m->next) {
636
912
  if (m->marker == (JPEG_APP0 + 0))
637
913
  continue;
638
914
 
639
915
  if (!ip_save_marker(ctx, m->marker, m->data, m->data_length)) {
640
- return;
916
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to preserve JPEG metadata marker");
917
+ return 0;
918
+ }
919
+ }
920
+ return 1;
921
+ }
922
+
923
+ static uint16_t ip_exif_u16(const unsigned char *p, int little_endian) {
924
+ if (little_endian)
925
+ return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
926
+ return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
927
+ }
928
+
929
+ static uint32_t ip_exif_u32(const unsigned char *p, int little_endian) {
930
+ if (little_endian)
931
+ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
932
+ ((uint32_t)p[3] << 24);
933
+ return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
934
+ }
935
+
936
+ static int ip_parse_exif_orientation(const unsigned char *data, unsigned int len) {
937
+ if (!data || len < 14)
938
+ return 1;
939
+ if (memcmp(data, "Exif\0\0", 6) != 0)
940
+ return 1;
941
+
942
+ const unsigned char *tiff = data + 6;
943
+ size_t tiff_len = (size_t)len - 6;
944
+ if (tiff_len < 8)
945
+ return 1;
946
+
947
+ int little_endian = 0;
948
+ if (tiff[0] == 'I' && tiff[1] == 'I')
949
+ little_endian = 1;
950
+ else if (tiff[0] == 'M' && tiff[1] == 'M')
951
+ little_endian = 0;
952
+ else
953
+ return 1;
954
+
955
+ if (ip_exif_u16(tiff + 2, little_endian) != 42)
956
+ return 1;
957
+
958
+ uint32_t ifd0_offset = ip_exif_u32(tiff + 4, little_endian);
959
+ if (ifd0_offset > tiff_len || tiff_len - ifd0_offset < 2)
960
+ return 1;
961
+
962
+ const unsigned char *ifd = tiff + ifd0_offset;
963
+ size_t ifd_len = tiff_len - ifd0_offset;
964
+ uint16_t entries = ip_exif_u16(ifd, little_endian);
965
+ ifd += 2;
966
+ ifd_len -= 2;
967
+
968
+ for (uint16_t i = 0; i < entries; i++) {
969
+ if (ifd_len < 12)
970
+ return 1;
971
+
972
+ uint16_t tag = ip_exif_u16(ifd, little_endian);
973
+ uint16_t type = ip_exif_u16(ifd + 2, little_endian);
974
+ uint32_t count = ip_exif_u32(ifd + 4, little_endian);
975
+
976
+ if (tag == 0x0112 && type == 3 && count == 1) {
977
+ uint16_t orientation = ip_exif_u16(ifd + 8, little_endian);
978
+ if (orientation >= 1 && orientation <= 8)
979
+ return (int)orientation;
980
+ return 1;
641
981
  }
982
+
983
+ ifd += 12;
984
+ ifd_len -= 12;
642
985
  }
986
+
987
+ return 1;
988
+ }
989
+
990
+ static int ip_read_exif_orientation_from_decompress(struct jpeg_decompress_struct *cinfo) {
991
+ jpeg_saved_marker_ptr m;
992
+ for (m = cinfo->marker_list; m != NULL; m = m->next) {
993
+ if (m->marker == (JPEG_APP0 + 1)) {
994
+ int orientation = ip_parse_exif_orientation(m->data, m->data_length);
995
+ if (orientation >= 2 && orientation <= 8)
996
+ return orientation;
997
+ }
998
+ }
999
+ return 1;
1000
+ }
1001
+
1002
+ static int ip_transform_pixels_for_orientation(ip_context_t *ctx, unsigned char **pixels,
1003
+ int *width, int *height, int channels) {
1004
+ int orientation = ctx->source_orientation;
1005
+ if (orientation <= 1 || orientation > 8)
1006
+ return 1;
1007
+
1008
+ int src_w = *width;
1009
+ int src_h = *height;
1010
+ int dst_w = (orientation >= 5 && orientation <= 8) ? src_h : src_w;
1011
+ int dst_h = (orientation >= 5 && orientation <= 8) ? src_w : src_h;
1012
+ size_t out_size = 0;
1013
+ if (!ip_checked_image_size(dst_w, dst_h, channels, &out_size)) {
1014
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "oriented image size overflows native size");
1015
+ return 0;
1016
+ }
1017
+
1018
+ unsigned char *src = *pixels;
1019
+ unsigned char *dst = (unsigned char *)malloc(out_size);
1020
+ if (!dst && out_size > 0) {
1021
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate EXIF-oriented pixel buffer");
1022
+ return 0;
1023
+ }
1024
+
1025
+ for (int y = 0; y < src_h; y++) {
1026
+ for (int x = 0; x < src_w; x++) {
1027
+ int dx = x;
1028
+ int dy = y;
1029
+ switch (orientation) {
1030
+ case 2:
1031
+ dx = src_w - 1 - x;
1032
+ dy = y;
1033
+ break;
1034
+ case 3:
1035
+ dx = src_w - 1 - x;
1036
+ dy = src_h - 1 - y;
1037
+ break;
1038
+ case 4:
1039
+ dx = x;
1040
+ dy = src_h - 1 - y;
1041
+ break;
1042
+ case 5:
1043
+ dx = y;
1044
+ dy = x;
1045
+ break;
1046
+ case 6:
1047
+ dx = src_h - 1 - y;
1048
+ dy = x;
1049
+ break;
1050
+ case 7:
1051
+ dx = src_h - 1 - y;
1052
+ dy = src_w - 1 - x;
1053
+ break;
1054
+ case 8:
1055
+ dx = y;
1056
+ dy = src_w - 1 - x;
1057
+ break;
1058
+ default:
1059
+ break;
1060
+ }
1061
+
1062
+ memcpy(dst + (((size_t)dy * (size_t)dst_w + (size_t)dx) * (size_t)channels),
1063
+ src + (((size_t)y * (size_t)src_w + (size_t)x) * (size_t)channels),
1064
+ (size_t)channels);
1065
+ }
1066
+ }
1067
+
1068
+ free(src);
1069
+ *pixels = dst;
1070
+ *width = dst_w;
1071
+ *height = dst_h;
1072
+ return 1;
643
1073
  }
644
1074
 
645
1075
  static void ip_write_preserved_markers(ip_context_t *ctx, struct jpeg_compress_struct *cinfo) {
@@ -668,107 +1098,114 @@ static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
668
1098
  longjmp(err->ctx->jmpbuf, 1);
669
1099
  }
670
1100
 
671
- static int ip_inspect_jpeg_header(ip_context_t *ctx) {
672
- if (!ctx->input_data || ctx->input_size < 2 || ctx->input_data[0] != 0xFF ||
673
- ctx->input_data[1] != 0xD8) {
674
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "input is not a JPEG image");
675
- 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";
676
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");
677
1122
 
678
1123
  struct jpeg_decompress_struct cinfo;
679
1124
  ip_jpeg_error_mgr jerr;
680
1125
  memset(&cinfo, 0, sizeof(cinfo));
681
1126
  memset(&jerr, 0, sizeof(jerr));
682
-
683
- cinfo.err = jpeg_std_error(&jerr.pub);
684
- jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
685
- jerr.ctx = ctx;
1127
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
686
1128
 
687
1129
  ctx->jmp_armed = 1;
688
- if (setjmp(ctx->jmpbuf)) {
689
- ctx->jmp_armed = 0;
690
- jpeg_destroy_decompress(&cinfo);
691
- return 0;
692
- }
1130
+ if (setjmp(ctx->jmpbuf))
1131
+ goto fail;
693
1132
 
694
1133
  jpeg_create_decompress(&cinfo);
695
1134
  jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
696
- int rc = jpeg_read_header(&cinfo, TRUE);
697
- if (rc != JPEG_HEADER_OK) {
698
- jpeg_destroy_decompress(&cinfo);
699
- ctx->jmp_armed = 0;
700
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
701
- return 0;
702
- }
1135
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1136
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
703
1137
 
704
- if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
705
- cinfo.jpeg_color_space == JCS_YCCK) {
706
- jpeg_destroy_decompress(&cinfo);
707
- ctx->jmp_armed = 0;
708
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
709
- "CMYK/YCCK JPEG input is not supported in this release");
710
- return 0;
711
- }
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");
712
1142
 
713
1143
  ctx->width = (int)cinfo.image_width;
714
1144
  ctx->height = (int)cinfo.image_height;
715
1145
  ctx->channels = cinfo.num_components;
716
- if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4) {
717
- ctx->channels = 3;
718
- }
719
- ctx->bit_depth = 8;
720
- 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;
721
1147
 
722
1148
  jpeg_destroy_decompress(&cinfo);
723
1149
  ctx->jmp_armed = 0;
724
1150
 
725
- if (ctx->width <= 0 || ctx->height <= 0) {
726
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG dimensions");
727
- return 0;
728
- }
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");
729
1155
 
730
- if (ctx->width > ctx->max_width) {
731
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
732
- return 0;
733
- }
734
- if (ctx->height > ctx->max_height) {
735
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
736
- return 0;
737
- }
738
- if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
739
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
740
- return 0;
741
- }
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");
742
1159
 
743
- 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;
744
1166
  }
745
1167
 
746
- static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
747
- (void)self;
1168
+ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
1169
+ ip_inspect_call_t *call = (ip_inspect_call_t *)ptr;
748
1170
  ip_context_t *ctx = ip_context_new();
749
1171
  if (!ctx)
750
1172
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
1173
+ call->ctx = ctx;
751
1174
 
752
- if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
753
- !ip_inspect_jpeg_header(ctx)) {
754
- VALUE exception = ip_status_to_exception(ctx->status);
755
- char message[512];
756
- snprintf(message, sizeof(message), "%s", ctx->error_message);
757
- ip_context_free(ctx);
758
- rb_raise(exception, "%s", message[0] ? message : "failed to inspect JPEG image");
1175
+ apply_configuration(call->self, ctx);
1176
+
1177
+ if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
1178
+ !ip_inspect_jpeg_header(ctx, 1)) {
1179
+ ip_raise_for_status(ctx);
1180
+ rb_raise(rb_eImagePackInvalidImageError, "failed to inspect JPEG image");
759
1181
  }
760
1182
 
761
- VALUE hash = rb_hash_new();
762
- rb_hash_aset(hash, ID2SYM(rb_intern("format")), ID2SYM(rb_intern("jpeg")));
763
- rb_hash_aset(hash, ID2SYM(rb_intern("width")), INT2NUM(ctx->width));
764
- rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(ctx->height));
765
- rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(ctx->channels));
766
- rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(ctx->bit_depth));
767
- rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(ctx->decoded_bytes));
1183
+ int width = ctx->width;
1184
+ int height = ctx->height;
1185
+ int channels = ctx->channels;
1186
+ int bit_depth = ctx->bit_depth;
1187
+ int color_space = ctx->jpeg_color_space;
1188
+ size_t decoded_bytes = ctx->decoded_bytes;
1189
+
768
1190
  ip_context_free(ctx);
1191
+ call->ctx = NULL;
1192
+
1193
+ VALUE hash = rb_hash_new();
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));
769
1201
  return hash;
770
1202
  }
771
1203
 
1204
+ static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
1205
+ ip_inspect_call_t call = {self, input, input_kind, NULL};
1206
+ return rb_ensure(ip_inspect_image_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
1207
+ }
1208
+
772
1209
  static J_COLOR_SPACE color_space_for_channels(int channels) {
773
1210
  return channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
774
1211
  }
@@ -786,103 +1223,57 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
786
1223
  if (mozjpeg_size_mode) {
787
1224
  cinfo->optimize_coding = TRUE;
788
1225
 
789
- if (!mozjpeg_trellis_enabled) {
790
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
791
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
792
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
793
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
794
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
795
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
796
- }
797
-
798
- return;
799
- }
800
-
801
- cinfo->optimize_coding = FALSE;
802
- #if defined(IMAGE_PACK_HAS_SIMD)
803
- cinfo->dct_method = JDCT_ISLOW;
804
- #else
805
- cinfo->dct_method = JDCT_FASTEST;
806
- #endif
807
- cinfo->smoothing_factor = 0;
808
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
809
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
810
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
811
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
812
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
813
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
814
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
815
-
816
- if (progressive_requested) {
817
- jpeg_simple_progression(cinfo);
818
- cinfo->optimize_coding = TRUE;
819
- }
820
- }
821
-
822
- static int prepare_encode_row(ip_context_t *ctx, JDIMENSION y, JSAMPROW *row) {
823
- if (ctx->channels == 4) {
824
- size_t rgb_row_size = 0;
825
- if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size)) {
826
- ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
827
- return 0;
1226
+ if (progressive_requested) {
1227
+ jpeg_simple_progression(cinfo);
1228
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, TRUE);
1229
+ } else {
1230
+ cinfo->scan_info = NULL;
1231
+ cinfo->num_scans = 0;
1232
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
828
1233
  }
829
1234
 
830
- if (ctx->scratch_row_size < rgb_row_size) {
831
- unsigned char *new_row = (unsigned char *)realloc(ctx->scratch_row, rgb_row_size);
832
- if (!new_row) {
833
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch row");
834
- return 0;
835
- }
836
- ctx->scratch_row = new_row;
837
- ctx->scratch_row_size = rgb_row_size;
838
- }
1235
+ if (!mozjpeg_trellis_enabled)
1236
+ ip_disable_mozjpeg_trellis(cinfo);
839
1237
 
840
- const unsigned char *IP_RESTRICT src =
841
- ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
842
- unsigned char *IP_RESTRICT dst = ctx->scratch_row;
843
- const int w = ctx->width;
844
- for (int x = 0; x < w; x++) {
845
- dst[x * 3 + 0] = src[x * 4 + 0];
846
- dst[x * 3 + 1] = src[x * 4 + 1];
847
- dst[x * 3 + 2] = src[x * 4 + 2];
848
- }
849
- *row = ctx->scratch_row;
850
- return 1;
1238
+ return;
851
1239
  }
852
1240
 
853
- *row = (JSAMPROW)(ctx->pixel_data + ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
854
- return 1;
1241
+ cinfo->optimize_coding = FALSE;
1242
+ cinfo->dct_method = IP_FAST_DCT;
1243
+ cinfo->smoothing_factor = 0;
1244
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1245
+ ip_disable_mozjpeg_trellis(cinfo);
1246
+
1247
+ if (progressive_requested) {
1248
+ jpeg_simple_progression(cinfo);
1249
+ cinfo->optimize_coding = TRUE;
1250
+ }
855
1251
  }
856
1252
 
857
1253
  static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
858
1254
  struct jpeg_compress_struct cinfo;
859
1255
  ip_jpeg_error_mgr jerr;
1256
+ unsigned long jpeg_size = 0;
860
1257
  memset(&cinfo, 0, sizeof(cinfo));
861
1258
  memset(&jerr, 0, sizeof(jerr));
862
-
863
- cinfo.err = jpeg_std_error(&jerr.pub);
864
- jerr.pub.error_exit = ip_jpeg_encode_error_exit;
865
- jerr.ctx = ctx;
1259
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_encode_error_exit);
1260
+ ctx->transient_jpeg_buf = NULL;
866
1261
 
867
1262
  ctx->jmp_armed = 1;
868
1263
  if (setjmp(ctx->jmpbuf)) {
869
- ctx->jmp_armed = 0;
870
- jpeg_destroy_compress(&cinfo);
871
1264
  if (ctx->status == IP_OK)
872
1265
  ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
873
- return 0;
1266
+ goto fail;
874
1267
  }
875
1268
 
876
1269
  jpeg_create_compress(&cinfo);
877
-
878
- unsigned char *jpeg_buf = NULL;
879
- unsigned long jpeg_size = 0;
880
- jpeg_mem_dest(&cinfo, &jpeg_buf, &jpeg_size);
1270
+ jpeg_mem_dest(&cinfo, &ctx->transient_jpeg_buf, &jpeg_size);
881
1271
 
882
1272
  cinfo.image_width = (JDIMENSION)ctx->width;
883
1273
  cinfo.image_height = (JDIMENSION)ctx->height;
884
- cinfo.input_components = ctx->channels == 4 ? 3 : ctx->channels;
885
- 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);
886
1277
 
887
1278
  configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
888
1279
  jpeg_set_defaults(&cinfo);
@@ -892,63 +1283,46 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
892
1283
 
893
1284
  jpeg_start_compress(&cinfo, TRUE);
894
1285
 
895
- if (!ctx->strip_metadata) {
1286
+ if (!ctx->strip_metadata)
896
1287
  ip_write_preserved_markers(ctx, &cinfo);
897
- }
898
1288
 
899
1289
  while (cinfo.next_scanline < cinfo.image_height) {
900
- if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
901
- ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
902
- jpeg_abort_compress(&cinfo);
903
- jpeg_destroy_compress(&cinfo);
904
- free(jpeg_buf);
905
- ctx->jmp_armed = 0;
906
- return 0;
907
- }
908
-
909
- if (ctx->channels == 4) {
910
- JSAMPROW row = NULL;
911
- if (!prepare_encode_row(ctx, cinfo.next_scanline, &row)) {
912
- jpeg_abort_compress(&cinfo);
913
- jpeg_destroy_compress(&cinfo);
914
- free(jpeg_buf);
915
- ctx->jmp_armed = 0;
916
- return 0;
917
- }
918
-
919
- jpeg_write_scanlines(&cinfo, &row, 1);
920
- continue;
921
- }
1290
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1291
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
922
1292
 
923
1293
  JSAMPROW rows[16];
924
- JDIMENSION batch = cinfo.image_height - cinfo.next_scanline;
1294
+ JDIMENSION start_scanline = cinfo.next_scanline;
1295
+ JDIMENSION batch = cinfo.image_height - start_scanline;
925
1296
  if (batch > 16)
926
1297
  batch = 16;
927
1298
 
928
- for (JDIMENSION i = 0; i < batch; i++) {
929
- JDIMENSION y = cinfo.next_scanline + i;
930
- rows[i] = (JSAMPROW)(ctx->pixel_data +
931
- ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
932
- }
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));
933
1302
 
934
1303
  jpeg_write_scanlines(&cinfo, rows, batch);
935
1304
  }
936
1305
 
937
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
+
938
1311
  jpeg_destroy_compress(&cinfo);
939
1312
  ctx->jmp_armed = 0;
940
1313
 
941
- if ((size_t)jpeg_size > ctx->max_output_size) {
942
- free(jpeg_buf);
943
- ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
944
- return 0;
945
- }
946
-
947
- ctx->output_data = jpeg_buf;
1314
+ ctx->output_data = ctx->transient_jpeg_buf;
1315
+ ctx->transient_jpeg_buf = NULL;
948
1316
  ctx->output_size = (size_t)jpeg_size;
949
- ctx->output_capacity = (size_t)jpeg_size;
950
1317
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
951
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;
952
1326
  }
953
1327
 
954
1328
  static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
@@ -957,120 +1331,84 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
957
1331
  ip_jpeg_error_mgr jerr;
958
1332
  memset(&cinfo, 0, sizeof(cinfo));
959
1333
  memset(&jerr, 0, sizeof(jerr));
960
-
961
- cinfo.err = jpeg_std_error(&jerr.pub);
962
- jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
963
- jerr.ctx = ctx;
1334
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1335
+ ctx->transient_decode_buf = NULL;
1336
+ ctx->source_orientation = 1;
964
1337
 
965
1338
  ctx->jmp_armed = 1;
966
1339
  if (setjmp(ctx->jmpbuf)) {
967
- ctx->jmp_armed = 0;
968
- jpeg_destroy_decompress(&cinfo);
969
1340
  if (ctx->status == IP_OK)
970
1341
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
971
- return 0;
1342
+ goto fail;
972
1343
  }
973
1344
 
974
1345
  jpeg_create_decompress(&cinfo);
975
1346
  jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
976
1347
 
1348
+ jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
977
1349
  if (!ctx->strip_metadata) {
978
1350
  jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
979
- for (int app = 1; app < 16; app++) {
1351
+ for (int app = 2; app < 16; app++)
980
1352
  jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
981
- }
982
1353
  }
983
1354
 
984
- int rc = jpeg_read_header(&cinfo, TRUE);
985
- if (rc != JPEG_HEADER_OK) {
986
- jpeg_destroy_decompress(&cinfo);
987
- ctx->jmp_armed = 0;
988
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
989
- return 0;
990
- }
1355
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1356
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
991
1357
 
992
- if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
993
- jpeg_destroy_decompress(&cinfo);
994
- ctx->jmp_armed = 0;
995
- ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
996
- return 0;
997
- }
1358
+ ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
1359
+
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");
998
1362
 
999
1363
  if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1000
- cinfo.jpeg_color_space == JCS_YCCK) {
1001
- jpeg_destroy_decompress(&cinfo);
1002
- ctx->jmp_armed = 0;
1003
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1004
- "CMYK/YCCK JPEG input is not supported in this release");
1005
- return 0;
1006
- }
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");
1007
1367
 
1008
1368
  int ch = cinfo.num_components == 1 ? 1 : 3;
1009
1369
 
1010
1370
  ctx->width = (int)cinfo.image_width;
1011
1371
  ctx->height = (int)cinfo.image_height;
1012
1372
  ctx->channels = ch;
1013
- if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes)) {
1014
- jpeg_destroy_decompress(&cinfo);
1015
- ctx->jmp_armed = 0;
1016
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1017
- return 0;
1018
- }
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
+
1019
1376
  validate_limits_for_pixels(ctx);
1020
- if (ctx->status != IP_OK) {
1021
- jpeg_destroy_decompress(&cinfo);
1022
- ctx->jmp_armed = 0;
1023
- return 0;
1024
- }
1377
+ if (ctx->status != IP_OK)
1378
+ goto fail;
1025
1379
 
1026
1380
  cinfo.out_color_space = ch == 1 ? JCS_GRAYSCALE : JCS_RGB;
1027
-
1028
- if (fast_decode_mode) {
1029
- #if defined(IMAGE_PACK_HAS_SIMD)
1030
- cinfo.dct_method = JDCT_ISLOW;
1031
- #else
1032
- cinfo.dct_method = JDCT_FASTEST;
1033
- #endif
1034
- cinfo.do_fancy_upsampling = FALSE;
1035
- cinfo.do_block_smoothing = FALSE;
1036
- cinfo.quantize_colors = FALSE;
1037
- cinfo.two_pass_quantize = FALSE;
1038
- cinfo.dither_mode = JDITHER_NONE;
1039
- }
1381
+ if (fast_decode_mode)
1382
+ ip_apply_fast_decode(&cinfo);
1040
1383
 
1041
1384
  jpeg_start_decompress(&cinfo);
1042
1385
 
1043
- if (!ctx->strip_metadata && !fast_decode_mode) {
1044
- ip_save_markers_from_decompress(ctx, &cinfo);
1045
- }
1386
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &cinfo))
1387
+ goto fail;
1046
1388
 
1047
1389
  size_t row_stride = 0;
1048
1390
  size_t size = 0;
1049
1391
  if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
1050
1392
  &row_stride) ||
1051
- !ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size)) {
1052
- jpeg_destroy_decompress(&cinfo);
1053
- ctx->jmp_armed = 0;
1054
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
1055
- return 0;
1056
- }
1057
- unsigned char *buf = (unsigned char *)ip_malloc_hot(size);
1058
- if (!buf && size > 0) {
1059
- jpeg_destroy_decompress(&cinfo);
1060
- ctx->jmp_armed = 0;
1061
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
1062
- return 0;
1063
- }
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");
1064
1399
 
1065
1400
  while (cinfo.output_scanline < cinfo.output_height) {
1401
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1402
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
1403
+
1066
1404
  JSAMPROW rows[16];
1067
1405
  JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1068
1406
  if (batch > 16)
1069
1407
  batch = 16;
1070
1408
 
1071
- for (JDIMENSION i = 0; i < batch; i++) {
1072
- rows[i] = buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1073
- }
1409
+ for (JDIMENSION i = 0; i < batch; i++)
1410
+ rows[i] =
1411
+ ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1074
1412
 
1075
1413
  jpeg_read_scanlines(&cinfo, rows, batch);
1076
1414
  }
@@ -1082,11 +1420,37 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1082
1420
  jpeg_destroy_decompress(&cinfo);
1083
1421
  ctx->jmp_armed = 0;
1084
1422
 
1423
+ unsigned char *buf = ctx->transient_decode_buf;
1424
+ ctx->transient_decode_buf = NULL;
1425
+
1426
+ if (ctx->strip_metadata && ctx->source_orientation > 1) {
1427
+ if (!ip_transform_pixels_for_orientation(ctx, &buf, &out_width, &out_height, ch)) {
1428
+ free(buf);
1429
+ return 0;
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
+ }
1440
+ }
1441
+
1085
1442
  *pixels = buf;
1086
1443
  *width = out_width;
1087
1444
  *height = out_height;
1088
1445
  *channels = ch;
1089
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;
1090
1454
  }
1091
1455
 
1092
1456
  static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
@@ -1121,7 +1485,6 @@ static void ip_clear_output_buffer(ip_context_t *ctx) {
1121
1485
 
1122
1486
  ctx->output_data = NULL;
1123
1487
  ctx->output_size = 0;
1124
- ctx->output_capacity = 0;
1125
1488
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1126
1489
  }
1127
1490
 
@@ -1131,48 +1494,30 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1131
1494
  ip_jpeg_error_mgr jerr;
1132
1495
  memset(&cinfo, 0, sizeof(cinfo));
1133
1496
  memset(&jerr, 0, sizeof(jerr));
1134
-
1135
- cinfo.err = jpeg_std_error(&jerr.pub);
1136
- jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1137
- jerr.ctx = ctx;
1497
+ cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1498
+ ctx->transient_decode_buf = NULL;
1138
1499
 
1139
1500
  ctx->jmp_armed = 1;
1140
1501
  if (setjmp(ctx->jmpbuf)) {
1141
- ctx->jmp_armed = 0;
1142
- jpeg_destroy_decompress(&cinfo);
1143
1502
  if (ctx->status == IP_OK)
1144
1503
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG luma decode failed");
1145
- return 0;
1504
+ goto fail;
1146
1505
  }
1147
1506
 
1148
1507
  jpeg_create_decompress(&cinfo);
1149
1508
  jpeg_mem_src(&cinfo, data, (unsigned long)size);
1150
1509
 
1151
- int rc = jpeg_read_header(&cinfo, TRUE);
1152
- if (rc != JPEG_HEADER_OK) {
1153
- jpeg_destroy_decompress(&cinfo);
1154
- ctx->jmp_armed = 0;
1155
- ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1156
- return 0;
1157
- }
1510
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1511
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1158
1512
 
1159
- if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
1160
- jpeg_destroy_decompress(&cinfo);
1161
- ctx->jmp_armed = 0;
1162
- ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1163
- return 0;
1164
- }
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");
1165
1515
 
1166
1516
  if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1167
- cinfo.jpeg_color_space == JCS_YCCK) {
1168
- jpeg_destroy_decompress(&cinfo);
1169
- ctx->jmp_armed = 0;
1170
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1171
- "CMYK/YCCK JPEG input is not supported in this release");
1172
- return 0;
1173
- }
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");
1174
1520
 
1175
- int out_channels = cinfo.num_components == 1 ? 1 : 3;
1176
1521
  int old_width = ctx->width;
1177
1522
  int old_height = ctx->height;
1178
1523
  int old_channels = ctx->channels;
@@ -1186,10 +1531,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1186
1531
  ctx->height = old_height;
1187
1532
  ctx->channels = old_channels;
1188
1533
  ctx->decoded_bytes = old_decoded_bytes;
1189
- jpeg_destroy_decompress(&cinfo);
1190
- ctx->jmp_armed = 0;
1191
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
1192
- return 0;
1534
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
1193
1535
  }
1194
1536
  validate_limits_for_pixels(ctx);
1195
1537
  ctx->width = old_width;
@@ -1198,110 +1540,58 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1198
1540
  size_t luma_size = ctx->decoded_bytes;
1199
1541
  ctx->decoded_bytes = old_decoded_bytes;
1200
1542
 
1201
- if (ctx->status != IP_OK) {
1202
- jpeg_destroy_decompress(&cinfo);
1203
- ctx->jmp_armed = 0;
1204
- return 0;
1205
- }
1543
+ if (ctx->status != IP_OK)
1544
+ goto fail;
1206
1545
 
1207
- cinfo.out_color_space = out_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
1208
- #if defined(IMAGE_PACK_HAS_SIMD)
1209
- cinfo.dct_method = JDCT_ISLOW;
1210
- #else
1211
- cinfo.dct_method = JDCT_FASTEST;
1212
- #endif
1213
- cinfo.do_fancy_upsampling = FALSE;
1214
- cinfo.do_block_smoothing = FALSE;
1215
- cinfo.quantize_colors = FALSE;
1216
- cinfo.two_pass_quantize = FALSE;
1217
- cinfo.dither_mode = JDITHER_NONE;
1546
+ cinfo.out_color_space = JCS_GRAYSCALE;
1547
+ ip_apply_fast_decode(&cinfo);
1218
1548
 
1219
1549
  jpeg_start_decompress(&cinfo);
1220
1550
 
1221
1551
  size_t luma_stride = (size_t)cinfo.output_width;
1222
- size_t row_stride = 0;
1223
- if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
1224
- &row_stride) ||
1225
- luma_stride == 0 || luma_size != luma_stride * (size_t)cinfo.output_height) {
1226
- jpeg_destroy_decompress(&cinfo);
1227
- ctx->jmp_armed = 0;
1228
- ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1229
- return 0;
1230
- }
1231
-
1232
- unsigned char *buf = (unsigned char *)ip_malloc_hot(luma_size);
1233
- if (!buf && luma_size > 0) {
1234
- jpeg_destroy_decompress(&cinfo);
1235
- ctx->jmp_armed = 0;
1236
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1237
- return 0;
1238
- }
1552
+ if (cinfo.output_components != 1 || luma_stride == 0 ||
1553
+ luma_size != luma_stride * (size_t)cinfo.output_height)
1554
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1239
1555
 
1240
- unsigned char *scratch = NULL;
1241
- if (out_channels != 1) {
1242
- size_t scratch_size = 0;
1243
- if (!ip_checked_mul_size(row_stride, 16, &scratch_size)) {
1244
- free(buf);
1245
- jpeg_destroy_decompress(&cinfo);
1246
- ctx->jmp_armed = 0;
1247
- ip_context_set_error(ctx, IP_ERR_LIMIT, "luma decode scratch size overflow");
1248
- return 0;
1249
- }
1250
- scratch = (unsigned char *)ip_malloc_hot(scratch_size);
1251
- if (!scratch) {
1252
- free(buf);
1253
- jpeg_destroy_decompress(&cinfo);
1254
- ctx->jmp_armed = 0;
1255
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma decode scratch row");
1256
- return 0;
1257
- }
1258
- }
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");
1259
1559
 
1260
1560
  while (cinfo.output_scanline < cinfo.output_height) {
1561
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1562
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
1563
+
1261
1564
  JSAMPROW rows[16];
1262
- JDIMENSION start_scanline = cinfo.output_scanline;
1263
- JDIMENSION batch = cinfo.output_height - start_scanline;
1565
+ JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1264
1566
  if (batch > 16)
1265
1567
  batch = 16;
1266
1568
 
1267
- if (out_channels == 1) {
1268
- for (JDIMENSION i = 0; i < batch; i++) {
1269
- rows[i] = buf + ((size_t)(start_scanline + i) * luma_stride);
1270
- }
1271
- } else {
1272
- for (JDIMENSION i = 0; i < batch; i++) {
1273
- rows[i] = scratch + ((size_t)i * row_stride);
1274
- }
1275
- }
1569
+ for (JDIMENSION i = 0; i < batch; i++)
1570
+ rows[i] =
1571
+ ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * luma_stride);
1276
1572
 
1277
- JDIMENSION lines_read = jpeg_read_scanlines(&cinfo, rows, batch);
1278
-
1279
- if (out_channels != 1) {
1280
- for (JDIMENSION y = 0; y < lines_read; y++) {
1281
- const unsigned char *IP_RESTRICT src = scratch + ((size_t)y * row_stride);
1282
- unsigned char *IP_RESTRICT dst = buf + ((size_t)(start_scanline + y) * luma_stride);
1283
- for (size_t x = 0; x < luma_stride; x++) {
1284
- unsigned int r = src[x * 3 + 0];
1285
- unsigned int g = src[x * 3 + 1];
1286
- unsigned int b = src[x * 3 + 2];
1287
- dst[x] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1288
- }
1289
- }
1290
- }
1573
+ jpeg_read_scanlines(&cinfo, rows, batch);
1291
1574
  }
1292
1575
 
1293
1576
  int out_width = (int)cinfo.output_width;
1294
1577
  int out_height = (int)cinfo.output_height;
1295
1578
 
1296
- free(scratch);
1297
1579
  jpeg_finish_decompress(&cinfo);
1298
1580
  jpeg_destroy_decompress(&cinfo);
1299
1581
  ctx->jmp_armed = 0;
1300
1582
 
1301
- *luma = buf;
1583
+ *luma = ctx->transient_decode_buf;
1584
+ ctx->transient_decode_buf = NULL;
1302
1585
  *width = out_width;
1303
1586
  *height = out_height;
1304
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;
1305
1595
  }
1306
1596
 
1307
1597
  static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned char *pixels,
@@ -1312,7 +1602,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1312
1602
  return NULL;
1313
1603
  }
1314
1604
 
1315
- unsigned char *luma = (unsigned char *)ip_malloc_hot(count);
1605
+ unsigned char *luma = (unsigned char *)malloc(count);
1316
1606
  if (!luma) {
1317
1607
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1318
1608
  return NULL;
@@ -1331,7 +1621,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1331
1621
  unsigned int r = src[i * 3 + 0];
1332
1622
  unsigned int g = src[i * 3 + 1];
1333
1623
  unsigned int b = src[i * 3 + 2];
1334
- 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);
1335
1625
  }
1336
1626
  return luma;
1337
1627
  }
@@ -1340,15 +1630,15 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1340
1630
  unsigned int r = src[i * 4 + 0];
1341
1631
  unsigned int g = src[i * 4 + 1];
1342
1632
  unsigned int b = src[i * 4 + 2];
1343
- 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);
1344
1634
  }
1345
1635
  return luma;
1346
1636
  }
1347
1637
 
1348
1638
  static double ip_ssim_window_score_double(int32_t n, int32_t sum_a, int32_t sum_b, int32_t sum_a2,
1349
1639
  int32_t sum_b2, int32_t sum_ab) {
1350
- const double c1 = 6.5025; /* (0.01 * 255)^2 */
1351
- const double c2 = 58.5225; /* (0.03 * 255)^2 */
1640
+ const double c1 = 6.5025;
1641
+ const double c2 = 58.5225;
1352
1642
 
1353
1643
  double inv_n = 1.0 / (double)n;
1354
1644
  double mean_a = (double)sum_a * inv_n;
@@ -1480,7 +1770,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1480
1770
  int reference_channels = 0;
1481
1771
 
1482
1772
  if (ctx->pixel_data) {
1483
- reference_pixels = ctx->owned_pixel_data;
1773
+ reference_pixels = (unsigned char *)ctx->pixel_data;
1484
1774
  reference_width = ctx->width;
1485
1775
  reference_height = ctx->height;
1486
1776
  reference_channels = ctx->channels;
@@ -1502,7 +1792,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1502
1792
 
1503
1793
  if (reference_channels == 4) {
1504
1794
  ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1505
- "min_ssim is not supported for RGBA input in v0.2.1");
1795
+ "min_ssim is not supported for RGBA input in v0.2.2");
1506
1796
  return 0;
1507
1797
  }
1508
1798
 
@@ -1542,7 +1832,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1542
1832
  size_t candidate_jpeg_size = ctx->output_size;
1543
1833
  ctx->output_data = NULL;
1544
1834
  ctx->output_size = 0;
1545
- ctx->output_capacity = 0;
1546
1835
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1547
1836
 
1548
1837
  unsigned char *candidate_luma = NULL;
@@ -1608,11 +1897,130 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1608
1897
 
1609
1898
  ctx->output_data = best_jpeg;
1610
1899
  ctx->output_size = best_jpeg_size;
1611
- ctx->output_capacity = best_jpeg_size;
1612
1900
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1613
1901
  return 1;
1614
1902
  }
1615
1903
 
1904
+ static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, int strip_metadata) {
1905
+ jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xFFFF);
1906
+ if (!strip_metadata) {
1907
+ jpeg_save_markers(cinfo, JPEG_COM, 0xFFFF);
1908
+ for (int app = 2; app < 16; app++) {
1909
+ jpeg_save_markers(cinfo, JPEG_APP0 + app, 0xFFFF);
1910
+ }
1911
+ }
1912
+ }
1913
+
1914
+ static int ip_validate_lossless_optimize_header(ip_context_t *ctx,
1915
+ struct jpeg_decompress_struct *srcinfo) {
1916
+ if (srcinfo->image_width > (JDIMENSION)INT_MAX || srcinfo->image_height > (JDIMENSION)INT_MAX) {
1917
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1918
+ return 0;
1919
+ }
1920
+
1921
+ ctx->width = (int)srcinfo->image_width;
1922
+ ctx->height = (int)srcinfo->image_height;
1923
+ ctx->channels = srcinfo->num_components;
1924
+ ctx->bit_depth = 8;
1925
+
1926
+ if (ctx->max_width < 0 || ctx->max_height < 0) {
1927
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
1928
+ return 0;
1929
+ }
1930
+ return ip_check_max_dimension_limits(ctx);
1931
+ }
1932
+
1933
+ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1934
+ struct jpeg_decompress_struct srcinfo;
1935
+ struct jpeg_compress_struct dstinfo;
1936
+ ip_jpeg_error_mgr srcerr;
1937
+ ip_jpeg_error_mgr dsterr;
1938
+ jvirt_barray_ptr *coef_arrays = NULL;
1939
+ unsigned long jpeg_size = 0;
1940
+
1941
+ memset(&srcinfo, 0, sizeof(srcinfo));
1942
+ memset(&dstinfo, 0, sizeof(dstinfo));
1943
+ memset(&srcerr, 0, sizeof(srcerr));
1944
+ memset(&dsterr, 0, sizeof(dsterr));
1945
+ ctx->transient_jpeg_buf = NULL;
1946
+
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);
1949
+
1950
+ ctx->jmp_armed = 1;
1951
+ if (setjmp(ctx->jmpbuf)) {
1952
+ if (ctx->status == IP_OK)
1953
+ ip_context_set_error(ctx, IP_ERR_ENCODE, "lossless JPEG optimize failed");
1954
+ goto fail;
1955
+ }
1956
+
1957
+ jpeg_create_decompress(&srcinfo);
1958
+ jpeg_create_compress(&dstinfo);
1959
+ jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
1960
+ ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
1961
+
1962
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1963
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1964
+
1965
+ if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK)
1966
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1967
+
1968
+ ctx->source_orientation = ip_read_exif_orientation_from_decompress(&srcinfo);
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");
1973
+
1974
+ if (!ip_validate_lossless_optimize_header(ctx, &srcinfo))
1975
+ goto fail;
1976
+
1977
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo))
1978
+ goto fail;
1979
+
1980
+ coef_arrays = jpeg_read_coefficients(&srcinfo);
1981
+ jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
1982
+ dstinfo.optimize_coding = TRUE;
1983
+ dstinfo.num_scans = 0;
1984
+ dstinfo.scan_info = NULL;
1985
+ if (ctx->progressive) {
1986
+ jpeg_simple_progression(&dstinfo);
1987
+ dstinfo.optimize_coding = TRUE;
1988
+ }
1989
+
1990
+ jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
1991
+
1992
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
1993
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1994
+
1995
+ jpeg_write_coefficients(&dstinfo, coef_arrays);
1996
+ if (!ctx->strip_metadata)
1997
+ ip_write_preserved_markers(ctx, &dstinfo);
1998
+
1999
+ jpeg_finish_compress(&dstinfo);
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
+
2005
+ jpeg_destroy_compress(&dstinfo);
2006
+ jpeg_destroy_decompress(&srcinfo);
2007
+ ctx->jmp_armed = 0;
2008
+
2009
+ ctx->output_data = ctx->transient_jpeg_buf;
2010
+ ctx->transient_jpeg_buf = NULL;
2011
+ ctx->output_size = (size_t)jpeg_size;
2012
+ ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
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;
2022
+ }
2023
+
1616
2024
  static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
1617
2025
  if (ctx->ssim_guard_enabled)
1618
2026
  return guarded_compress_jpeg_input_with_mode(ctx, 0);
@@ -1625,6 +2033,15 @@ static int ip_mozjpeg_compress(ip_context_t *ctx) {
1625
2033
  return compress_jpeg_input_with_mode(ctx, 1);
1626
2034
  }
1627
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
+
1628
2045
  static void ip_resolve_execution(ip_context_t *ctx) {
1629
2046
  if (ctx->requested_execution != IP_EXEC_AUTO) {
1630
2047
  ctx->resolved_execution = ctx->requested_execution;
@@ -1632,12 +2049,12 @@ static void ip_resolve_execution(ip_context_t *ctx) {
1632
2049
  }
1633
2050
 
1634
2051
  if (ctx->cancellable_requested) {
1635
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2052
+ ctx->resolved_execution = ip_async_execution(ctx);
1636
2053
  return;
1637
2054
  }
1638
2055
 
1639
2056
  if (ctx->ssim_guard_enabled) {
1640
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2057
+ ctx->resolved_execution = ip_async_execution(ctx);
1641
2058
  return;
1642
2059
  }
1643
2060
 
@@ -1647,7 +2064,7 @@ static void ip_resolve_execution(ip_context_t *ctx) {
1647
2064
  return;
1648
2065
  }
1649
2066
 
1650
- ctx->resolved_execution = ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
2067
+ ctx->resolved_execution = ip_async_execution(ctx);
1651
2068
  }
1652
2069
 
1653
2070
  static void ip_unblock_function(void *data) {
@@ -1684,7 +2101,40 @@ static int ip_run_context(ip_context_t *ctx) {
1684
2101
  } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
1685
2102
  rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, 0);
1686
2103
  } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2104
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
1687
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
2110
+ } else {
2111
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2112
+ }
2113
+
2114
+ return ctx->status == IP_OK;
2115
+ }
2116
+
2117
+ static void *ip_run_optimize_nogvl(void *data) {
2118
+ ip_context_t *ctx = (ip_context_t *)data;
2119
+ if (ctx->status == IP_OK)
2120
+ ip_lossless_optimize_jpeg(ctx);
2121
+ return NULL;
2122
+ }
2123
+
2124
+ static int ip_run_optimize_context(ip_context_t *ctx) {
2125
+ ip_resolve_execution(ctx);
2126
+
2127
+ if (ctx->resolved_execution == IP_EXEC_DIRECT) {
2128
+ ip_run_optimize_nogvl(ctx);
2129
+ } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2130
+ rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, 0);
2131
+ } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2132
+ #if IMAGE_PACK_HAS_OFFLOAD_SAFE
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
1688
2138
  } else {
1689
2139
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
1690
2140
  }
@@ -1692,31 +2142,40 @@ static int ip_run_context(ip_context_t *ctx) {
1692
2142
  return ctx->status == IP_OK;
1693
2143
  }
1694
2144
 
1695
- 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) {
1696
2146
  VALUE value = rb_funcall(config, id, 0);
1697
2147
  if (NIL_P(value))
1698
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
+ }
1699
2152
  return NUM2SIZET(value);
1700
2153
  }
1701
2154
 
1702
- 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) {
1703
2156
  VALUE value = rb_funcall(config, id, 0);
1704
2157
  if (NIL_P(value))
1705
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
+ }
1706
2162
  return NUM2INT(value);
1707
2163
  }
1708
2164
 
1709
2165
  static void apply_configuration(VALUE self, ip_context_t *ctx) {
1710
2166
  VALUE config = rb_funcall(self, id_configuration, 0);
1711
- ctx->direct_input_threshold =
1712
- config_size_value(config, id_direct_input_threshold, ctx->direct_input_threshold);
1713
- ctx->direct_pixel_threshold =
1714
- config_size_value(config, id_direct_pixel_threshold, ctx->direct_pixel_threshold);
1715
- ctx->max_pixels = (uint64_t)config_size_value(config, id_max_pixels, (size_t)ctx->max_pixels);
1716
- ctx->max_width = config_int_value(config, id_max_width, ctx->max_width);
1717
- ctx->max_height = config_int_value(config, id_max_height, ctx->max_height);
1718
- ctx->max_output_size = config_size_value(config, id_max_output_size, ctx->max_output_size);
1719
- 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");
1720
2179
  }
1721
2180
 
1722
2181
  static void validate_limits_for_pixels(ip_context_t *ctx) {
@@ -1726,18 +2185,12 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
1726
2185
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "image dimensions must be positive");
1727
2186
  return;
1728
2187
  }
1729
- if (ctx->width > ctx->max_width) {
1730
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
2188
+ if (ctx->max_width < 0 || ctx->max_height < 0) {
2189
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
1731
2190
  return;
1732
2191
  }
1733
- if (ctx->height > ctx->max_height) {
1734
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
2192
+ if (!ip_check_max_dimension_limits(ctx))
1735
2193
  return;
1736
- }
1737
- if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
1738
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1739
- return;
1740
- }
1741
2194
  if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &decoded_bytes)) {
1742
2195
  ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1743
2196
  return;
@@ -1746,131 +2199,233 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
1746
2199
  ctx->decoded_bytes = decoded_bytes;
1747
2200
  }
1748
2201
 
2202
+ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
2203
+ ip_compress_jpeg_call_t *call = (ip_compress_jpeg_call_t *)ptr;
2204
+ ip_context_t *ctx = ip_context_new();
2205
+ if (!ctx)
2206
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2207
+ call->ctx = ctx;
2208
+
2209
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2210
+ ctx->algo = ip_parse_algo(call->algo);
2211
+ ctx->quality = NUM2INT(call->quality);
2212
+ ctx->selected_quality = ctx->quality;
2213
+ ip_validate_quality_or_raise(ctx);
2214
+ ctx->min_ssim = NUM2DBL(call->min_ssim);
2215
+ ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
2216
+ ip_validate_min_ssim_or_raise(ctx);
2217
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2218
+ ctx->progressive = ip_bool_value(call->progressive);
2219
+ ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2220
+ ctx->requested_execution = ip_parse_execution(call->execution);
2221
+ ctx->cancellable_requested = ip_bool_value(call->cancellable);
2222
+ ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2223
+ apply_configuration(call->self, ctx);
2224
+
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
+ ip_raise_for_status(ctx);
2229
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2230
+ }
2231
+
2232
+ if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2233
+ !ip_inspect_jpeg_header(ctx, 0)) {
2234
+ ip_raise_for_status(ctx);
2235
+ rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2236
+ }
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
+
2244
+ ip_run_context(ctx);
2245
+ return ip_finish_output(ctx, out_kind);
2246
+ }
2247
+
1749
2248
  static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
1750
2249
  VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
1751
2250
  VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
1752
2251
  VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2252
+ ip_compress_jpeg_call_t call = {
2253
+ self, input, input_kind, output, output_kind,
2254
+ algo, quality, min_ssim, mozjpeg_trellis, progressive,
2255
+ strip_metadata, execution, cancellable, has_scheduler, NULL};
2256
+ return rb_ensure(ip_compress_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
2257
+ }
2258
+
2259
+ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2260
+ ip_compress_pixels_call_t *call = (ip_compress_pixels_call_t *)ptr;
1753
2261
  ip_context_t *ctx = ip_context_new();
1754
2262
  if (!ctx)
1755
2263
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2264
+ call->ctx = ctx;
1756
2265
 
1757
- ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1758
- ctx->algo = ip_parse_algo(algo);
1759
- ctx->quality = NUM2INT(quality);
2266
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2267
+ ctx->algo = ip_parse_algo(call->algo);
2268
+ ctx->quality = NUM2INT(call->quality);
1760
2269
  ctx->selected_quality = ctx->quality;
1761
2270
  ip_validate_quality_or_raise(ctx);
1762
- ctx->min_ssim = NUM2DBL(min_ssim);
2271
+ ctx->min_ssim = NUM2DBL(call->min_ssim);
1763
2272
  ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
1764
2273
  ip_validate_min_ssim_or_raise(ctx);
1765
- ctx->mozjpeg_trellis_enabled = ip_bool_value(mozjpeg_trellis);
1766
- ctx->progressive = ip_bool_value(progressive);
1767
- ctx->strip_metadata = ip_bool_value(strip_metadata);
1768
- ctx->requested_execution = ip_parse_execution(execution);
1769
- ctx->cancellable_requested = ip_bool_value(cancellable);
1770
- ctx->has_scheduler = ip_bool_value(has_scheduler);
1771
- apply_configuration(self, ctx);
1772
-
1773
- if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
1774
- !ip_prepare_output_path(ctx, output, out_kind)) {
1775
- VALUE exception = ip_status_to_exception(ctx->status);
1776
- char message[512];
1777
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1778
- ip_context_free(ctx);
1779
- rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
2274
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2275
+ ctx->progressive = ip_bool_value(call->progressive);
2276
+ ctx->strip_metadata = 1;
2277
+ ctx->requested_execution = ip_parse_execution(call->execution);
2278
+ ctx->cancellable_requested = ip_bool_value(call->cancellable);
2279
+ ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2280
+ apply_configuration(call->self, ctx);
2281
+
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))) {
2285
+ ip_raise_for_status(ctx);
2286
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
1780
2287
  }
1781
2288
 
1782
- if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
1783
- !ip_inspect_jpeg_header(ctx)) {
1784
- VALUE exception = ip_status_to_exception(ctx->status);
1785
- char message[512];
1786
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1787
- ip_context_free(ctx);
1788
- rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
2289
+ validate_limits_for_pixels(ctx);
2290
+ if (ctx->status != IP_OK)
2291
+ ip_raise_for_status(ctx);
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");
1789
2297
  }
1790
2298
 
1791
2299
  ip_run_context(ctx);
1792
- VALUE result = ip_finish_output(ctx, out_kind);
1793
- ip_context_free(ctx);
1794
- return result;
2300
+ return ip_finish_output(ctx, out_kind);
1795
2301
  }
1796
2302
 
1797
2303
  static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
1798
2304
  VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
1799
- VALUE quality, VALUE progressive, VALUE execution,
2305
+ VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
2306
+ VALUE progressive, VALUE exact_size, VALUE execution,
1800
2307
  VALUE cancellable, VALUE has_scheduler) {
2308
+ ip_compress_pixels_call_t call = {
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};
2312
+ return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
2313
+ (VALUE)&call.ctx);
2314
+ }
2315
+
2316
+ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2317
+ ip_optimize_jpeg_call_t *call = (ip_optimize_jpeg_call_t *)ptr;
1801
2318
  ip_context_t *ctx = ip_context_new();
1802
2319
  if (!ctx)
1803
2320
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2321
+ call->ctx = ctx;
1804
2322
 
1805
- ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1806
- ctx->algo = ip_parse_algo(algo);
1807
- ctx->quality = NUM2INT(quality);
1808
- ip_validate_quality_or_raise(ctx);
1809
- ctx->progressive = ip_bool_value(progressive);
1810
- ctx->strip_metadata = 1;
1811
- ctx->requested_execution = ip_parse_execution(execution);
1812
- ctx->cancellable_requested = ip_bool_value(cancellable);
1813
- ctx->has_scheduler = ip_bool_value(has_scheduler);
1814
- apply_configuration(self, ctx);
1815
-
1816
- if (!ip_prepare_pixels(ctx, buffer, NUM2INT(width), NUM2INT(height), NUM2INT(channels)) ||
1817
- !ip_prepare_output_path(ctx, output, out_kind)) {
1818
- VALUE exception = ip_status_to_exception(ctx->status);
1819
- char message[512];
1820
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1821
- ip_context_free(ctx);
1822
- rb_raise(exception, "%s", message[0] ? message : "invalid pixel input");
2323
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2324
+ ctx->progressive = ip_bool_value(call->progressive);
2325
+ ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2326
+ ctx->requested_execution = ip_parse_execution(call->execution);
2327
+ ctx->cancellable_requested = ip_bool_value(call->cancellable);
2328
+ ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2329
+ ctx->ssim_guard_enabled = 0;
2330
+ apply_configuration(call->self, ctx);
2331
+
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");
1823
2337
  }
1824
2338
 
1825
- validate_limits_for_pixels(ctx);
1826
- if (ctx->status != IP_OK) {
1827
- VALUE exception = ip_status_to_exception(ctx->status);
1828
- char message[512];
1829
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1830
- ip_context_free(ctx);
1831
- rb_raise(exception, "%s", message);
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");
1832
2343
  }
1833
2344
 
1834
- ip_run_context(ctx);
1835
- VALUE result = ip_finish_output(ctx, out_kind);
1836
- ip_context_free(ctx);
1837
- return result;
2345
+ ip_resolve_execution(ctx);
2346
+ if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2347
+ ip_raise_for_status(ctx);
2348
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2349
+ }
2350
+
2351
+ ip_run_optimize_context(ctx);
2352
+ return ip_finish_output(ctx, out_kind);
2353
+ }
2354
+
2355
+ static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
2356
+ VALUE output_kind, VALUE progressive, VALUE strip_metadata,
2357
+ VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2358
+ ip_optimize_jpeg_call_t call = {
2359
+ self, input, input_kind, output, output_kind, progressive,
2360
+ strip_metadata, execution, cancellable, has_scheduler, NULL};
2361
+ return rb_ensure(ip_optimize_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
1838
2362
  }
1839
2363
 
1840
2364
  IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
1841
- id_jpeg_turbo = rb_intern("jpeg_turbo");
1842
- id_mozjpeg = rb_intern("mozjpeg");
1843
- id_direct = rb_intern("direct");
1844
- id_nogvl = rb_intern("nogvl");
1845
- id_offload = rb_intern("offload");
1846
- id_auto = rb_intern("auto");
1847
- id_bytes = rb_intern("bytes");
1848
- id_path = rb_intern("path");
1849
- id_io_buffer = rb_intern("io_buffer");
1850
- id_return_string = rb_intern("return_string");
1851
- id_configuration = rb_intern("configuration");
1852
- id_direct_input_threshold = rb_intern("direct_input_threshold");
1853
- id_direct_pixel_threshold = rb_intern("direct_pixel_threshold");
1854
- id_max_pixels = rb_intern("max_pixels");
1855
- id_max_width = rb_intern("max_width");
1856
- id_max_height = rb_intern("max_height");
1857
- id_max_output_size = rb_intern("max_output_size");
1858
- 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);
1859
2388
 
1860
2389
  rb_mImagePack = rb_define_module("ImagePack");
1861
- rb_eImagePackError = rb_const_get(rb_mImagePack, rb_intern("Error"));
1862
- rb_eImagePackInvalidArgumentError =
1863
- rb_const_get(rb_mImagePack, rb_intern("InvalidArgumentError"));
1864
- rb_eImagePackInvalidImageError = rb_const_get(rb_mImagePack, rb_intern("InvalidImageError"));
1865
- rb_eImagePackUnsupportedError = rb_const_get(rb_mImagePack, rb_intern("UnsupportedError"));
1866
- rb_eImagePackLimitExceededError = rb_const_get(rb_mImagePack, rb_intern("LimitExceededError"));
1867
- rb_eImagePackEncodeError = rb_const_get(rb_mImagePack, rb_intern("EncodeError"));
1868
- rb_eImagePackQualityConstraintError =
1869
- rb_const_get(rb_mImagePack, rb_intern("QualityConstraintError"));
1870
- rb_eImagePackOutOfMemoryError = rb_const_get(rb_mImagePack, rb_intern("OutOfMemoryError"));
1871
- rb_eImagePackCancelledError = rb_const_get(rb_mImagePack, rb_intern("CancelledError"));
1872
-
1873
- rb_define_singleton_method(rb_mImagePack, "__compress_jpeg", ip_compress_jpeg_entry, 13);
1874
- rb_define_singleton_method(rb_mImagePack, "__compress_pixels", ip_compress_pixels_entry, 12);
1875
- rb_define_singleton_method(rb_mImagePack, "__inspect_image", ip_inspect_image_entry, 2);
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));
2405
+
2406
+ rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION", rb_str_new_cstr(VERSION));
2407
+ #if defined(IMAGE_PACK_HAS_SIMD)
2408
+ rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qtrue);
2409
+ #else
2410
+ rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qfalse);
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
2417
+
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
+ }
1876
2431
  }