image_pack 0.2.5 → 0.2.7

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +29 -5
  4. data/ext/image_pack/extconf.rb +2 -2
  5. data/ext/image_pack/image_pack.c +742 -263
  6. data/ext/image_pack/mozjpeg_sources.rb +5 -0
  7. data/ext/image_pack/vendor/mozjpeg/transupp.c +2373 -0
  8. data/lib/image_pack/version.rb +1 -1
  9. data/lib/image_pack.rb +121 -7
  10. metadata +6 -104
  11. data/ext/image_pack/vendor/mozjpeg/croptest.in +0 -95
  12. data/ext/image_pack/vendor/mozjpeg/libjpeg.map.in +0 -11
  13. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jsimd_neon.S +0 -1200
  14. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch64/jsimd_neon.S +0 -2254
  15. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolext-avx2.asm +0 -578
  16. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolext-mmx.asm +0 -476
  17. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolext-sse2.asm +0 -503
  18. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolor-avx2.asm +0 -121
  19. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolor-mmx.asm +0 -121
  20. data/ext/image_pack/vendor/mozjpeg/simd/i386/jccolor-sse2.asm +0 -120
  21. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgray-avx2.asm +0 -113
  22. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgray-mmx.asm +0 -113
  23. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgray-sse2.asm +0 -112
  24. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgryext-avx2.asm +0 -457
  25. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgryext-mmx.asm +0 -355
  26. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcgryext-sse2.asm +0 -382
  27. data/ext/image_pack/vendor/mozjpeg/simd/i386/jchuff-sse2.asm +0 -761
  28. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcphuff-sse2.asm +0 -662
  29. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcsample-avx2.asm +0 -388
  30. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcsample-mmx.asm +0 -324
  31. data/ext/image_pack/vendor/mozjpeg/simd/i386/jcsample-sse2.asm +0 -351
  32. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolext-avx2.asm +0 -515
  33. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolext-mmx.asm +0 -404
  34. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolext-sse2.asm +0 -458
  35. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolor-avx2.asm +0 -118
  36. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolor-mmx.asm +0 -117
  37. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdcolor-sse2.asm +0 -117
  38. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmerge-avx2.asm +0 -136
  39. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmerge-mmx.asm +0 -123
  40. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmerge-sse2.asm +0 -135
  41. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmrgext-avx2.asm +0 -575
  42. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmrgext-mmx.asm +0 -460
  43. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdmrgext-sse2.asm +0 -517
  44. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdsample-avx2.asm +0 -760
  45. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdsample-mmx.asm +0 -731
  46. data/ext/image_pack/vendor/mozjpeg/simd/i386/jdsample-sse2.asm +0 -724
  47. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctflt-3dn.asm +0 -318
  48. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctflt-sse.asm +0 -369
  49. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctfst-mmx.asm +0 -395
  50. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctfst-sse2.asm +0 -403
  51. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctint-avx2.asm +0 -331
  52. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctint-mmx.asm +0 -620
  53. data/ext/image_pack/vendor/mozjpeg/simd/i386/jfdctint-sse2.asm +0 -633
  54. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctflt-3dn.asm +0 -451
  55. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctflt-sse.asm +0 -571
  56. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctflt-sse2.asm +0 -497
  57. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctfst-mmx.asm +0 -499
  58. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctfst-sse2.asm +0 -501
  59. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctint-avx2.asm +0 -453
  60. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctint-mmx.asm +0 -851
  61. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctint-sse2.asm +0 -858
  62. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctred-mmx.asm +0 -704
  63. data/ext/image_pack/vendor/mozjpeg/simd/i386/jidctred-sse2.asm +0 -592
  64. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquant-3dn.asm +0 -230
  65. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquant-mmx.asm +0 -276
  66. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquant-sse.asm +0 -208
  67. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquantf-sse2.asm +0 -168
  68. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquanti-avx2.asm +0 -188
  69. data/ext/image_pack/vendor/mozjpeg/simd/i386/jquanti-sse2.asm +0 -201
  70. data/ext/image_pack/vendor/mozjpeg/simd/i386/jsimdcpu.asm +0 -135
  71. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd_dspr2.S +0 -4543
  72. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd_dspr2_asm.h +0 -292
  73. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcsample.h +0 -28
  74. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jsimd_mmi.h +0 -69
  75. data/ext/image_pack/vendor/mozjpeg/simd/mips64/loongson-mmintrin.h +0 -1334
  76. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcsample.h +0 -28
  77. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jsimd_altivec.h +0 -98
  78. data/ext/image_pack/vendor/mozjpeg/tjbenchtest.in +0 -256
  79. data/ext/image_pack/vendor/mozjpeg/tjbenchtest.java.in +0 -215
  80. data/ext/image_pack/vendor/mozjpeg/tjexampletest.in +0 -149
  81. data/ext/image_pack/vendor/mozjpeg/tjexampletest.java.in +0 -151
@@ -25,6 +25,7 @@
25
25
  #endif
26
26
  #include <jpeglib.h>
27
27
  #include <jconfigint.h>
28
+ #include <transupp.h>
28
29
 
29
30
  #ifndef IMAGE_PACK_INIT_EXPORT
30
31
  #if defined(_WIN32)
@@ -82,6 +83,21 @@
82
83
 
83
84
  #define IP_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
84
85
 
86
+ #define IP_MAX_MARKERS 64
87
+ #define IP_MAX_PROFILE_MARKERS 256
88
+ #define IP_MAX_MARKER_BYTES (8u * 1024 * 1024)
89
+ #define IP_MAX_ICC_BYTES (4u * 1024 * 1024)
90
+ #define IP_AUTO_444_QUALITY 90
91
+
92
+ typedef enum {
93
+ IP_SUBSAMP_AUTO = 0,
94
+ IP_SUBSAMP_420 = 1,
95
+ IP_SUBSAMP_422 = 2,
96
+ IP_SUBSAMP_444 = 3
97
+ } ip_subsamp_t;
98
+ typedef enum { IP_TUNE_HVS = 0, IP_TUNE_SSIM = 1, IP_TUNE_MS_SSIM = 2, IP_TUNE_PSNR = 3 } ip_tune_t;
99
+ typedef enum { IP_EFFORT_DEFAULT = 0, IP_EFFORT_FAST = 1, IP_EFFORT_MAX = 2 } ip_effort_t;
100
+
85
101
  typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
86
102
 
87
103
  typedef enum {
@@ -136,6 +152,19 @@ typedef struct {
136
152
  int selected_quality;
137
153
  int progressive;
138
154
  int strip_metadata;
155
+ int strip_icc;
156
+ int trim_partial_mcus;
157
+ int subsampling;
158
+ int subsampling_active;
159
+ int tune;
160
+ int effort;
161
+ unsigned int scale_num;
162
+ unsigned int scale_denom;
163
+ unsigned char *icc_profile;
164
+ unsigned int icc_len;
165
+ size_t preserved_marker_bytes;
166
+ size_t preserved_misc_marker_count;
167
+ size_t preserved_profile_marker_count;
139
168
  int mozjpeg_trellis_enabled;
140
169
  int mozjpeg_scan_opt_enabled;
141
170
  ip_algo_t algo;
@@ -262,24 +291,38 @@ static int ip_jpeg_turbo_compress(ip_context_t *ctx);
262
291
  static int ip_mozjpeg_compress(ip_context_t *ctx);
263
292
  static int ip_lossless_optimize_jpeg(ip_context_t *ctx);
264
293
  static int ip_run_optimize_context(ip_context_t *ctx);
294
+ static void configure_mozjpeg_profile_before_defaults(struct jpeg_compress_struct *cinfo,
295
+ int mozjpeg_size_mode);
296
+ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struct *cinfo,
297
+ const ip_context_t *ctx,
298
+ int mozjpeg_size_mode, int measurement);
299
+ static void ip_configure_encoder(struct jpeg_compress_struct *cinfo, ip_context_t *ctx,
300
+ int mozjpeg_size_mode, int measurement);
301
+ static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, const ip_context_t *ctx);
302
+ static int ip_scan_jpeg_marker_budget(ip_context_t *ctx);
303
+ static int ip_capture_icc(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo);
304
+ static void ip_write_sidecar_metadata(ip_context_t *ctx, struct jpeg_compress_struct *cinfo);
305
+ static int ip_open_source_jpeg(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo);
306
+ static int ip_ingest_source_metadata(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo);
265
307
 
266
308
  typedef struct {
267
309
  VALUE self, input, input_kind, output, output_kind, algo, quality, min_ssim;
268
310
  VALUE mozjpeg_trellis, progressive, strip_metadata, execution, cancellable, has_scheduler;
269
- VALUE report, strict, mozjpeg_scan_opt;
311
+ VALUE report, strict, mozjpeg_scan_opt, strip_icc, subsampling, tune, effort;
312
+ VALUE scale_num, scale_denom;
270
313
  ip_context_t *ctx;
271
314
  } ip_compress_jpeg_call_t;
272
315
 
273
316
  typedef struct {
274
317
  VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
275
318
  VALUE mozjpeg_trellis, progressive, exact_size, execution, cancellable, has_scheduler;
276
- VALUE report, strict, mozjpeg_scan_opt;
319
+ VALUE report, strict, mozjpeg_scan_opt, subsampling, tune, effort;
277
320
  ip_context_t *ctx;
278
321
  } ip_compress_pixels_call_t;
279
322
 
280
323
  typedef struct {
281
324
  VALUE self, input, input_kind, output, output_kind, progressive, strip_metadata;
282
- VALUE execution, cancellable, has_scheduler, strict;
325
+ VALUE execution, cancellable, has_scheduler, strict, strip_icc, trim;
283
326
  ip_context_t *ctx;
284
327
  } ip_optimize_jpeg_call_t;
285
328
 
@@ -302,7 +345,7 @@ static VALUE ip_compress_pixels_entry(int argc, VALUE *argv, VALUE self);
302
345
  static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
303
346
  VALUE output_kind, VALUE progressive, VALUE strip_metadata,
304
347
  VALUE execution, VALUE cancellable, VALUE has_scheduler,
305
- VALUE strict);
348
+ VALUE strict, VALUE strip_icc, VALUE trim);
306
349
 
307
350
  static VALUE ip_status_to_exception(ip_status_t status) {
308
351
  switch (status) {
@@ -448,6 +491,11 @@ static ip_context_t *ip_context_new(void) {
448
491
  ctx->quality = 82;
449
492
  ctx->mozjpeg_trellis_enabled = 1;
450
493
  ctx->mozjpeg_scan_opt_enabled = 1;
494
+ ctx->scale_num = 1;
495
+ ctx->scale_denom = 1;
496
+ ctx->subsampling = IP_SUBSAMP_AUTO;
497
+ ctx->tune = IP_TUNE_HVS;
498
+ ctx->effort = IP_EFFORT_DEFAULT;
451
499
  ctx->selected_quality = 82;
452
500
  ctx->requested_execution = IP_EXEC_AUTO;
453
501
  ctx->resolved_execution = IP_EXEC_AUTO;
@@ -472,6 +520,7 @@ static void ip_context_free(ip_context_t *ctx) {
472
520
  free(ctx->output_path);
473
521
  free(ctx->transient_jpeg_buf);
474
522
  free(ctx->transient_decode_buf);
523
+ free(ctx->icc_profile);
475
524
 
476
525
  if (ctx->preserved_markers) {
477
526
  for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
@@ -592,6 +641,96 @@ static struct jpeg_error_mgr *ip_use_error(ip_jpeg_error_mgr *jerr, ip_context_t
592
641
  return base;
593
642
  }
594
643
 
644
+ static void ip_jpeg_longjmp_error(j_common_ptr cinfo, ip_status_t status) {
645
+ ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
646
+ char buffer[JMSG_LENGTH_MAX];
647
+ (*cinfo->err->format_message)(cinfo, buffer);
648
+ ip_context_set_error(err->ctx, status, buffer);
649
+ if (err->ctx->jmp_armed)
650
+ longjmp(err->ctx->jmpbuf, 1);
651
+ }
652
+
653
+ static void ip_jpeg_invalid_error_exit(j_common_ptr cinfo) {
654
+ ip_jpeg_longjmp_error(cinfo, IP_ERR_INVALID_IMAGE);
655
+ }
656
+
657
+ static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
658
+ ip_jpeg_longjmp_error(cinfo, IP_ERR_ENCODE);
659
+ }
660
+
661
+ static void ip_init_decompress(struct jpeg_decompress_struct *cinfo, ip_jpeg_error_mgr *jerr,
662
+ ip_context_t *ctx) {
663
+ memset(cinfo, 0, sizeof(*cinfo));
664
+ memset(jerr, 0, sizeof(*jerr));
665
+ cinfo->err = ip_use_error(jerr, ctx, ip_jpeg_invalid_error_exit);
666
+ }
667
+
668
+ static void ip_init_compress(struct jpeg_compress_struct *cinfo, ip_jpeg_error_mgr *jerr,
669
+ ip_context_t *ctx) {
670
+ memset(cinfo, 0, sizeof(*cinfo));
671
+ memset(jerr, 0, sizeof(*jerr));
672
+ cinfo->err = ip_use_error(jerr, ctx, ip_jpeg_encode_error_exit);
673
+ }
674
+
675
+ static void ip_open_mem_src(struct jpeg_decompress_struct *cinfo, const unsigned char *data,
676
+ size_t size) {
677
+ jpeg_create_decompress(cinfo);
678
+ jpeg_mem_src(cinfo, data, (unsigned long)size);
679
+ }
680
+
681
+ static void ip_close_codec_pair(struct jpeg_compress_struct *dst,
682
+ struct jpeg_decompress_struct *src, ip_context_t *ctx) {
683
+ jpeg_destroy_compress(dst);
684
+ jpeg_destroy_decompress(src);
685
+ ctx->jmp_armed = 0;
686
+ }
687
+
688
+ static void ip_claim_jpeg_output(ip_context_t *ctx, unsigned long jpeg_size) {
689
+ ctx->output_data = ctx->transient_jpeg_buf;
690
+ ctx->transient_jpeg_buf = NULL;
691
+ ctx->output_size = (size_t)jpeg_size;
692
+ ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
693
+ }
694
+
695
+ static int ip_within_output_limit(ip_context_t *ctx, unsigned long jpeg_size) {
696
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
697
+ IP_FAIL(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
698
+ return 1;
699
+ }
700
+
701
+ static int ip_header_fits_int(ip_context_t *ctx, const struct jpeg_decompress_struct *cinfo) {
702
+ if (cinfo->image_width > (JDIMENSION)INT_MAX || cinfo->image_height > (JDIMENSION)INT_MAX)
703
+ IP_FAIL(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
704
+ return 1;
705
+ }
706
+
707
+ static int ip_reject_cmyk(ip_context_t *ctx, const struct jpeg_decompress_struct *cinfo) {
708
+ if (cinfo->num_components == 4 || cinfo->jpeg_color_space == JCS_CMYK ||
709
+ cinfo->jpeg_color_space == JCS_YCCK)
710
+ IP_FAIL(ctx, IP_ERR_UNSUPPORTED, "CMYK/YCCK JPEG input is not supported in this release");
711
+ return 1;
712
+ }
713
+
714
+ static void ip_apply_decode_scale(struct jpeg_decompress_struct *cinfo, const ip_context_t *ctx) {
715
+ if (ctx->scale_num > 0 && ctx->scale_denom > 0) {
716
+ cinfo->scale_num = ctx->scale_num;
717
+ cinfo->scale_denom = ctx->scale_denom;
718
+ }
719
+ }
720
+
721
+ static void ip_luma_samp(int mode, int *h, int *v) {
722
+ if (mode == IP_SUBSAMP_444) {
723
+ *h = 1;
724
+ *v = 1;
725
+ } else if (mode == IP_SUBSAMP_422) {
726
+ *h = 2;
727
+ *v = 1;
728
+ } else {
729
+ *h = 2;
730
+ *v = 2;
731
+ }
732
+ }
733
+
595
734
  static void ip_apply_fast_decode(struct jpeg_decompress_struct *cinfo) {
596
735
  cinfo->dct_method = IP_FAST_DCT;
597
736
  cinfo->do_fancy_upsampling = FALSE;
@@ -607,7 +746,176 @@ static void ip_disable_mozjpeg_trellis(struct jpeg_compress_struct *cinfo) {
607
746
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
608
747
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
609
748
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
610
- jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
749
+ }
750
+
751
+ static int ip_resolve_subsampling(const ip_context_t *ctx) {
752
+ if (ctx->subsampling != IP_SUBSAMP_AUTO)
753
+ return ctx->subsampling;
754
+ /* :fast / :jpeg_turbo keeps libjpeg 4:2:0 unless the caller opts in. */
755
+ if (ctx->algo == IP_ALGO_MOZJPEG && ctx->quality >= IP_AUTO_444_QUALITY)
756
+ return IP_SUBSAMP_444;
757
+ return IP_SUBSAMP_420;
758
+ }
759
+
760
+ static void ip_apply_subsampling(struct jpeg_compress_struct *cinfo, int mode) {
761
+ int h;
762
+ int v;
763
+ int ci;
764
+
765
+ if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components < 3)
766
+ return;
767
+
768
+ ip_luma_samp(mode, &h, &v);
769
+ cinfo->comp_info[0].h_samp_factor = h;
770
+ cinfo->comp_info[0].v_samp_factor = v;
771
+ for (ci = 1; ci < cinfo->num_components; ci++) {
772
+ cinfo->comp_info[ci].h_samp_factor = 1;
773
+ cinfo->comp_info[ci].v_samp_factor = 1;
774
+ }
775
+ }
776
+
777
+ static void ip_set_tune_params(struct jpeg_compress_struct *cinfo, int tbl, float scale1,
778
+ float scale2, boolean use_weights) {
779
+ jpeg_c_set_int_param(cinfo, JINT_BASE_QUANT_TBL_IDX, tbl);
780
+ jpeg_c_set_float_param(cinfo, JFLOAT_LAMBDA_LOG_SCALE1, scale1);
781
+ jpeg_c_set_float_param(cinfo, JFLOAT_LAMBDA_LOG_SCALE2, scale2);
782
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_LAMBDA_WEIGHT_TBL, use_weights);
783
+ }
784
+
785
+ static void ip_apply_tune(struct jpeg_compress_struct *cinfo, int tune) {
786
+ switch (tune) {
787
+ case IP_TUNE_PSNR:
788
+ ip_set_tune_params(cinfo, 1, 9.0f, 0.0f, FALSE);
789
+ break;
790
+ case IP_TUNE_SSIM:
791
+ ip_set_tune_params(cinfo, 1, 11.5f, 12.75f, FALSE);
792
+ break;
793
+ case IP_TUNE_MS_SSIM:
794
+ ip_set_tune_params(cinfo, 3, 12.0f, 13.0f, TRUE);
795
+ break;
796
+ case IP_TUNE_HVS:
797
+ default:
798
+ ip_set_tune_params(cinfo, 3, 14.75f, 16.5f, TRUE);
799
+ break;
800
+ }
801
+ }
802
+
803
+ static void ip_apply_effort(struct jpeg_compress_struct *cinfo, int effort, int mozjpeg_size_mode) {
804
+ if (!mozjpeg_size_mode)
805
+ return;
806
+ if (effort == IP_EFFORT_MAX) {
807
+ jpeg_c_set_int_param(cinfo, JINT_TRELLIS_NUM_LOOPS, 3);
808
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, TRUE);
809
+ }
810
+ }
811
+
812
+ static void ip_configure_encoder(struct jpeg_compress_struct *cinfo, ip_context_t *ctx,
813
+ int mozjpeg_size_mode, int measurement) {
814
+ configure_mozjpeg_profile_before_defaults(cinfo, mozjpeg_size_mode);
815
+ jpeg_set_defaults(cinfo);
816
+ if (mozjpeg_size_mode)
817
+ ip_apply_tune(cinfo, ctx->tune);
818
+ jpeg_set_quality(cinfo, ctx->quality, TRUE);
819
+ configure_mozjpeg_features_after_defaults(cinfo, ctx, mozjpeg_size_mode, measurement);
820
+ ip_apply_effort(cinfo, ctx->effort, mozjpeg_size_mode);
821
+ ip_apply_subsampling(cinfo, ctx->subsampling_active);
822
+ }
823
+
824
+ static int ip_marker_is_saved(const ip_context_t *ctx, unsigned marker) {
825
+ if (marker == JPEG_APP0 + 1)
826
+ return 1;
827
+ if (marker == JPEG_APP0 + 2)
828
+ return !ctx->strip_icc;
829
+ if (ctx->strip_metadata)
830
+ return 0;
831
+ if (marker == JPEG_COM)
832
+ return 1;
833
+ if (marker >= JPEG_APP0 && marker <= JPEG_APP0 + 15)
834
+ return marker != JPEG_APP0;
835
+ return 0;
836
+ }
837
+
838
+ static int ip_marker_is_profile_chain(unsigned marker) {
839
+ return marker == JPEG_APP0 + 1 || marker == JPEG_APP0 + 2;
840
+ }
841
+
842
+ static int ip_scan_jpeg_marker_budget(ip_context_t *ctx) {
843
+ const unsigned char *p;
844
+ size_t n;
845
+ size_t i;
846
+ size_t misc_count = 0;
847
+ size_t profile_count = 0;
848
+ size_t bytes = 0;
849
+
850
+ if (!ctx->input_data || ctx->input_size < 2)
851
+ return 1;
852
+
853
+ p = ctx->input_data;
854
+ n = ctx->input_size;
855
+ if (p[0] != 0xFF || p[1] != 0xD8)
856
+ return 1;
857
+
858
+ i = 2;
859
+ while (i + 1 < n) {
860
+ unsigned marker;
861
+ unsigned len;
862
+
863
+ if (p[i] != 0xFF) {
864
+ i++;
865
+ continue;
866
+ }
867
+ while (i < n && p[i] == 0xFF)
868
+ i++;
869
+ if (i >= n)
870
+ break;
871
+ marker = p[i++];
872
+ if (marker == 0xD8)
873
+ continue;
874
+ if (marker == 0xD9 || marker == 0xDA)
875
+ break;
876
+ if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7))
877
+ continue;
878
+ if (i + 2 > n)
879
+ break;
880
+ len = ((unsigned)p[i] << 8) | (unsigned)p[i + 1];
881
+ if (len < 2)
882
+ break;
883
+ if (ip_marker_is_saved(ctx, marker)) {
884
+ bytes += (size_t)(len - 2);
885
+ if (ip_marker_is_profile_chain(marker))
886
+ profile_count++;
887
+ else
888
+ misc_count++;
889
+ if (misc_count > IP_MAX_MARKERS || profile_count > IP_MAX_PROFILE_MARKERS ||
890
+ bytes > IP_MAX_MARKER_BYTES) {
891
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "too many/too large JPEG metadata markers");
892
+ return 0;
893
+ }
894
+ }
895
+ i += len;
896
+ }
897
+ return 1;
898
+ }
899
+
900
+ static JXFORM_CODE ip_xform_for_orientation(int orientation) {
901
+ switch (orientation) {
902
+ case 2:
903
+ return JXFORM_FLIP_H;
904
+ case 3:
905
+ return JXFORM_ROT_180;
906
+ case 4:
907
+ return JXFORM_FLIP_V;
908
+ case 5:
909
+ return JXFORM_TRANSPOSE;
910
+ case 6:
911
+ return JXFORM_ROT_90;
912
+ case 7:
913
+ return JXFORM_TRANSVERSE;
914
+ case 8:
915
+ return JXFORM_ROT_270;
916
+ default:
917
+ return JXFORM_NONE;
918
+ }
611
919
  }
612
920
 
613
921
  static int ip_check_max_dimension_limits(ip_context_t *ctx) {
@@ -936,6 +1244,17 @@ static VALUE ip_build_report(ip_context_t *ctx, VALUE output_value) {
936
1244
 
937
1245
  static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
938
1246
  unsigned int len) {
1247
+ size_t next_bytes = ctx->preserved_marker_bytes + (size_t)len;
1248
+ int profile = ip_marker_is_profile_chain((unsigned)marker);
1249
+ size_t next_profile = ctx->preserved_profile_marker_count + (profile ? 1 : 0);
1250
+ size_t next_misc = ctx->preserved_misc_marker_count + (profile ? 0 : 1);
1251
+
1252
+ if (next_bytes > IP_MAX_MARKER_BYTES || next_profile > IP_MAX_PROFILE_MARKERS ||
1253
+ next_misc > IP_MAX_MARKERS) {
1254
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "too many/too large JPEG metadata markers");
1255
+ return 0;
1256
+ }
1257
+
939
1258
  if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
940
1259
  if (ctx->preserved_marker_capacity > SIZE_MAX / 2)
941
1260
  return 0;
@@ -960,6 +1279,11 @@ static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *da
960
1279
  ctx->preserved_markers[ctx->preserved_marker_count].data = copy;
961
1280
  ctx->preserved_markers[ctx->preserved_marker_count].len = len;
962
1281
  ctx->preserved_marker_count++;
1282
+ ctx->preserved_marker_bytes += (size_t)len;
1283
+ if (profile)
1284
+ ctx->preserved_profile_marker_count++;
1285
+ else
1286
+ ctx->preserved_misc_marker_count++;
963
1287
  return 1;
964
1288
  }
965
1289
 
@@ -971,7 +1295,8 @@ static int ip_save_markers_from_decompress(ip_context_t *ctx,
971
1295
  continue;
972
1296
 
973
1297
  if (!ip_save_marker(ctx, m->marker, m->data, m->data_length)) {
974
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to preserve JPEG metadata marker");
1298
+ if (ctx->status == IP_OK)
1299
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to preserve JPEG metadata marker");
975
1300
  return 0;
976
1301
  }
977
1302
  }
@@ -1138,22 +1463,57 @@ static void ip_write_preserved_markers(ip_context_t *ctx, struct jpeg_compress_s
1138
1463
  }
1139
1464
  }
1140
1465
 
1141
- static void ip_jpeg_invalid_error_exit(j_common_ptr cinfo) {
1142
- ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
1143
- char buffer[JMSG_LENGTH_MAX];
1144
- (*cinfo->err->format_message)(cinfo, buffer);
1145
- ip_context_set_error(err->ctx, IP_ERR_INVALID_IMAGE, buffer);
1146
- if (err->ctx->jmp_armed)
1147
- longjmp(err->ctx->jmpbuf, 1);
1466
+ static int ip_capture_icc(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo) {
1467
+ JOCTET *icc = NULL;
1468
+ unsigned int len = 0;
1469
+
1470
+ if (ctx->strip_icc)
1471
+ return 1;
1472
+ if (!jpeg_read_icc_profile(cinfo, &icc, &len) || !icc || len == 0)
1473
+ return 1;
1474
+ if (len > IP_MAX_ICC_BYTES) {
1475
+ free(icc);
1476
+ return 1;
1477
+ }
1478
+ free(ctx->icc_profile);
1479
+ ctx->icc_profile = icc;
1480
+ ctx->icc_len = len;
1481
+ return 1;
1148
1482
  }
1149
1483
 
1150
- static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
1151
- ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
1152
- char buffer[JMSG_LENGTH_MAX];
1153
- (*cinfo->err->format_message)(cinfo, buffer);
1154
- ip_context_set_error(err->ctx, IP_ERR_ENCODE, buffer);
1155
- if (err->ctx->jmp_armed)
1156
- longjmp(err->ctx->jmpbuf, 1);
1484
+ static void ip_write_sidecar_metadata(ip_context_t *ctx, struct jpeg_compress_struct *cinfo) {
1485
+ if (!ctx->strip_metadata) {
1486
+ ip_write_preserved_markers(ctx, cinfo);
1487
+ return;
1488
+ }
1489
+ if (ctx->icc_profile && ctx->icc_len > 0)
1490
+ jpeg_write_icc_profile(cinfo, ctx->icc_profile, ctx->icc_len);
1491
+ }
1492
+
1493
+ static int ip_prepare_source_markers(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo) {
1494
+ if (!ip_scan_jpeg_marker_budget(ctx))
1495
+ return 0;
1496
+ ip_setup_marker_saving(cinfo, ctx);
1497
+ return 1;
1498
+ }
1499
+
1500
+ static int ip_read_source_header(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo) {
1501
+ if (jpeg_read_header(cinfo, TRUE) != JPEG_HEADER_OK)
1502
+ IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1503
+ ctx->source_orientation = ip_read_exif_orientation_from_decompress(cinfo);
1504
+ return 1;
1505
+ }
1506
+
1507
+ static int ip_open_source_jpeg(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo) {
1508
+ return ip_prepare_source_markers(ctx, cinfo) && ip_read_source_header(ctx, cinfo);
1509
+ }
1510
+
1511
+ static int ip_ingest_source_metadata(ip_context_t *ctx, struct jpeg_decompress_struct *cinfo) {
1512
+ if (!ip_capture_icc(ctx, cinfo))
1513
+ return 0;
1514
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, cinfo))
1515
+ return 0;
1516
+ return 1;
1157
1517
  }
1158
1518
 
1159
1519
  static const char *ip_jpeg_color_space_name(int color_space) {
@@ -1180,16 +1540,13 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck) {
1180
1540
 
1181
1541
  struct jpeg_decompress_struct cinfo;
1182
1542
  ip_jpeg_error_mgr jerr;
1183
- memset(&cinfo, 0, sizeof(cinfo));
1184
- memset(&jerr, 0, sizeof(jerr));
1185
- cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1543
+ ip_init_decompress(&cinfo, &jerr, ctx);
1186
1544
 
1187
1545
  ctx->jmp_armed = 1;
1188
1546
  if (setjmp(ctx->jmpbuf))
1189
1547
  goto fail;
1190
1548
 
1191
- jpeg_create_decompress(&cinfo);
1192
- jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
1549
+ ip_open_mem_src(&cinfo, ctx->input_data, ctx->input_size);
1193
1550
  if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1194
1551
  IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1195
1552
 
@@ -1275,20 +1632,18 @@ static void configure_mozjpeg_profile_before_defaults(struct jpeg_compress_struc
1275
1632
  }
1276
1633
 
1277
1634
  static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struct *cinfo,
1278
- int mozjpeg_size_mode,
1279
- int progressive_requested,
1280
- int mozjpeg_trellis_enabled,
1281
- int scan_opt_enabled, int measurement) {
1635
+ const ip_context_t *ctx,
1636
+ int mozjpeg_size_mode, int measurement) {
1282
1637
  if (mozjpeg_size_mode) {
1283
1638
  cinfo->optimize_coding = TRUE;
1284
1639
 
1285
- if (progressive_requested) {
1286
- if (mozjpeg_trellis_enabled) {
1640
+ if (ctx->progressive) {
1641
+ if (ctx->mozjpeg_trellis_enabled) {
1287
1642
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, TRUE);
1288
1643
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, TRUE);
1289
1644
  }
1290
1645
  jpeg_c_set_int_param(cinfo, JINT_DC_SCAN_OPT_MODE, 2);
1291
- int run_scan_search = (measurement || !scan_opt_enabled) ? FALSE : TRUE;
1646
+ int run_scan_search = (measurement || !ctx->mozjpeg_scan_opt_enabled) ? FALSE : TRUE;
1292
1647
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, run_scan_search);
1293
1648
  jpeg_simple_progression(cinfo);
1294
1649
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, run_scan_search);
@@ -1298,7 +1653,7 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
1298
1653
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1299
1654
  }
1300
1655
 
1301
- if (!mozjpeg_trellis_enabled)
1656
+ if (!ctx->mozjpeg_trellis_enabled)
1302
1657
  ip_disable_mozjpeg_trellis(cinfo);
1303
1658
 
1304
1659
  return;
@@ -1310,7 +1665,7 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
1310
1665
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1311
1666
  ip_disable_mozjpeg_trellis(cinfo);
1312
1667
 
1313
- if (progressive_requested) {
1668
+ if (ctx->progressive) {
1314
1669
  jpeg_simple_progression(cinfo);
1315
1670
  cinfo->optimize_coding = TRUE;
1316
1671
  }
@@ -1320,9 +1675,7 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode,
1320
1675
  struct jpeg_compress_struct cinfo;
1321
1676
  ip_jpeg_error_mgr jerr;
1322
1677
  unsigned long jpeg_size = 0;
1323
- memset(&cinfo, 0, sizeof(cinfo));
1324
- memset(&jerr, 0, sizeof(jerr));
1325
- cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_encode_error_exit);
1678
+ ip_init_compress(&cinfo, &jerr, ctx);
1326
1679
  ctx->transient_jpeg_buf = NULL;
1327
1680
 
1328
1681
  ctx->jmp_armed = 1;
@@ -1345,17 +1698,10 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode,
1345
1698
  ctx->channels == 4 ? JCS_EXT_RGBA : color_space_for_channels(ctx->channels);
1346
1699
  }
1347
1700
 
1348
- configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
1349
- jpeg_set_defaults(&cinfo);
1350
- jpeg_set_quality(&cinfo, ctx->quality, TRUE);
1351
- configure_mozjpeg_features_after_defaults(&cinfo, mozjpeg_size_mode, ctx->progressive,
1352
- ctx->mozjpeg_trellis_enabled,
1353
- ctx->mozjpeg_scan_opt_enabled, measurement);
1701
+ ip_configure_encoder(&cinfo, ctx, mozjpeg_size_mode, measurement);
1354
1702
 
1355
1703
  jpeg_start_compress(&cinfo, TRUE);
1356
-
1357
- if (!ctx->strip_metadata)
1358
- ip_write_preserved_markers(ctx, &cinfo);
1704
+ ip_write_sidecar_metadata(ctx, &cinfo);
1359
1705
 
1360
1706
  while (cinfo.next_scanline < cinfo.image_height) {
1361
1707
  if (atomic_load(&ctx->cancelled))
@@ -1376,16 +1722,12 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode,
1376
1722
 
1377
1723
  jpeg_finish_compress(&cinfo);
1378
1724
 
1379
- if (!measurement && ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
1380
- IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
1725
+ if (!measurement && !ip_within_output_limit(ctx, jpeg_size))
1726
+ goto fail;
1381
1727
 
1382
1728
  jpeg_destroy_compress(&cinfo);
1383
1729
  ctx->jmp_armed = 0;
1384
-
1385
- ctx->output_data = ctx->transient_jpeg_buf;
1386
- ctx->transient_jpeg_buf = NULL;
1387
- ctx->output_size = (size_t)jpeg_size;
1388
- ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1730
+ ip_claim_jpeg_output(ctx, jpeg_size);
1389
1731
  return 1;
1390
1732
 
1391
1733
  fail:
@@ -1401,9 +1743,7 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1401
1743
  int allow_ycbcr_transcode) {
1402
1744
  struct jpeg_decompress_struct cinfo;
1403
1745
  ip_jpeg_error_mgr jerr;
1404
- memset(&cinfo, 0, sizeof(cinfo));
1405
- memset(&jerr, 0, sizeof(jerr));
1406
- cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
1746
+ ip_init_decompress(&cinfo, &jerr, ctx);
1407
1747
  ctx->transient_decode_buf = NULL;
1408
1748
  ctx->source_orientation = 1;
1409
1749
  ctx->decoded_as_ycbcr = 0;
@@ -1415,28 +1755,11 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1415
1755
  goto fail;
1416
1756
  }
1417
1757
 
1418
- jpeg_create_decompress(&cinfo);
1419
- jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
1420
-
1421
- jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
1422
- if (!ctx->strip_metadata) {
1423
- jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
1424
- for (int app = 2; app < 16; app++)
1425
- jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
1426
- }
1427
-
1428
- if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1429
- IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1430
-
1431
- ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
1432
-
1433
- if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
1434
- IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1435
-
1436
- if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1437
- cinfo.jpeg_color_space == JCS_YCCK)
1438
- IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1439
- "CMYK/YCCK JPEG input is not supported in this release");
1758
+ ip_open_mem_src(&cinfo, ctx->input_data, ctx->input_size);
1759
+ if (!ip_open_source_jpeg(ctx, &cinfo))
1760
+ goto fail;
1761
+ if (!ip_header_fits_int(ctx, &cinfo) || !ip_reject_cmyk(ctx, &cinfo))
1762
+ goto fail;
1440
1763
 
1441
1764
  int ch = cinfo.num_components == 1 ? 1 : 3;
1442
1765
 
@@ -1453,12 +1776,21 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1453
1776
  int use_ycbcr = allow_ycbcr_transcode && ch == 3;
1454
1777
  cinfo.out_color_space = ch == 1 ? JCS_GRAYSCALE : (use_ycbcr ? JCS_YCbCr : JCS_RGB);
1455
1778
  ctx->decoded_as_ycbcr = use_ycbcr;
1779
+ ip_apply_decode_scale(&cinfo, ctx);
1456
1780
  if (fast_decode_mode)
1457
1781
  ip_apply_fast_decode(&cinfo);
1458
1782
 
1459
1783
  jpeg_start_decompress(&cinfo);
1460
1784
 
1461
- if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &cinfo))
1785
+ ctx->width = (int)cinfo.output_width;
1786
+ ctx->height = (int)cinfo.output_height;
1787
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
1788
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1789
+ validate_limits_for_pixels(ctx);
1790
+ if (ctx->status != IP_OK)
1791
+ goto fail;
1792
+
1793
+ if (!ip_ingest_source_metadata(ctx, &cinfo))
1462
1794
  goto fail;
1463
1795
 
1464
1796
  size_t row_stride = 0;
@@ -1528,11 +1860,166 @@ fail:
1528
1860
  return 0;
1529
1861
  }
1530
1862
 
1863
+ static void ip_free_raw_planes(JSAMPARRAY planes[3], unsigned char *bufs[3]) {
1864
+ int ci;
1865
+ for (ci = 0; ci < 3; ci++) {
1866
+ free(planes[ci]);
1867
+ planes[ci] = NULL;
1868
+ free(bufs[ci]);
1869
+ bufs[ci] = NULL;
1870
+ }
1871
+ }
1872
+
1873
+ static int ip_alloc_raw_planes(ip_context_t *ctx, struct jpeg_decompress_struct *src,
1874
+ JSAMPARRAY planes[3], unsigned char *bufs[3], int *rows_out) {
1875
+ int ci;
1876
+ int rows = (int)src->max_v_samp_factor * DCTSIZE;
1877
+
1878
+ memset(planes, 0, sizeof(JSAMPARRAY) * 3);
1879
+ memset(bufs, 0, sizeof(unsigned char *) * 3);
1880
+ *rows_out = rows;
1881
+
1882
+ for (ci = 0; ci < src->num_components && ci < 3; ci++) {
1883
+ int width = (int)src->comp_info[ci].width_in_blocks * DCTSIZE;
1884
+ size_t plane_size = (size_t)rows * (size_t)width;
1885
+ int r;
1886
+
1887
+ planes[ci] = (JSAMPARRAY)malloc((size_t)rows * sizeof(JSAMPROW));
1888
+ bufs[ci] = (unsigned char *)malloc(plane_size);
1889
+ if ((!planes[ci] || !bufs[ci]) && (rows > 0) && (width > 0)) {
1890
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate raw JPEG plane buffer");
1891
+ ip_free_raw_planes(planes, bufs);
1892
+ return 0;
1893
+ }
1894
+ for (r = 0; r < rows; r++)
1895
+ planes[ci][r] = bufs[ci] + ((size_t)r * (size_t)width);
1896
+ }
1897
+ return 1;
1898
+ }
1899
+
1900
+ /* Returns 1 on success, 0 on fallback (status still IP_OK) or error. */
1901
+ static int encode_jpeg_raw_transcode(ip_context_t *ctx, int mozjpeg_size_mode) {
1902
+ struct jpeg_decompress_struct srcinfo;
1903
+ struct jpeg_compress_struct dstinfo;
1904
+ ip_jpeg_error_mgr srcerr;
1905
+ ip_jpeg_error_mgr dsterr;
1906
+ JSAMPARRAY planes[3];
1907
+ unsigned char *bufs[3];
1908
+ int rows = 0;
1909
+ unsigned long jpeg_size = 0;
1910
+ int want_h = 2;
1911
+ int want_v = 2;
1912
+
1913
+ ip_init_decompress(&srcinfo, &srcerr, ctx);
1914
+ ip_init_compress(&dstinfo, &dsterr, ctx);
1915
+ memset(planes, 0, sizeof(planes));
1916
+ memset(bufs, 0, sizeof(bufs));
1917
+ ctx->transient_jpeg_buf = NULL;
1918
+
1919
+ ctx->jmp_armed = 1;
1920
+ if (setjmp(ctx->jmpbuf)) {
1921
+ if (ctx->status == IP_OK)
1922
+ ip_context_set_error(ctx, IP_ERR_ENCODE, "raw JPEG transcode failed");
1923
+ goto fail;
1924
+ }
1925
+
1926
+ ip_open_mem_src(&srcinfo, ctx->input_data, ctx->input_size);
1927
+ if (!ip_open_source_jpeg(ctx, &srcinfo))
1928
+ goto fail;
1929
+ if (ctx->strip_metadata && ctx->source_orientation > 1)
1930
+ goto fail;
1931
+
1932
+ if (srcinfo.jpeg_color_space != JCS_YCbCr || srcinfo.num_components != 3)
1933
+ goto fail;
1934
+ if (ctx->scale_denom > 0 && !(ctx->scale_num == 1 && ctx->scale_denom == 1))
1935
+ goto fail;
1936
+
1937
+ ctx->subsampling_active = ip_resolve_subsampling(ctx);
1938
+ ip_luma_samp(ctx->subsampling_active, &want_h, &want_v);
1939
+ if (srcinfo.comp_info[0].h_samp_factor != want_h ||
1940
+ srcinfo.comp_info[0].v_samp_factor != want_v || srcinfo.comp_info[1].h_samp_factor != 1 ||
1941
+ srcinfo.comp_info[1].v_samp_factor != 1 || srcinfo.comp_info[2].h_samp_factor != 1 ||
1942
+ srcinfo.comp_info[2].v_samp_factor != 1)
1943
+ goto fail;
1944
+
1945
+ ip_apply_decode_scale(&srcinfo, ctx);
1946
+ srcinfo.raw_data_out = TRUE;
1947
+ srcinfo.out_color_space = JCS_YCbCr;
1948
+ jpeg_start_decompress(&srcinfo);
1949
+
1950
+ ctx->width = (int)srcinfo.output_width;
1951
+ ctx->height = (int)srcinfo.output_height;
1952
+ ctx->channels = 3;
1953
+ if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
1954
+ IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
1955
+ validate_limits_for_pixels(ctx);
1956
+ if (ctx->status != IP_OK)
1957
+ goto fail;
1958
+
1959
+ if (!ip_ingest_source_metadata(ctx, &srcinfo))
1960
+ goto fail;
1961
+
1962
+ if (!ip_alloc_raw_planes(ctx, &srcinfo, planes, bufs, &rows))
1963
+ goto fail;
1964
+
1965
+ jpeg_create_compress(&dstinfo);
1966
+ jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
1967
+ dstinfo.image_width = srcinfo.output_width;
1968
+ dstinfo.image_height = srcinfo.output_height;
1969
+ dstinfo.input_components = 3;
1970
+ dstinfo.in_color_space = JCS_YCbCr;
1971
+ ip_configure_encoder(&dstinfo, ctx, mozjpeg_size_mode, 0);
1972
+ dstinfo.raw_data_in = TRUE;
1973
+
1974
+ jpeg_start_compress(&dstinfo, TRUE);
1975
+ ip_write_sidecar_metadata(ctx, &dstinfo);
1976
+
1977
+ while (srcinfo.output_scanline < srcinfo.output_height) {
1978
+ JDIMENSION got;
1979
+
1980
+ if (atomic_load(&ctx->cancelled))
1981
+ IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG transcode cancelled");
1982
+
1983
+ got = jpeg_read_raw_data(&srcinfo, planes, (JDIMENSION)rows);
1984
+ if (got == 0)
1985
+ IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "failed to read raw JPEG data");
1986
+ if (jpeg_write_raw_data(&dstinfo, planes, (JDIMENSION)rows) == 0)
1987
+ IP_FAIL_GOTO(ctx, IP_ERR_ENCODE, "failed to write raw JPEG data");
1988
+ }
1989
+
1990
+ jpeg_finish_compress(&dstinfo);
1991
+ jpeg_finish_decompress(&srcinfo);
1992
+
1993
+ if (!ip_within_output_limit(ctx, jpeg_size))
1994
+ goto fail;
1995
+
1996
+ ip_free_raw_planes(planes, bufs);
1997
+ ip_close_codec_pair(&dstinfo, &srcinfo, ctx);
1998
+ ip_claim_jpeg_output(ctx, jpeg_size);
1999
+ return 1;
2000
+
2001
+ fail:
2002
+ ip_close_codec_pair(&dstinfo, &srcinfo, ctx);
2003
+ ip_free_raw_planes(planes, bufs);
2004
+ free(ctx->transient_jpeg_buf);
2005
+ ctx->transient_jpeg_buf = NULL;
2006
+ return 0;
2007
+ }
2008
+
1531
2009
  static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
1532
2010
  if (ctx->pixel_data) {
2011
+ ctx->subsampling_active = ip_resolve_subsampling(ctx);
1533
2012
  return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode, 0);
1534
2013
  }
1535
2014
 
2015
+ if (!ctx->ssim_guard_enabled &&
2016
+ (ctx->scale_denom == 0 || (ctx->scale_num == 1 && ctx->scale_denom == 1))) {
2017
+ if (encode_jpeg_raw_transcode(ctx, mozjpeg_size_mode))
2018
+ return 1;
2019
+ if (ctx->status != IP_OK)
2020
+ return 0;
2021
+ }
2022
+
1536
2023
  unsigned char *pixels = NULL;
1537
2024
  int width = 0;
1538
2025
  int height = 0;
@@ -1547,6 +2034,7 @@ static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mod
1547
2034
  ctx->height = height;
1548
2035
  ctx->channels = channels;
1549
2036
  ctx->decoded_bytes = ctx->pixel_size;
2037
+ ctx->subsampling_active = ip_resolve_subsampling(ctx);
1550
2038
 
1551
2039
  return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode, 0);
1552
2040
  }
@@ -1567,9 +2055,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1567
2055
  unsigned char **luma, int *width, int *height) {
1568
2056
  struct jpeg_decompress_struct cinfo;
1569
2057
  ip_jpeg_error_mgr jerr;
1570
- memset(&cinfo, 0, sizeof(cinfo));
1571
- memset(&jerr, 0, sizeof(jerr));
1572
- cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
2058
+ ip_init_decompress(&cinfo, &jerr, ctx);
1573
2059
  ctx->transient_decode_buf = NULL;
1574
2060
 
1575
2061
  ctx->jmp_armed = 1;
@@ -1579,19 +2065,12 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1579
2065
  goto fail;
1580
2066
  }
1581
2067
 
1582
- jpeg_create_decompress(&cinfo);
1583
- jpeg_mem_src(&cinfo, data, (unsigned long)size);
2068
+ ip_open_mem_src(&cinfo, data, size);
1584
2069
 
1585
2070
  if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
1586
2071
  IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1587
-
1588
- if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
1589
- IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1590
-
1591
- if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1592
- cinfo.jpeg_color_space == JCS_YCCK)
1593
- IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
1594
- "CMYK/YCCK JPEG input is not supported in this release");
2072
+ if (!ip_header_fits_int(ctx, &cinfo) || !ip_reject_cmyk(ctx, &cinfo))
2073
+ goto fail;
1595
2074
 
1596
2075
  int old_width = ctx->width;
1597
2076
  int old_height = ctx->height;
@@ -1918,6 +2397,9 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1918
2397
  if (!reference_luma)
1919
2398
  return 0;
1920
2399
 
2400
+ ctx->subsampling_active = ip_resolve_subsampling(ctx);
2401
+
2402
+ int search_start_quality = ctx->quality;
1921
2403
  int search_low = ctx->quality;
1922
2404
  int search_high = 100;
1923
2405
  int best_quality = -1;
@@ -1931,6 +2413,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1931
2413
  int q_lo = 0, q_hi = 0;
1932
2414
  double s_lo = 0.0, s_hi = 0.0;
1933
2415
 
2416
+ ssim_search:
1934
2417
  while (search_low <= search_high) {
1935
2418
  if (atomic_load(&ctx->cancelled)) {
1936
2419
  free(reference_luma);
@@ -1990,6 +2473,20 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1990
2473
  }
1991
2474
 
1992
2475
  if (best_quality < 0) {
2476
+ if (ctx->subsampling == IP_SUBSAMP_AUTO && ctx->subsampling_active == IP_SUBSAMP_420) {
2477
+ ctx->subsampling_active = IP_SUBSAMP_444;
2478
+ ctx->quality = search_start_quality;
2479
+ search_low = search_start_quality;
2480
+ search_high = 100;
2481
+ best_quality = -1;
2482
+ best_ssim = 0.0;
2483
+ free(best_jpeg);
2484
+ best_jpeg = NULL;
2485
+ best_jpeg_size = 0;
2486
+ have_lo = 0;
2487
+ have_hi = 0;
2488
+ goto ssim_search;
2489
+ }
1993
2490
  char message[512];
1994
2491
  snprintf(message, sizeof(message),
1995
2492
  "cannot satisfy min_ssim=%.6f; best observed SSIM %.6f at quality=%d",
@@ -2072,11 +2569,15 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
2072
2569
  return 1;
2073
2570
  }
2074
2571
 
2075
- static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, int strip_metadata) {
2572
+ static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, const ip_context_t *ctx) {
2076
2573
  jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xFFFF);
2077
- if (!strip_metadata) {
2574
+ if (!ctx->strip_icc)
2575
+ jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xFFFF);
2576
+ if (!ctx->strip_metadata) {
2078
2577
  jpeg_save_markers(cinfo, JPEG_COM, 0xFFFF);
2079
- for (int app = 2; app < 16; app++) {
2578
+ for (int app = 0; app < 16; app++) {
2579
+ if (app == 1 || app == 2)
2580
+ continue;
2080
2581
  jpeg_save_markers(cinfo, JPEG_APP0 + app, 0xFFFF);
2081
2582
  }
2082
2583
  }
@@ -2084,10 +2585,8 @@ static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, int str
2084
2585
 
2085
2586
  static int ip_validate_lossless_optimize_header(ip_context_t *ctx,
2086
2587
  struct jpeg_decompress_struct *srcinfo) {
2087
- if (srcinfo->image_width > (JDIMENSION)INT_MAX || srcinfo->image_height > (JDIMENSION)INT_MAX) {
2088
- ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
2588
+ if (!ip_header_fits_int(ctx, srcinfo))
2089
2589
  return 0;
2090
- }
2091
2590
 
2092
2591
  ctx->width = (int)srcinfo->image_width;
2093
2592
  ctx->height = (int)srcinfo->image_height;
@@ -2109,15 +2608,10 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
2109
2608
  jvirt_barray_ptr *coef_arrays = NULL;
2110
2609
  unsigned long jpeg_size = 0;
2111
2610
 
2112
- memset(&srcinfo, 0, sizeof(srcinfo));
2113
- memset(&dstinfo, 0, sizeof(dstinfo));
2114
- memset(&srcerr, 0, sizeof(srcerr));
2115
- memset(&dsterr, 0, sizeof(dsterr));
2611
+ ip_init_decompress(&srcinfo, &srcerr, ctx);
2612
+ ip_init_compress(&dstinfo, &dsterr, ctx);
2116
2613
  ctx->transient_jpeg_buf = NULL;
2117
2614
 
2118
- srcinfo.err = ip_use_error(&srcerr, ctx, ip_jpeg_invalid_error_exit);
2119
- dstinfo.err = ip_use_error(&dsterr, ctx, ip_jpeg_encode_error_exit);
2120
-
2121
2615
  ctx->jmp_armed = 1;
2122
2616
  if (setjmp(ctx->jmpbuf)) {
2123
2617
  if (ctx->status == IP_OK)
@@ -2125,70 +2619,82 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
2125
2619
  goto fail;
2126
2620
  }
2127
2621
 
2622
+ jpeg_transform_info xform;
2623
+ jvirt_barray_ptr *dst_coef_arrays = NULL;
2624
+
2625
+ memset(&xform, 0, sizeof(xform));
2626
+ xform.trim = ctx->trim_partial_mcus ? TRUE : FALSE;
2627
+ xform.perfect = ctx->trim_partial_mcus ? FALSE : TRUE;
2628
+
2128
2629
  jpeg_create_decompress(&srcinfo);
2129
2630
  jpeg_create_compress(&dstinfo);
2130
2631
  jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
2131
- ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
2632
+ if (!ip_prepare_source_markers(ctx, &srcinfo))
2633
+ goto fail;
2132
2634
 
2133
2635
  if (atomic_load(&ctx->cancelled))
2134
2636
  IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
2135
2637
 
2136
- if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK)
2137
- IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
2138
-
2139
- ctx->source_orientation = ip_read_exif_orientation_from_decompress(&srcinfo);
2638
+ if (!ip_read_source_header(ctx, &srcinfo))
2639
+ goto fail;
2140
2640
  if (ctx->strip_metadata && ctx->source_orientation > 1)
2141
- IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
2142
- "lossless optimize cannot strip EXIF Orientation without changing "
2143
- "visual orientation; use strip_metadata: false or ImagePack.compress");
2641
+ xform.transform = ip_xform_for_orientation(ctx->source_orientation);
2144
2642
 
2145
2643
  if (!ip_validate_lossless_optimize_header(ctx, &srcinfo))
2146
2644
  goto fail;
2147
2645
 
2148
- if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo))
2646
+ if (!ip_ingest_source_metadata(ctx, &srcinfo))
2149
2647
  goto fail;
2150
2648
 
2649
+ if (xform.transform != JXFORM_NONE) {
2650
+ if (!jtransform_request_workspace(&srcinfo, &xform))
2651
+ IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
2652
+ "lossless EXIF Orientation transform would crop partial MCU edges; "
2653
+ "pass trim: true to allow cropping, or ImagePack.compress");
2654
+ }
2655
+
2151
2656
  coef_arrays = jpeg_read_coefficients(&srcinfo);
2152
2657
  jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
2153
2658
  dstinfo.optimize_coding = TRUE;
2154
2659
  dstinfo.num_scans = 0;
2155
2660
  dstinfo.scan_info = NULL;
2156
2661
  if (ctx->progressive) {
2662
+ jpeg_c_set_bool_param(&dstinfo, JBOOLEAN_OPTIMIZE_SCANS, TRUE);
2157
2663
  jpeg_simple_progression(&dstinfo);
2158
2664
  dstinfo.optimize_coding = TRUE;
2665
+ } else {
2666
+ jpeg_c_set_bool_param(&dstinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
2159
2667
  }
2160
2668
 
2669
+ if (xform.transform != JXFORM_NONE)
2670
+ dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, coef_arrays, &xform);
2671
+ else
2672
+ dst_coef_arrays = coef_arrays;
2673
+
2161
2674
  jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
2162
2675
 
2163
2676
  if (atomic_load(&ctx->cancelled))
2164
2677
  IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
2165
2678
 
2166
- jpeg_write_coefficients(&dstinfo, coef_arrays);
2167
- if (!ctx->strip_metadata)
2168
- ip_write_preserved_markers(ctx, &dstinfo);
2679
+ jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
2680
+ ip_write_sidecar_metadata(ctx, &dstinfo);
2681
+ if (xform.transform != JXFORM_NONE)
2682
+ jtransform_execute_transform(&srcinfo, &dstinfo, coef_arrays, &xform);
2169
2683
 
2170
2684
  jpeg_finish_compress(&dstinfo);
2171
2685
  jpeg_finish_decompress(&srcinfo);
2172
2686
 
2173
- if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
2174
- IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
2175
-
2176
- jpeg_destroy_compress(&dstinfo);
2177
- jpeg_destroy_decompress(&srcinfo);
2178
- ctx->jmp_armed = 0;
2687
+ if (!ip_within_output_limit(ctx, jpeg_size))
2688
+ goto fail;
2179
2689
 
2180
- ctx->output_data = ctx->transient_jpeg_buf;
2181
- ctx->transient_jpeg_buf = NULL;
2182
- ctx->output_size = (size_t)jpeg_size;
2183
- ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
2690
+ ip_close_codec_pair(&dstinfo, &srcinfo, ctx);
2691
+ ip_claim_jpeg_output(ctx, jpeg_size);
2184
2692
  return 1;
2185
2693
 
2186
2694
  fail:
2187
- jpeg_destroy_compress(&dstinfo);
2188
- jpeg_destroy_decompress(&srcinfo);
2695
+ ip_close_codec_pair(&dstinfo, &srcinfo, ctx);
2189
2696
  free(ctx->transient_jpeg_buf);
2190
2697
  ctx->transient_jpeg_buf = NULL;
2191
- ctx->jmp_armed = 0;
2192
2698
  return 0;
2193
2699
  }
2194
2700
 
@@ -2264,16 +2770,16 @@ static void *ip_run_encode_nogvl(void *data) {
2264
2770
  return NULL;
2265
2771
  }
2266
2772
 
2267
- static int ip_run_context(ip_context_t *ctx) {
2773
+ static int ip_invoke(ip_context_t *ctx, void *(*fn)(void *)) {
2268
2774
  ip_resolve_execution(ctx);
2269
2775
 
2270
2776
  if (ctx->resolved_execution == IP_EXEC_DIRECT) {
2271
- ip_run_encode_nogvl(ctx);
2777
+ fn(ctx);
2272
2778
  } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2273
- rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, 0);
2779
+ rb_nogvl(fn, ctx, ip_unblock_function, ctx, 0);
2274
2780
  } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2275
2781
  #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2276
- rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2782
+ rb_nogvl(fn, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2277
2783
  #else
2278
2784
  ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
2279
2785
  "offload execution is unavailable in this runtime; it requires Ruby "
@@ -2289,6 +2795,10 @@ static int ip_run_context(ip_context_t *ctx) {
2289
2795
  return ctx->status == IP_OK;
2290
2796
  }
2291
2797
 
2798
+ static int ip_run_context(ip_context_t *ctx) {
2799
+ return ip_invoke(ctx, ip_run_encode_nogvl);
2800
+ }
2801
+
2292
2802
  static void *ip_run_optimize_nogvl(void *data) {
2293
2803
  ip_context_t *ctx = (ip_context_t *)data;
2294
2804
  if (ctx->status == IP_OK)
@@ -2297,28 +2807,7 @@ static void *ip_run_optimize_nogvl(void *data) {
2297
2807
  }
2298
2808
 
2299
2809
  static int ip_run_optimize_context(ip_context_t *ctx) {
2300
- ip_resolve_execution(ctx);
2301
-
2302
- if (ctx->resolved_execution == IP_EXEC_DIRECT) {
2303
- ip_run_optimize_nogvl(ctx);
2304
- } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2305
- rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, 0);
2306
- } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2307
- #if IMAGE_PACK_HAS_OFFLOAD_SAFE
2308
- rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2309
- #else
2310
- ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
2311
- "offload execution is unavailable in this runtime; it requires Ruby "
2312
- ">= 3.4 and IMAGE_PACK_DISABLE_OFFLOAD must not be set");
2313
- #endif
2314
- } else {
2315
- ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2316
- }
2317
-
2318
- if (ctx->resolved_execution != IP_EXEC_DIRECT)
2319
- rb_thread_check_ints();
2320
-
2321
- return ctx->status == IP_OK;
2810
+ return ip_invoke(ctx, ip_run_optimize_nogvl);
2322
2811
  }
2323
2812
 
2324
2813
  static size_t config_size_value(VALUE config, ID id, size_t fallback, const char *name) {
@@ -2378,161 +2867,151 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
2378
2867
  ctx->decoded_bytes = decoded_bytes;
2379
2868
  }
2380
2869
 
2381
- static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
2382
- ip_compress_jpeg_call_t *call = (ip_compress_jpeg_call_t *)ptr;
2870
+ static ip_context_t *ip_entry_alloc(ip_context_t **slot) {
2383
2871
  ip_context_t *ctx = ip_context_new();
2384
2872
  if (!ctx)
2385
2873
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2386
- call->ctx = ctx;
2874
+ *slot = ctx;
2875
+ return ctx;
2876
+ }
2387
2877
 
2388
- ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2389
- ctx->algo = ip_parse_algo(call->algo);
2390
- ctx->quality = NUM2INT(call->quality);
2878
+ static void ip_bind_runtime(ip_context_t *ctx, VALUE execution, VALUE cancellable,
2879
+ VALUE has_scheduler, VALUE strict) {
2880
+ ctx->requested_execution = ip_parse_execution(execution);
2881
+ ctx->cancellable_requested = ip_bool_value(cancellable);
2882
+ ctx->has_scheduler = ip_bool_value(has_scheduler);
2883
+ ctx->strict = ip_bool_value(strict);
2884
+ }
2885
+
2886
+ static void ip_bind_encode_profile(ip_context_t *ctx, VALUE algo, VALUE quality, VALUE min_ssim,
2887
+ VALUE trellis, VALUE scan_opt, VALUE progressive) {
2888
+ ctx->algo = ip_parse_algo(algo);
2889
+ ctx->quality = NUM2INT(quality);
2391
2890
  ctx->selected_quality = ctx->quality;
2392
2891
  ip_validate_quality_or_raise(ctx);
2393
- ctx->min_ssim = NUM2DBL(call->min_ssim);
2892
+ ctx->min_ssim = NUM2DBL(min_ssim);
2394
2893
  ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
2395
2894
  ip_validate_min_ssim_or_raise(ctx);
2396
- ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2397
- ctx->mozjpeg_scan_opt_enabled = ip_bool_value(call->mozjpeg_scan_opt);
2398
- ctx->progressive = ip_bool_value(call->progressive);
2399
- ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2400
- ctx->requested_execution = ip_parse_execution(call->execution);
2401
- ctx->cancellable_requested = ip_bool_value(call->cancellable);
2402
- ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2403
- ctx->strict = ip_bool_value(call->strict);
2404
- apply_configuration(call->self, ctx);
2895
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(trellis);
2896
+ ctx->mozjpeg_scan_opt_enabled = ip_bool_value(scan_opt);
2897
+ ctx->progressive = ip_bool_value(progressive);
2898
+ }
2405
2899
 
2406
- ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
2407
- if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2408
- !ip_prepare_input_bytes(ctx, call->input, in_kind)) {
2409
- ip_raise_for_status(ctx);
2410
- rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2411
- }
2900
+ static void ip_bind_encode_knobs(ip_context_t *ctx, VALUE subsampling, VALUE tune, VALUE effort) {
2901
+ ctx->subsampling = NUM2INT(subsampling);
2902
+ ctx->tune = NUM2INT(tune);
2903
+ ctx->effort = NUM2INT(effort);
2904
+ }
2412
2905
 
2906
+ static int ip_bind_jpeg_io(ip_context_t *ctx, VALUE input, VALUE input_kind, VALUE output,
2907
+ ip_output_kind_t out_kind, int inspect_allow_cmyk) {
2908
+ ip_input_kind_t in_kind = ip_parse_input_kind(input_kind);
2909
+ if (!ip_prepare_output_path(ctx, output, out_kind) ||
2910
+ !ip_prepare_input_bytes(ctx, input, in_kind))
2911
+ return 0;
2413
2912
  if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2414
- !ip_inspect_jpeg_header(ctx, 0)) {
2415
- ip_raise_for_status(ctx);
2416
- rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2417
- }
2418
-
2913
+ !ip_inspect_jpeg_header(ctx, inspect_allow_cmyk))
2914
+ return 0;
2419
2915
  ip_resolve_execution(ctx);
2420
- if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2421
- ip_raise_for_status(ctx);
2422
- rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2423
- }
2916
+ return ip_ensure_owned_input_for_async(ctx, input, in_kind);
2917
+ }
2918
+
2919
+ static void ip_raise_prepare(ip_context_t *ctx, const char *message) {
2920
+ ip_raise_for_status(ctx);
2921
+ rb_raise(rb_eImagePackInvalidArgumentError, "%s", message);
2922
+ }
2923
+
2924
+ static VALUE ip_complete_encode(ip_context_t *ctx, ip_output_kind_t kind, VALUE report) {
2925
+ VALUE out = ip_finish_output(ctx, kind);
2926
+ return ip_bool_value(report) ? ip_build_report(ctx, out) : out;
2927
+ }
2928
+
2929
+ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
2930
+ ip_compress_jpeg_call_t *call = (ip_compress_jpeg_call_t *)ptr;
2931
+ ip_context_t *ctx = ip_entry_alloc(&call->ctx);
2932
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2933
+
2934
+ ip_bind_encode_profile(ctx, call->algo, call->quality, call->min_ssim, call->mozjpeg_trellis,
2935
+ call->mozjpeg_scan_opt, call->progressive);
2936
+ ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2937
+ ctx->strip_icc = ip_bool_value(call->strip_icc);
2938
+ ip_bind_encode_knobs(ctx, call->subsampling, call->tune, call->effort);
2939
+ ctx->scale_num = (unsigned int)NUM2INT(call->scale_num);
2940
+ ctx->scale_denom = (unsigned int)NUM2INT(call->scale_denom);
2941
+ ip_bind_runtime(ctx, call->execution, call->cancellable, call->has_scheduler, call->strict);
2942
+ apply_configuration(call->self, ctx);
2943
+
2944
+ if (!ip_bind_jpeg_io(ctx, call->input, call->input_kind, call->output, out_kind, 0))
2945
+ ip_raise_prepare(ctx, "invalid JPEG input");
2424
2946
 
2425
2947
  ip_run_context(ctx);
2426
- if (ip_bool_value(call->report)) {
2427
- VALUE output_value = ip_finish_output(ctx, out_kind);
2428
- return ip_build_report(ctx, output_value);
2429
- }
2430
- return ip_finish_output(ctx, out_kind);
2948
+ return ip_complete_encode(ctx, out_kind, call->report);
2431
2949
  }
2432
2950
 
2433
2951
  static VALUE ip_compress_jpeg_entry(int argc, VALUE *argv, VALUE self) {
2434
- rb_check_arity(argc, 16, 16);
2952
+ rb_check_arity(argc, 22, 22);
2435
2953
  ip_compress_jpeg_call_t call = {self, argv[0], argv[1], argv[2], argv[3], argv[4],
2436
2954
  argv[5], argv[6], argv[7], argv[8], argv[9], argv[10],
2437
- argv[11], argv[12], argv[13], argv[14], argv[15], NULL};
2955
+ argv[11], argv[12], argv[13], argv[14], argv[15], argv[16],
2956
+ argv[17], argv[18], argv[19], argv[20], argv[21], NULL};
2438
2957
  return rb_ensure(ip_compress_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
2439
2958
  }
2440
2959
 
2441
2960
  static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2442
2961
  ip_compress_pixels_call_t *call = (ip_compress_pixels_call_t *)ptr;
2443
- ip_context_t *ctx = ip_context_new();
2444
- if (!ctx)
2445
- rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2446
- call->ctx = ctx;
2447
-
2962
+ ip_context_t *ctx = ip_entry_alloc(&call->ctx);
2448
2963
  ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2449
- ctx->algo = ip_parse_algo(call->algo);
2450
- ctx->quality = NUM2INT(call->quality);
2451
- ctx->selected_quality = ctx->quality;
2452
- ip_validate_quality_or_raise(ctx);
2453
- ctx->min_ssim = NUM2DBL(call->min_ssim);
2454
- ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
2455
- ip_validate_min_ssim_or_raise(ctx);
2456
- ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2457
- ctx->mozjpeg_scan_opt_enabled = ip_bool_value(call->mozjpeg_scan_opt);
2458
- ctx->progressive = ip_bool_value(call->progressive);
2964
+
2965
+ ip_bind_encode_profile(ctx, call->algo, call->quality, call->min_ssim, call->mozjpeg_trellis,
2966
+ call->mozjpeg_scan_opt, call->progressive);
2459
2967
  ctx->strip_metadata = 1;
2460
- ctx->requested_execution = ip_parse_execution(call->execution);
2461
- ctx->cancellable_requested = ip_bool_value(call->cancellable);
2462
- ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2463
- ctx->strict = ip_bool_value(call->strict);
2968
+ ctx->strip_icc = 1;
2969
+ ip_bind_encode_knobs(ctx, call->subsampling, call->tune, call->effort);
2970
+ ip_bind_runtime(ctx, call->execution, call->cancellable, call->has_scheduler, call->strict);
2464
2971
  apply_configuration(call->self, ctx);
2465
2972
 
2466
2973
  if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2467
2974
  !ip_prepare_pixels(ctx, call->buffer, NUM2INT(call->width), NUM2INT(call->height),
2468
- NUM2INT(call->channels), ip_bool_value(call->exact_size))) {
2469
- ip_raise_for_status(ctx);
2470
- rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
2471
- }
2975
+ NUM2INT(call->channels), ip_bool_value(call->exact_size)))
2976
+ ip_raise_prepare(ctx, "invalid pixel input");
2472
2977
 
2473
2978
  validate_limits_for_pixels(ctx);
2474
2979
  if (ctx->status != IP_OK)
2475
2980
  ip_raise_for_status(ctx);
2476
2981
 
2477
2982
  ip_resolve_execution(ctx);
2478
- if (!ip_ensure_owned_pixels_for_async(ctx, call->buffer)) {
2479
- ip_raise_for_status(ctx);
2480
- rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
2481
- }
2983
+ if (!ip_ensure_owned_pixels_for_async(ctx, call->buffer))
2984
+ ip_raise_prepare(ctx, "invalid pixel input");
2482
2985
 
2483
2986
  ip_run_context(ctx);
2484
- if (ip_bool_value(call->report)) {
2485
- VALUE output_value = ip_finish_output(ctx, out_kind);
2486
- return ip_build_report(ctx, output_value);
2487
- }
2488
- return ip_finish_output(ctx, out_kind);
2987
+ return ip_complete_encode(ctx, out_kind, call->report);
2489
2988
  }
2490
2989
 
2491
2990
  static VALUE ip_compress_pixels_entry(int argc, VALUE *argv, VALUE self) {
2492
- rb_check_arity(argc, 18, 18);
2493
- ip_compress_pixels_call_t call = {self, argv[0], argv[1], argv[2], argv[3],
2494
- argv[4], argv[5], argv[6], argv[7], argv[8],
2495
- argv[9], argv[10], argv[11], argv[12], argv[13],
2496
- argv[14], argv[15], argv[16], argv[17], NULL};
2991
+ rb_check_arity(argc, 21, 21);
2992
+ ip_compress_pixels_call_t call = {self, argv[0], argv[1], argv[2], argv[3], argv[4],
2993
+ argv[5], argv[6], argv[7], argv[8], argv[9], argv[10],
2994
+ argv[11], argv[12], argv[13], argv[14], argv[15], argv[16],
2995
+ argv[17], argv[18], argv[19], argv[20], NULL};
2497
2996
  return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
2498
2997
  (VALUE)&call.ctx);
2499
2998
  }
2500
2999
 
2501
3000
  static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2502
3001
  ip_optimize_jpeg_call_t *call = (ip_optimize_jpeg_call_t *)ptr;
2503
- ip_context_t *ctx = ip_context_new();
2504
- if (!ctx)
2505
- rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2506
- call->ctx = ctx;
2507
-
3002
+ ip_context_t *ctx = ip_entry_alloc(&call->ctx);
2508
3003
  ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
3004
+
2509
3005
  ctx->progressive = ip_bool_value(call->progressive);
2510
3006
  ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2511
- ctx->requested_execution = ip_parse_execution(call->execution);
2512
- ctx->cancellable_requested = ip_bool_value(call->cancellable);
2513
- ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2514
- ctx->strict = ip_bool_value(call->strict);
3007
+ ctx->strip_icc = ip_bool_value(call->strip_icc);
3008
+ ctx->trim_partial_mcus = ip_bool_value(call->trim);
2515
3009
  ctx->ssim_guard_enabled = 0;
3010
+ ip_bind_runtime(ctx, call->execution, call->cancellable, call->has_scheduler, call->strict);
2516
3011
  apply_configuration(call->self, ctx);
2517
3012
 
2518
- ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
2519
- if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
2520
- !ip_prepare_input_bytes(ctx, call->input, in_kind)) {
2521
- ip_raise_for_status(ctx);
2522
- rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2523
- }
2524
-
2525
- if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
2526
- !ip_inspect_jpeg_header(ctx, 1)) {
2527
- ip_raise_for_status(ctx);
2528
- rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
2529
- }
2530
-
2531
- ip_resolve_execution(ctx);
2532
- if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
2533
- ip_raise_for_status(ctx);
2534
- rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2535
- }
3013
+ if (!ip_bind_jpeg_io(ctx, call->input, call->input_kind, call->output, out_kind, 1))
3014
+ ip_raise_prepare(ctx, "invalid JPEG input");
2536
3015
 
2537
3016
  ip_run_optimize_context(ctx);
2538
3017
  return ip_finish_output(ctx, out_kind);
@@ -2541,10 +3020,10 @@ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2541
3020
  static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
2542
3021
  VALUE output_kind, VALUE progressive, VALUE strip_metadata,
2543
3022
  VALUE execution, VALUE cancellable, VALUE has_scheduler,
2544
- VALUE strict) {
2545
- ip_optimize_jpeg_call_t call = {self, input, input_kind, output,
2546
- output_kind, progressive, strip_metadata, execution,
2547
- cancellable, has_scheduler, strict, NULL};
3023
+ VALUE strict, VALUE strip_icc, VALUE trim) {
3024
+ ip_optimize_jpeg_call_t call = {
3025
+ self, input, input_kind, output, output_kind, progressive, strip_metadata,
3026
+ execution, cancellable, has_scheduler, strict, strip_icc, trim, NULL};
2548
3027
  return rb_ensure(ip_optimize_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
2549
3028
  }
2550
3029
 
@@ -2611,7 +3090,7 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
2611
3090
  int arity;
2612
3091
  } methods[] = {{"__compress_jpeg", (VALUE (*)(ANYARGS))ip_compress_jpeg_entry, -1},
2613
3092
  {"__compress_pixels", (VALUE (*)(ANYARGS))ip_compress_pixels_entry, -1},
2614
- {"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry, 10},
3093
+ {"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry, 12},
2615
3094
  {"__inspect_image", (VALUE (*)(ANYARGS))ip_inspect_image_entry, 2}};
2616
3095
  for (size_t i = 0; i < IP_ARRAY_LEN(methods); i++) {
2617
3096
  rb_define_singleton_method(rb_mImagePack, methods[i].name, methods[i].fn, methods[i].arity);