image_pack 0.2.0 → 0.2.2

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.
@@ -16,6 +16,7 @@
16
16
  #include <stdlib.h>
17
17
  #include <string.h>
18
18
  #include <jpeglib.h>
19
+ #include <jconfigint.h>
19
20
 
20
21
  #ifndef IMAGE_PACK_INIT_EXPORT
21
22
  #if defined(_WIN32)
@@ -39,6 +40,14 @@
39
40
  #define FALSE 0
40
41
  #endif
41
42
 
43
+ #if defined(_MSC_VER) && !defined(__clang__)
44
+ #define IP_RESTRICT __restrict
45
+ #elif defined(__GNUC__) || defined(__clang__)
46
+ #define IP_RESTRICT __restrict
47
+ #else
48
+ #define IP_RESTRICT
49
+ #endif
50
+
42
51
  typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
43
52
 
44
53
  typedef enum {
@@ -105,6 +114,7 @@ typedef struct {
105
114
  int max_width;
106
115
  int max_height;
107
116
  size_t max_output_size;
117
+ size_t max_input_size;
108
118
 
109
119
  ip_status_t status;
110
120
  char error_message[512];
@@ -117,6 +127,19 @@ typedef struct {
117
127
 
118
128
  unsigned char *scratch_row;
119
129
  size_t scratch_row_size;
130
+
131
+ struct {
132
+ int marker;
133
+ unsigned char *data;
134
+ unsigned int len;
135
+ } *preserved_markers;
136
+ size_t preserved_marker_count;
137
+ size_t preserved_marker_capacity;
138
+
139
+ unsigned char *transient_jpeg_buf;
140
+ unsigned char *transient_decode_buf;
141
+ unsigned char *transient_scratch_buf;
142
+ int source_orientation;
120
143
  } ip_context_t;
121
144
 
122
145
  typedef struct {
@@ -152,6 +175,7 @@ static ID id_max_pixels;
152
175
  static ID id_max_width;
153
176
  static ID id_max_height;
154
177
  static ID id_max_output_size;
178
+ static ID id_max_input_size;
155
179
 
156
180
  static ip_context_t *ip_context_new(void);
157
181
  static void ip_context_free(ip_context_t *ctx);
@@ -187,13 +211,45 @@ static void validate_limits_for_pixels(ip_context_t *ctx);
187
211
 
188
212
  static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
189
213
  int *height, int *channels, int fast_decode_mode);
214
+ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char *data, size_t size,
215
+ unsigned char **luma, int *width, int *height);
190
216
  static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode);
191
217
  static int ip_jpeg_turbo_compress(ip_context_t *ctx);
192
218
  static int ip_mozjpeg_compress(ip_context_t *ctx);
219
+ static int ip_lossless_optimize_jpeg(ip_context_t *ctx);
220
+ static int ip_run_optimize_context(ip_context_t *ctx);
193
221
 
194
- #if defined(IMAGE_PACK_ENABLE_LOSSLESS_TRANSCODE_FAST_PATH)
195
- static int ip_jpeg_transcode_coefficients(ip_context_t *ctx, int mozjpeg_size_mode);
196
- #endif
222
+ typedef struct {
223
+ VALUE self, input, input_kind, output, output_kind, algo, quality, min_ssim;
224
+ VALUE mozjpeg_trellis, progressive, strip_metadata, execution, cancellable, has_scheduler;
225
+ ip_context_t *ctx;
226
+ } ip_compress_jpeg_call_t;
227
+
228
+ typedef struct {
229
+ VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
230
+ VALUE progressive, execution, cancellable, has_scheduler;
231
+ ip_context_t *ctx;
232
+ } ip_compress_pixels_call_t;
233
+
234
+ typedef struct {
235
+ VALUE self, input, input_kind, output, output_kind, progressive, strip_metadata;
236
+ VALUE execution, cancellable, has_scheduler;
237
+ ip_context_t *ctx;
238
+ } ip_optimize_jpeg_call_t;
239
+
240
+ typedef struct {
241
+ VALUE self, input, input_kind;
242
+ ip_context_t *ctx;
243
+ } ip_inspect_call_t;
244
+
245
+ static VALUE ip_call_cleanup(VALUE ptr) {
246
+ ip_context_t **ctx_ptr = (ip_context_t **)ptr;
247
+ if (ctx_ptr && *ctx_ptr) {
248
+ ip_context_free(*ctx_ptr);
249
+ *ctx_ptr = NULL;
250
+ }
251
+ return Qnil;
252
+ }
197
253
 
198
254
  static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
199
255
  VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
@@ -201,8 +257,11 @@ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, V
201
257
  VALUE execution, VALUE cancellable, VALUE has_scheduler);
202
258
  static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
203
259
  VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
204
- VALUE quality, VALUE progressive, VALUE execution,
205
- VALUE cancellable, VALUE has_scheduler);
260
+ VALUE quality, VALUE min_ssim, VALUE progressive,
261
+ VALUE execution, VALUE cancellable, VALUE has_scheduler);
262
+ static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
263
+ VALUE output_kind, VALUE progressive, VALUE strip_metadata,
264
+ VALUE execution, VALUE cancellable, VALUE has_scheduler);
206
265
 
207
266
  static VALUE ip_status_to_exception(ip_status_t status) {
208
267
  switch (status) {
@@ -251,12 +310,15 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
251
310
  return ip_checked_mul_size(pixels, (size_t)channels, out);
252
311
  }
253
312
 
313
+ static void *ip_malloc_hot(size_t size) {
314
+ return malloc(size);
315
+ }
316
+
254
317
  static void ip_validate_quality_or_raise(ip_context_t *ctx) {
255
318
  if (ctx->quality >= 1 && ctx->quality <= 100)
256
319
  return;
257
320
 
258
321
  int quality = ctx->quality;
259
- ip_context_free(ctx);
260
322
  rb_raise(rb_eImagePackInvalidArgumentError, "quality must be Integer 1..100, got: %d", quality);
261
323
  }
262
324
 
@@ -267,7 +329,6 @@ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
267
329
  return;
268
330
 
269
331
  double min_ssim = ctx->min_ssim;
270
- ip_context_free(ctx);
271
332
  rb_raise(rb_eImagePackInvalidArgumentError,
272
333
  "min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
273
334
  }
@@ -297,6 +358,8 @@ static ip_context_t *ip_context_new(void) {
297
358
  ctx->max_width = 30000;
298
359
  ctx->max_height = 30000;
299
360
  ctx->max_output_size = 256 * 1024 * 1024;
361
+ ctx->max_input_size = 256 * 1024 * 1024;
362
+ ctx->source_orientation = 1;
300
363
  atomic_init(&ctx->cancelled, 0);
301
364
  return ctx;
302
365
  }
@@ -309,6 +372,16 @@ static void ip_context_free(ip_context_t *ctx) {
309
372
  free(ctx->owned_pixel_data);
310
373
  free(ctx->output_path);
311
374
  free(ctx->scratch_row);
375
+ free(ctx->transient_jpeg_buf);
376
+ free(ctx->transient_decode_buf);
377
+ free(ctx->transient_scratch_buf);
378
+
379
+ if (ctx->preserved_markers) {
380
+ for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
381
+ free(ctx->preserved_markers[i].data);
382
+ }
383
+ free(ctx->preserved_markers);
384
+ }
312
385
 
313
386
  if (ctx->output_data && ctx->output_owner == IP_OUTPUT_OWNER_MALLOC) {
314
387
  free(ctx->output_data);
@@ -405,6 +478,12 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
405
478
  }
406
479
  rewind(fp);
407
480
 
481
+ if (ctx->max_input_size > 0 && (size_t)size > ctx->max_input_size) {
482
+ fclose(fp);
483
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "input file exceeds max_input_size");
484
+ return 0;
485
+ }
486
+
408
487
  unsigned char *data = (unsigned char *)malloc((size_t)size);
409
488
  if (!data && size > 0) {
410
489
  fclose(fp);
@@ -435,6 +514,16 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
435
514
  if (kind == IP_INPUT_BYTES) {
436
515
  Check_Type(input, T_STRING);
437
516
  size_t len = (size_t)RSTRING_LEN(input);
517
+ if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
518
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
519
+ return 0;
520
+ }
521
+ if (ctx->requested_execution == IP_EXEC_DIRECT) {
522
+ ctx->input_data = (const unsigned char *)RSTRING_PTR(input);
523
+ ctx->input_size = len;
524
+ return 1;
525
+ }
526
+
438
527
  unsigned char *copy = (unsigned char *)malloc(len);
439
528
  if (!copy && len > 0) {
440
529
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
@@ -452,6 +541,10 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
452
541
  VALUE str = io_buffer_to_string(input);
453
542
  StringValue(str);
454
543
  size_t len = (size_t)RSTRING_LEN(str);
544
+ if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
545
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "input IO::Buffer exceeds max_input_size");
546
+ return 0;
547
+ }
455
548
  unsigned char *copy = (unsigned char *)malloc(len);
456
549
  if (!copy && len > 0) {
457
550
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy IO::Buffer input");
@@ -492,7 +585,18 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
492
585
  return 0;
493
586
  }
494
587
 
495
- unsigned char *copy = (unsigned char *)malloc(expected);
588
+ if (ctx->requested_execution == IP_EXEC_DIRECT && RB_TYPE_P(buffer, T_STRING)) {
589
+ ctx->pixel_data = (const unsigned char *)RSTRING_PTR(str);
590
+ ctx->pixel_size = expected;
591
+ ctx->width = width;
592
+ ctx->height = height;
593
+ ctx->channels = channels;
594
+ ctx->bit_depth = 8;
595
+ ctx->decoded_bytes = expected;
596
+ return 1;
597
+ }
598
+
599
+ unsigned char *copy = (unsigned char *)ip_malloc_hot(expected);
496
600
  if (!copy && expected > 0) {
497
601
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
498
602
  return 0;
@@ -530,26 +634,248 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
530
634
  ip_raise_for_status(ctx);
531
635
 
532
636
  if (kind == IP_OUTPUT_RETURN_STRING) {
637
+ if (ctx->output_size > (size_t)LONG_MAX)
638
+ rb_raise(rb_eImagePackLimitExceededError, "output is too large for a Ruby String");
533
639
  VALUE out = rb_str_new((const char *)ctx->output_data, (long)ctx->output_size);
534
640
  rb_enc_associate(out, rb_ascii8bit_encoding());
535
641
  return out;
536
642
  }
537
643
 
538
- FILE *fp = fopen(ctx->output_path, "wb");
644
+ size_t path_len = strlen(ctx->output_path);
645
+ char suffix[64];
646
+ snprintf(suffix, sizeof(suffix), ".tmp.image_pack.%p", (void *)ctx);
647
+ size_t tmp_len = path_len + strlen(suffix) + 1;
648
+ char *tmp_path = (char *)malloc(tmp_len);
649
+ if (!tmp_path)
650
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate temporary output path");
651
+ snprintf(tmp_path, tmp_len, "%s%s", ctx->output_path, suffix);
652
+
653
+ FILE *fp = fopen(tmp_path, "wb");
539
654
  if (!fp) {
655
+ free(tmp_path);
540
656
  rb_raise(rb_eImagePackInvalidArgumentError, "failed to open output path: %s",
541
657
  ctx->output_path);
542
658
  }
543
659
 
544
660
  size_t written = fwrite(ctx->output_data, 1, ctx->output_size, fp);
545
- fclose(fp);
546
- if (written != ctx->output_size) {
661
+ int write_failed = written != ctx->output_size || ferror(fp);
662
+ int close_failed = fclose(fp) != 0;
663
+ if (write_failed || close_failed) {
664
+ remove(tmp_path);
665
+ free(tmp_path);
547
666
  rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
548
667
  }
549
668
 
669
+ if (rename(tmp_path, ctx->output_path) != 0) {
670
+ remove(tmp_path);
671
+ free(tmp_path);
672
+ rb_raise(rb_eImagePackEncodeError, "failed to move temporary JPEG output into place");
673
+ }
674
+
675
+ free(tmp_path);
550
676
  return Qtrue;
551
677
  }
552
678
 
679
+ static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
680
+ unsigned int len) {
681
+ if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
682
+ size_t new_cap =
683
+ ctx->preserved_marker_capacity == 0 ? 4 : ctx->preserved_marker_capacity * 2;
684
+ void *new_buf = realloc(ctx->preserved_markers, new_cap * sizeof(*ctx->preserved_markers));
685
+ if (!new_buf)
686
+ return 0;
687
+ ctx->preserved_markers = new_buf;
688
+ ctx->preserved_marker_capacity = new_cap;
689
+ }
690
+
691
+ unsigned char *copy = (unsigned char *)malloc(len);
692
+ if (!copy && len > 0)
693
+ return 0;
694
+ if (len > 0)
695
+ memcpy(copy, data, len);
696
+
697
+ ctx->preserved_markers[ctx->preserved_marker_count].marker = marker;
698
+ ctx->preserved_markers[ctx->preserved_marker_count].data = copy;
699
+ ctx->preserved_markers[ctx->preserved_marker_count].len = len;
700
+ ctx->preserved_marker_count++;
701
+ return 1;
702
+ }
703
+
704
+ static int ip_save_markers_from_decompress(ip_context_t *ctx,
705
+ struct jpeg_decompress_struct *cinfo) {
706
+ jpeg_saved_marker_ptr m;
707
+ for (m = cinfo->marker_list; m != NULL; m = m->next) {
708
+ if (m->marker == (JPEG_APP0 + 0))
709
+ continue;
710
+
711
+ if (!ip_save_marker(ctx, m->marker, m->data, m->data_length)) {
712
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to preserve JPEG metadata marker");
713
+ return 0;
714
+ }
715
+ }
716
+ return 1;
717
+ }
718
+
719
+ static uint16_t ip_exif_u16(const unsigned char *p, int little_endian) {
720
+ if (little_endian)
721
+ return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
722
+ return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
723
+ }
724
+
725
+ static uint32_t ip_exif_u32(const unsigned char *p, int little_endian) {
726
+ if (little_endian)
727
+ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
728
+ ((uint32_t)p[3] << 24);
729
+ return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
730
+ }
731
+
732
+ static int ip_parse_exif_orientation(const unsigned char *data, unsigned int len) {
733
+ if (!data || len < 14)
734
+ return 1;
735
+ if (memcmp(data, "Exif\0\0", 6) != 0)
736
+ return 1;
737
+
738
+ const unsigned char *tiff = data + 6;
739
+ size_t tiff_len = (size_t)len - 6;
740
+ if (tiff_len < 8)
741
+ return 1;
742
+
743
+ int little_endian = 0;
744
+ if (tiff[0] == 'I' && tiff[1] == 'I')
745
+ little_endian = 1;
746
+ else if (tiff[0] == 'M' && tiff[1] == 'M')
747
+ little_endian = 0;
748
+ else
749
+ return 1;
750
+
751
+ if (ip_exif_u16(tiff + 2, little_endian) != 42)
752
+ return 1;
753
+
754
+ uint32_t ifd0_offset = ip_exif_u32(tiff + 4, little_endian);
755
+ if (ifd0_offset > tiff_len || tiff_len - ifd0_offset < 2)
756
+ return 1;
757
+
758
+ const unsigned char *ifd = tiff + ifd0_offset;
759
+ size_t ifd_len = tiff_len - ifd0_offset;
760
+ uint16_t entries = ip_exif_u16(ifd, little_endian);
761
+ ifd += 2;
762
+ ifd_len -= 2;
763
+
764
+ for (uint16_t i = 0; i < entries; i++) {
765
+ if (ifd_len < 12)
766
+ return 1;
767
+
768
+ uint16_t tag = ip_exif_u16(ifd, little_endian);
769
+ uint16_t type = ip_exif_u16(ifd + 2, little_endian);
770
+ uint32_t count = ip_exif_u32(ifd + 4, little_endian);
771
+
772
+ if (tag == 0x0112 && type == 3 && count == 1) {
773
+ uint16_t orientation = ip_exif_u16(ifd + 8, little_endian);
774
+ if (orientation >= 1 && orientation <= 8)
775
+ return (int)orientation;
776
+ return 1;
777
+ }
778
+
779
+ ifd += 12;
780
+ ifd_len -= 12;
781
+ }
782
+
783
+ return 1;
784
+ }
785
+
786
+ static int ip_read_exif_orientation_from_decompress(struct jpeg_decompress_struct *cinfo) {
787
+ jpeg_saved_marker_ptr m;
788
+ for (m = cinfo->marker_list; m != NULL; m = m->next) {
789
+ if (m->marker == (JPEG_APP0 + 1)) {
790
+ int orientation = ip_parse_exif_orientation(m->data, m->data_length);
791
+ if (orientation >= 2 && orientation <= 8)
792
+ return orientation;
793
+ }
794
+ }
795
+ return 1;
796
+ }
797
+
798
+ static int ip_transform_pixels_for_orientation(ip_context_t *ctx, unsigned char **pixels,
799
+ int *width, int *height, int channels) {
800
+ int orientation = ctx->source_orientation;
801
+ if (orientation <= 1 || orientation > 8)
802
+ return 1;
803
+
804
+ int src_w = *width;
805
+ int src_h = *height;
806
+ int dst_w = (orientation >= 5 && orientation <= 8) ? src_h : src_w;
807
+ int dst_h = (orientation >= 5 && orientation <= 8) ? src_w : src_h;
808
+ size_t out_size = 0;
809
+ if (!ip_checked_image_size(dst_w, dst_h, channels, &out_size)) {
810
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "oriented image size overflows native size");
811
+ return 0;
812
+ }
813
+
814
+ unsigned char *src = *pixels;
815
+ unsigned char *dst = (unsigned char *)ip_malloc_hot(out_size);
816
+ if (!dst && out_size > 0) {
817
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate EXIF-oriented pixel buffer");
818
+ return 0;
819
+ }
820
+
821
+ for (int y = 0; y < src_h; y++) {
822
+ for (int x = 0; x < src_w; x++) {
823
+ int dx = x;
824
+ int dy = y;
825
+ switch (orientation) {
826
+ case 2:
827
+ dx = src_w - 1 - x;
828
+ dy = y;
829
+ break;
830
+ case 3:
831
+ dx = src_w - 1 - x;
832
+ dy = src_h - 1 - y;
833
+ break;
834
+ case 4:
835
+ dx = x;
836
+ dy = src_h - 1 - y;
837
+ break;
838
+ case 5:
839
+ dx = y;
840
+ dy = x;
841
+ break;
842
+ case 6:
843
+ dx = src_h - 1 - y;
844
+ dy = x;
845
+ break;
846
+ case 7:
847
+ dx = src_h - 1 - y;
848
+ dy = src_w - 1 - x;
849
+ break;
850
+ case 8:
851
+ dx = y;
852
+ dy = src_w - 1 - x;
853
+ break;
854
+ default:
855
+ break;
856
+ }
857
+
858
+ memcpy(dst + (((size_t)dy * (size_t)dst_w + (size_t)dx) * (size_t)channels),
859
+ src + (((size_t)y * (size_t)src_w + (size_t)x) * (size_t)channels),
860
+ (size_t)channels);
861
+ }
862
+ }
863
+
864
+ free(src);
865
+ *pixels = dst;
866
+ *width = dst_w;
867
+ *height = dst_h;
868
+ return 1;
869
+ }
870
+
871
+ static void ip_write_preserved_markers(ip_context_t *ctx, struct jpeg_compress_struct *cinfo) {
872
+ for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
873
+ jpeg_write_marker(cinfo, ctx->preserved_markers[i].marker,
874
+ (const JOCTET *)ctx->preserved_markers[i].data,
875
+ ctx->preserved_markers[i].len);
876
+ }
877
+ }
878
+
553
879
  static void ip_jpeg_invalid_error_exit(j_common_ptr cinfo) {
554
880
  ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
555
881
  char buffer[JMSG_LENGTH_MAX];
@@ -601,11 +927,23 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
601
927
  return 0;
602
928
  }
603
929
 
930
+ if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
931
+ cinfo.jpeg_color_space == JCS_YCCK) {
932
+ jpeg_destroy_decompress(&cinfo);
933
+ ctx->jmp_armed = 0;
934
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
935
+ "CMYK/YCCK JPEG input is not supported in this release");
936
+ return 0;
937
+ }
938
+
604
939
  ctx->width = (int)cinfo.image_width;
605
940
  ctx->height = (int)cinfo.image_height;
606
941
  ctx->channels = cinfo.num_components;
607
942
  if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4) {
608
- ctx->channels = 3;
943
+ jpeg_destroy_decompress(&cinfo);
944
+ ctx->jmp_armed = 0;
945
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED, "JPEG component count is not supported");
946
+ return 0;
609
947
  }
610
948
  ctx->bit_depth = 8;
611
949
  ctx->decoded_bytes = (size_t)ctx->width * (size_t)ctx->height * (size_t)ctx->channels;
@@ -618,15 +956,15 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
618
956
  return 0;
619
957
  }
620
958
 
621
- if (ctx->width > ctx->max_width) {
959
+ if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
622
960
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
623
961
  return 0;
624
962
  }
625
- if (ctx->height > ctx->max_height) {
963
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
626
964
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
627
965
  return 0;
628
966
  }
629
- if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
967
+ if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
630
968
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
631
969
  return 0;
632
970
  }
@@ -634,32 +972,43 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
634
972
  return 1;
635
973
  }
636
974
 
637
- static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
638
- (void)self;
975
+ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
976
+ ip_inspect_call_t *call = (ip_inspect_call_t *)ptr;
639
977
  ip_context_t *ctx = ip_context_new();
640
978
  if (!ctx)
641
979
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
980
+ call->ctx = ctx;
642
981
 
643
- if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
982
+ if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
644
983
  !ip_inspect_jpeg_header(ctx)) {
645
- VALUE exception = ip_status_to_exception(ctx->status);
646
- char message[512];
647
- snprintf(message, sizeof(message), "%s", ctx->error_message);
648
- ip_context_free(ctx);
649
- rb_raise(exception, "%s", message[0] ? message : "failed to inspect JPEG image");
984
+ ip_raise_for_status(ctx);
985
+ rb_raise(rb_eImagePackInvalidImageError, "failed to inspect JPEG image");
650
986
  }
651
987
 
988
+ int width = ctx->width;
989
+ int height = ctx->height;
990
+ int channels = ctx->channels;
991
+ int bit_depth = ctx->bit_depth;
992
+ size_t decoded_bytes = ctx->decoded_bytes;
993
+
994
+ ip_context_free(ctx);
995
+ call->ctx = NULL;
996
+
652
997
  VALUE hash = rb_hash_new();
653
998
  rb_hash_aset(hash, ID2SYM(rb_intern("format")), ID2SYM(rb_intern("jpeg")));
654
- rb_hash_aset(hash, ID2SYM(rb_intern("width")), INT2NUM(ctx->width));
655
- rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(ctx->height));
656
- rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(ctx->channels));
657
- rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(ctx->bit_depth));
658
- rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(ctx->decoded_bytes));
659
- ip_context_free(ctx);
999
+ rb_hash_aset(hash, ID2SYM(rb_intern("width")), INT2NUM(width));
1000
+ rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(height));
1001
+ rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(channels));
1002
+ rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(bit_depth));
1003
+ rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(decoded_bytes));
660
1004
  return hash;
661
1005
  }
662
1006
 
1007
+ static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
1008
+ ip_inspect_call_t call = {self, input, input_kind, NULL};
1009
+ return rb_ensure(ip_inspect_image_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
1010
+ }
1011
+
663
1012
  static J_COLOR_SPACE color_space_for_channels(int channels) {
664
1013
  return channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
665
1014
  }
@@ -677,6 +1026,15 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
677
1026
  if (mozjpeg_size_mode) {
678
1027
  cinfo->optimize_coding = TRUE;
679
1028
 
1029
+ if (progressive_requested) {
1030
+ jpeg_simple_progression(cinfo);
1031
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, TRUE);
1032
+ } else {
1033
+ cinfo->scan_info = NULL;
1034
+ cinfo->num_scans = 0;
1035
+ jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
1036
+ }
1037
+
680
1038
  if (!mozjpeg_trellis_enabled) {
681
1039
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
682
1040
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
@@ -710,63 +1068,77 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
710
1068
  }
711
1069
  }
712
1070
 
713
- static int prepare_encode_row(ip_context_t *ctx, JDIMENSION y, JSAMPROW *row) {
714
- if (ctx->channels == 4) {
715
- size_t rgb_row_size = 0;
716
- if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size)) {
717
- ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
718
- return 0;
1071
+ static int prepare_encode_rows(ip_context_t *ctx, JDIMENSION start, JDIMENSION batch,
1072
+ JSAMPROW *rows) {
1073
+ if (ctx->channels != 4) {
1074
+ for (JDIMENSION i = 0; i < batch; i++) {
1075
+ JDIMENSION y = start + i;
1076
+ rows[i] = (JSAMPROW)(ctx->pixel_data +
1077
+ ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
719
1078
  }
1079
+ return 1;
1080
+ }
720
1081
 
721
- if (ctx->scratch_row_size < rgb_row_size) {
722
- unsigned char *new_row = (unsigned char *)realloc(ctx->scratch_row, rgb_row_size);
723
- if (!new_row) {
724
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch row");
725
- return 0;
726
- }
727
- ctx->scratch_row = new_row;
728
- ctx->scratch_row_size = rgb_row_size;
1082
+ size_t rgb_row_size = 0;
1083
+ size_t scratch_size = 0;
1084
+ if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size) ||
1085
+ !ip_checked_mul_size(rgb_row_size, (size_t)batch, &scratch_size)) {
1086
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
1087
+ return 0;
1088
+ }
1089
+
1090
+ if (ctx->scratch_row_size < scratch_size) {
1091
+ unsigned char *new_rows = (unsigned char *)realloc(ctx->scratch_row, scratch_size);
1092
+ if (!new_rows) {
1093
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch rows");
1094
+ return 0;
729
1095
  }
1096
+ ctx->scratch_row = new_rows;
1097
+ ctx->scratch_row_size = scratch_size;
1098
+ }
730
1099
 
731
- const unsigned char *src = ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
732
- unsigned char *dst = ctx->scratch_row;
733
- for (int x = 0; x < ctx->width; x++) {
1100
+ for (JDIMENSION i = 0; i < batch; i++) {
1101
+ JDIMENSION y = start + i;
1102
+ const unsigned char *IP_RESTRICT src =
1103
+ ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
1104
+ unsigned char *IP_RESTRICT dst = ctx->scratch_row + ((size_t)i * rgb_row_size);
1105
+ const int w = ctx->width;
1106
+ for (int x = 0; x < w; x++) {
734
1107
  dst[x * 3 + 0] = src[x * 4 + 0];
735
1108
  dst[x * 3 + 1] = src[x * 4 + 1];
736
1109
  dst[x * 3 + 2] = src[x * 4 + 2];
737
1110
  }
738
- *row = ctx->scratch_row;
739
- return 1;
1111
+ rows[i] = dst;
740
1112
  }
741
1113
 
742
- *row = (JSAMPROW)(ctx->pixel_data + ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
743
1114
  return 1;
744
1115
  }
745
1116
 
746
1117
  static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
747
1118
  struct jpeg_compress_struct cinfo;
748
1119
  ip_jpeg_error_mgr jerr;
1120
+ unsigned long jpeg_size = 0;
749
1121
  memset(&cinfo, 0, sizeof(cinfo));
750
1122
  memset(&jerr, 0, sizeof(jerr));
751
1123
 
752
1124
  cinfo.err = jpeg_std_error(&jerr.pub);
753
1125
  jerr.pub.error_exit = ip_jpeg_encode_error_exit;
754
1126
  jerr.ctx = ctx;
1127
+ ctx->transient_jpeg_buf = NULL;
755
1128
 
756
1129
  ctx->jmp_armed = 1;
757
1130
  if (setjmp(ctx->jmpbuf)) {
758
1131
  ctx->jmp_armed = 0;
759
1132
  jpeg_destroy_compress(&cinfo);
1133
+ free(ctx->transient_jpeg_buf);
1134
+ ctx->transient_jpeg_buf = NULL;
760
1135
  if (ctx->status == IP_OK)
761
1136
  ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
762
1137
  return 0;
763
1138
  }
764
1139
 
765
1140
  jpeg_create_compress(&cinfo);
766
-
767
- unsigned char *jpeg_buf = NULL;
768
- unsigned long jpeg_size = 0;
769
- jpeg_mem_dest(&cinfo, &jpeg_buf, &jpeg_size);
1141
+ jpeg_mem_dest(&cinfo, &ctx->transient_jpeg_buf, &jpeg_size);
770
1142
 
771
1143
  cinfo.image_width = (JDIMENSION)ctx->width;
772
1144
  cinfo.image_height = (JDIMENSION)ctx->height;
@@ -781,40 +1153,52 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
781
1153
 
782
1154
  jpeg_start_compress(&cinfo, TRUE);
783
1155
 
1156
+ if (!ctx->strip_metadata) {
1157
+ ip_write_preserved_markers(ctx, &cinfo);
1158
+ }
1159
+
784
1160
  while (cinfo.next_scanline < cinfo.image_height) {
785
- if (ctx->cancellable_requested && (cinfo.next_scanline % 16 == 0) &&
786
- atomic_load(&ctx->cancelled)) {
1161
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
787
1162
  ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
788
1163
  jpeg_abort_compress(&cinfo);
789
1164
  jpeg_destroy_compress(&cinfo);
790
- free(jpeg_buf);
1165
+ free(ctx->transient_jpeg_buf);
1166
+ ctx->transient_jpeg_buf = NULL;
791
1167
  ctx->jmp_armed = 0;
792
1168
  return 0;
793
1169
  }
794
1170
 
795
- JSAMPROW row = NULL;
796
- if (!prepare_encode_row(ctx, cinfo.next_scanline, &row)) {
1171
+ JSAMPROW rows[16];
1172
+ JDIMENSION start_scanline = cinfo.next_scanline;
1173
+ JDIMENSION batch = cinfo.image_height - start_scanline;
1174
+ if (batch > 16)
1175
+ batch = 16;
1176
+
1177
+ if (!prepare_encode_rows(ctx, start_scanline, batch, rows)) {
797
1178
  jpeg_abort_compress(&cinfo);
798
1179
  jpeg_destroy_compress(&cinfo);
799
- free(jpeg_buf);
1180
+ free(ctx->transient_jpeg_buf);
1181
+ ctx->transient_jpeg_buf = NULL;
800
1182
  ctx->jmp_armed = 0;
801
1183
  return 0;
802
1184
  }
803
1185
 
804
- jpeg_write_scanlines(&cinfo, &row, 1);
1186
+ jpeg_write_scanlines(&cinfo, rows, batch);
805
1187
  }
806
1188
 
807
1189
  jpeg_finish_compress(&cinfo);
808
1190
  jpeg_destroy_compress(&cinfo);
809
1191
  ctx->jmp_armed = 0;
810
1192
 
811
- if ((size_t)jpeg_size > ctx->max_output_size) {
812
- free(jpeg_buf);
1193
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size) {
1194
+ free(ctx->transient_jpeg_buf);
1195
+ ctx->transient_jpeg_buf = NULL;
813
1196
  ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
814
1197
  return 0;
815
1198
  }
816
1199
 
817
- ctx->output_data = jpeg_buf;
1200
+ ctx->output_data = ctx->transient_jpeg_buf;
1201
+ ctx->transient_jpeg_buf = NULL;
818
1202
  ctx->output_size = (size_t)jpeg_size;
819
1203
  ctx->output_capacity = (size_t)jpeg_size;
820
1204
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
@@ -831,11 +1215,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
831
1215
  cinfo.err = jpeg_std_error(&jerr.pub);
832
1216
  jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
833
1217
  jerr.ctx = ctx;
1218
+ ctx->transient_decode_buf = NULL;
1219
+ ctx->source_orientation = 1;
834
1220
 
835
1221
  ctx->jmp_armed = 1;
836
1222
  if (setjmp(ctx->jmpbuf)) {
837
1223
  ctx->jmp_armed = 0;
838
1224
  jpeg_destroy_decompress(&cinfo);
1225
+ free(ctx->transient_decode_buf);
1226
+ ctx->transient_decode_buf = NULL;
839
1227
  if (ctx->status == IP_OK)
840
1228
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
841
1229
  return 0;
@@ -843,6 +1231,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
843
1231
 
844
1232
  jpeg_create_decompress(&cinfo);
845
1233
  jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
1234
+
1235
+ jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
1236
+ if (!ctx->strip_metadata) {
1237
+ jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
1238
+ for (int app = 2; app < 16; app++) {
1239
+ jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
1240
+ }
1241
+ }
1242
+
846
1243
  int rc = jpeg_read_header(&cinfo, TRUE);
847
1244
  if (rc != JPEG_HEADER_OK) {
848
1245
  jpeg_destroy_decompress(&cinfo);
@@ -851,6 +1248,8 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
851
1248
  return 0;
852
1249
  }
853
1250
 
1251
+ ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
1252
+
854
1253
  if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
855
1254
  jpeg_destroy_decompress(&cinfo);
856
1255
  ctx->jmp_armed = 0;
@@ -858,6 +1257,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
858
1257
  return 0;
859
1258
  }
860
1259
 
1260
+ if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1261
+ cinfo.jpeg_color_space == JCS_YCCK) {
1262
+ jpeg_destroy_decompress(&cinfo);
1263
+ ctx->jmp_armed = 0;
1264
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1265
+ "CMYK/YCCK JPEG input is not supported in this release");
1266
+ return 0;
1267
+ }
1268
+
861
1269
  int ch = cinfo.num_components == 1 ? 1 : 3;
862
1270
 
863
1271
  ctx->width = (int)cinfo.image_width;
@@ -892,6 +1300,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
892
1300
  }
893
1301
 
894
1302
  jpeg_start_decompress(&cinfo);
1303
+
1304
+ if (!ctx->strip_metadata) {
1305
+ if (!ip_save_markers_from_decompress(ctx, &cinfo)) {
1306
+ jpeg_destroy_decompress(&cinfo);
1307
+ ctx->jmp_armed = 0;
1308
+ return 0;
1309
+ }
1310
+ }
1311
+
895
1312
  size_t row_stride = 0;
896
1313
  size_t size = 0;
897
1314
  if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
@@ -902,8 +1319,8 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
902
1319
  ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
903
1320
  return 0;
904
1321
  }
905
- unsigned char *buf = (unsigned char *)malloc(size);
906
- if (!buf && size > 0) {
1322
+ ctx->transient_decode_buf = (unsigned char *)ip_malloc_hot(size);
1323
+ if (!ctx->transient_decode_buf && size > 0) {
907
1324
  jpeg_destroy_decompress(&cinfo);
908
1325
  ctx->jmp_armed = 0;
909
1326
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
@@ -911,8 +1328,27 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
911
1328
  }
912
1329
 
913
1330
  while (cinfo.output_scanline < cinfo.output_height) {
914
- JSAMPROW row = buf + ((size_t)cinfo.output_scanline * row_stride);
915
- jpeg_read_scanlines(&cinfo, &row, 1);
1331
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1332
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
1333
+ jpeg_abort_decompress(&cinfo);
1334
+ jpeg_destroy_decompress(&cinfo);
1335
+ free(ctx->transient_decode_buf);
1336
+ ctx->transient_decode_buf = NULL;
1337
+ ctx->jmp_armed = 0;
1338
+ return 0;
1339
+ }
1340
+
1341
+ JSAMPROW rows[16];
1342
+ JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1343
+ if (batch > 16)
1344
+ batch = 16;
1345
+
1346
+ for (JDIMENSION i = 0; i < batch; i++) {
1347
+ rows[i] =
1348
+ ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1349
+ }
1350
+
1351
+ jpeg_read_scanlines(&cinfo, rows, batch);
916
1352
  }
917
1353
 
918
1354
  int out_width = (int)cinfo.output_width;
@@ -922,6 +1358,16 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
922
1358
  jpeg_destroy_decompress(&cinfo);
923
1359
  ctx->jmp_armed = 0;
924
1360
 
1361
+ unsigned char *buf = ctx->transient_decode_buf;
1362
+ ctx->transient_decode_buf = NULL;
1363
+
1364
+ if (ctx->strip_metadata && ctx->source_orientation > 1) {
1365
+ if (!ip_transform_pixels_for_orientation(ctx, &buf, &out_width, &out_height, ch)) {
1366
+ free(buf);
1367
+ return 0;
1368
+ }
1369
+ }
1370
+
925
1371
  *pixels = buf;
926
1372
  *width = out_width;
927
1373
  *height = out_height;
@@ -965,29 +1411,154 @@ static void ip_clear_output_buffer(ip_context_t *ctx) {
965
1411
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
966
1412
  }
967
1413
 
968
- static int ip_decode_jpeg_buffer_preserving_context(ip_context_t *ctx, const unsigned char *data,
969
- size_t size, unsigned char **pixels, int *width,
970
- int *height, int *channels) {
971
- const unsigned char *old_input_data = ctx->input_data;
972
- size_t old_input_size = ctx->input_size;
1414
+ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char *data, size_t size,
1415
+ unsigned char **luma, int *width, int *height) {
1416
+ struct jpeg_decompress_struct cinfo;
1417
+ ip_jpeg_error_mgr jerr;
1418
+ memset(&cinfo, 0, sizeof(cinfo));
1419
+ memset(&jerr, 0, sizeof(jerr));
1420
+
1421
+ cinfo.err = jpeg_std_error(&jerr.pub);
1422
+ jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1423
+ jerr.ctx = ctx;
1424
+ ctx->transient_decode_buf = NULL;
1425
+
1426
+ ctx->jmp_armed = 1;
1427
+ if (setjmp(ctx->jmpbuf)) {
1428
+ ctx->jmp_armed = 0;
1429
+ jpeg_destroy_decompress(&cinfo);
1430
+ free(ctx->transient_decode_buf);
1431
+ ctx->transient_decode_buf = NULL;
1432
+ if (ctx->status == IP_OK)
1433
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG luma decode failed");
1434
+ return 0;
1435
+ }
1436
+
1437
+ jpeg_create_decompress(&cinfo);
1438
+ jpeg_mem_src(&cinfo, data, (unsigned long)size);
1439
+
1440
+ int rc = jpeg_read_header(&cinfo, TRUE);
1441
+ if (rc != JPEG_HEADER_OK) {
1442
+ jpeg_destroy_decompress(&cinfo);
1443
+ ctx->jmp_armed = 0;
1444
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1445
+ return 0;
1446
+ }
1447
+
1448
+ if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
1449
+ jpeg_destroy_decompress(&cinfo);
1450
+ ctx->jmp_armed = 0;
1451
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1452
+ return 0;
1453
+ }
1454
+
1455
+ if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
1456
+ cinfo.jpeg_color_space == JCS_YCCK) {
1457
+ jpeg_destroy_decompress(&cinfo);
1458
+ ctx->jmp_armed = 0;
1459
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1460
+ "CMYK/YCCK JPEG input is not supported in this release");
1461
+ return 0;
1462
+ }
1463
+
973
1464
  int old_width = ctx->width;
974
1465
  int old_height = ctx->height;
975
1466
  int old_channels = ctx->channels;
976
- int old_bit_depth = ctx->bit_depth;
977
1467
  size_t old_decoded_bytes = ctx->decoded_bytes;
978
1468
 
979
- ctx->input_data = data;
980
- ctx->input_size = size;
981
- int ok = ip_jpeg_decode_to_pixels(ctx, pixels, width, height, channels, 0);
982
-
983
- ctx->input_data = old_input_data;
984
- ctx->input_size = old_input_size;
1469
+ ctx->width = (int)cinfo.image_width;
1470
+ ctx->height = (int)cinfo.image_height;
1471
+ ctx->channels = 1;
1472
+ if (!ip_checked_image_size(ctx->width, ctx->height, 1, &ctx->decoded_bytes)) {
1473
+ ctx->width = old_width;
1474
+ ctx->height = old_height;
1475
+ ctx->channels = old_channels;
1476
+ ctx->decoded_bytes = old_decoded_bytes;
1477
+ jpeg_destroy_decompress(&cinfo);
1478
+ ctx->jmp_armed = 0;
1479
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
1480
+ return 0;
1481
+ }
1482
+ validate_limits_for_pixels(ctx);
985
1483
  ctx->width = old_width;
986
1484
  ctx->height = old_height;
987
1485
  ctx->channels = old_channels;
988
- ctx->bit_depth = old_bit_depth;
1486
+ size_t luma_size = ctx->decoded_bytes;
989
1487
  ctx->decoded_bytes = old_decoded_bytes;
990
- return ok;
1488
+
1489
+ if (ctx->status != IP_OK) {
1490
+ jpeg_destroy_decompress(&cinfo);
1491
+ ctx->jmp_armed = 0;
1492
+ return 0;
1493
+ }
1494
+
1495
+ cinfo.out_color_space = JCS_GRAYSCALE;
1496
+ #if defined(IMAGE_PACK_HAS_SIMD)
1497
+ cinfo.dct_method = JDCT_ISLOW;
1498
+ #else
1499
+ cinfo.dct_method = JDCT_FASTEST;
1500
+ #endif
1501
+ cinfo.do_fancy_upsampling = FALSE;
1502
+ cinfo.do_block_smoothing = FALSE;
1503
+ cinfo.quantize_colors = FALSE;
1504
+ cinfo.two_pass_quantize = FALSE;
1505
+ cinfo.dither_mode = JDITHER_NONE;
1506
+
1507
+ jpeg_start_decompress(&cinfo);
1508
+
1509
+ size_t luma_stride = (size_t)cinfo.output_width;
1510
+ if (cinfo.output_components != 1 || luma_stride == 0 ||
1511
+ luma_size != luma_stride * (size_t)cinfo.output_height) {
1512
+ jpeg_destroy_decompress(&cinfo);
1513
+ ctx->jmp_armed = 0;
1514
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1515
+ return 0;
1516
+ }
1517
+
1518
+ ctx->transient_decode_buf = (unsigned char *)ip_malloc_hot(luma_size);
1519
+ if (!ctx->transient_decode_buf && luma_size > 0) {
1520
+ jpeg_destroy_decompress(&cinfo);
1521
+ ctx->jmp_armed = 0;
1522
+ ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1523
+ return 0;
1524
+ }
1525
+
1526
+ while (cinfo.output_scanline < cinfo.output_height) {
1527
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1528
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
1529
+ jpeg_abort_decompress(&cinfo);
1530
+ jpeg_destroy_decompress(&cinfo);
1531
+ free(ctx->transient_decode_buf);
1532
+ ctx->transient_decode_buf = NULL;
1533
+ ctx->jmp_armed = 0;
1534
+ return 0;
1535
+ }
1536
+
1537
+ JSAMPROW rows[16];
1538
+ JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1539
+ if (batch > 16)
1540
+ batch = 16;
1541
+
1542
+ for (JDIMENSION i = 0; i < batch; i++) {
1543
+ rows[i] =
1544
+ ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * luma_stride);
1545
+ }
1546
+
1547
+ jpeg_read_scanlines(&cinfo, rows, batch);
1548
+ }
1549
+
1550
+ int out_width = (int)cinfo.output_width;
1551
+ int out_height = (int)cinfo.output_height;
1552
+
1553
+ jpeg_finish_decompress(&cinfo);
1554
+ jpeg_destroy_decompress(&cinfo);
1555
+ ctx->jmp_armed = 0;
1556
+
1557
+ *luma = ctx->transient_decode_buf;
1558
+ ctx->transient_decode_buf = NULL;
1559
+ *width = out_width;
1560
+ *height = out_height;
1561
+ return 1;
991
1562
  }
992
1563
 
993
1564
  static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned char *pixels,
@@ -998,7 +1569,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
998
1569
  return NULL;
999
1570
  }
1000
1571
 
1001
- unsigned char *luma = (unsigned char *)malloc(count);
1572
+ unsigned char *luma = (unsigned char *)ip_malloc_hot(count);
1002
1573
  if (!luma) {
1003
1574
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1004
1575
  return NULL;
@@ -1009,85 +1580,153 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
1009
1580
  return luma;
1010
1581
  }
1011
1582
 
1012
- for (size_t i = 0; i < count; i++) {
1013
- const unsigned char *p = pixels + (i * (size_t)channels);
1014
- unsigned int y =
1015
- 77u * (unsigned int)p[0] + 150u * (unsigned int)p[1] + 29u * (unsigned int)p[2] + 128u;
1016
- luma[i] = (unsigned char)(y >> 8);
1583
+ const unsigned char *IP_RESTRICT src = pixels;
1584
+ unsigned char *IP_RESTRICT dst = luma;
1585
+
1586
+ if (channels == 3) {
1587
+ for (size_t i = 0; i < count; i++) {
1588
+ unsigned int r = src[i * 3 + 0];
1589
+ unsigned int g = src[i * 3 + 1];
1590
+ unsigned int b = src[i * 3 + 2];
1591
+ dst[i] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1592
+ }
1593
+ return luma;
1017
1594
  }
1018
1595
 
1596
+ for (size_t i = 0; i < count; i++) {
1597
+ unsigned int r = src[i * 4 + 0];
1598
+ unsigned int g = src[i * 4 + 1];
1599
+ unsigned int b = src[i * 4 + 2];
1600
+ dst[i] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1601
+ }
1019
1602
  return luma;
1020
1603
  }
1021
1604
 
1022
- static double ip_compute_ssim_luma_buffer(const unsigned char *a, const unsigned char *b, int width,
1023
- int height) {
1024
- const int window = 8;
1605
+ static double ip_ssim_window_score_double(int32_t n, int32_t sum_a, int32_t sum_b, int32_t sum_a2,
1606
+ int32_t sum_b2, int32_t sum_ab) {
1025
1607
  const double c1 = 6.5025; /* (0.01 * 255)^2 */
1026
1608
  const double c2 = 58.5225; /* (0.03 * 255)^2 */
1027
- double total_ssim = 0.0;
1028
- int windows = 0;
1029
1609
 
1030
- for (int y0 = 0; y0 < height; y0 += window) {
1031
- int y1 = y0 + window;
1032
- if (y1 > height)
1033
- y1 = height;
1610
+ double inv_n = 1.0 / (double)n;
1611
+ double mean_a = (double)sum_a * inv_n;
1612
+ double mean_b = (double)sum_b * inv_n;
1613
+ double var_a = ((double)sum_a2 * inv_n) - (mean_a * mean_a);
1614
+ double var_b = ((double)sum_b2 * inv_n) - (mean_b * mean_b);
1615
+ double cov_ab = ((double)sum_ab * inv_n) - (mean_a * mean_b);
1616
+
1617
+ if (var_a < 0.0)
1618
+ var_a = 0.0;
1619
+ if (var_b < 0.0)
1620
+ var_b = 0.0;
1621
+
1622
+ double numerator = (2.0 * mean_a * mean_b + c1) * (2.0 * cov_ab + c2);
1623
+ double denominator = (mean_a * mean_a + mean_b * mean_b + c1) * (var_a + var_b + c2);
1624
+ double ssim = denominator == 0.0 ? 1.0 : numerator / denominator;
1625
+
1626
+ if (ssim < 0.0)
1627
+ ssim = 0.0;
1628
+ if (ssim > 1.0)
1629
+ ssim = 1.0;
1630
+ return ssim;
1631
+ }
1034
1632
 
1035
- for (int x0 = 0; x0 < width; x0 += window) {
1036
- int x1 = x0 + window;
1037
- if (x1 > width)
1038
- x1 = width;
1039
-
1040
- double sum_a = 0.0;
1041
- double sum_b = 0.0;
1042
- double sum_a2 = 0.0;
1043
- double sum_b2 = 0.0;
1044
- double sum_ab = 0.0;
1045
- int n = 0;
1046
-
1047
- for (int y = y0; y < y1; y++) {
1048
- size_t row = (size_t)y * (size_t)width;
1049
- for (int x = x0; x < x1; x++) {
1050
- size_t idx = row + (size_t)x;
1051
- double la = (double)a[idx];
1052
- double lb = (double)b[idx];
1053
- sum_a += la;
1054
- sum_b += lb;
1055
- sum_a2 += la * la;
1056
- sum_b2 += lb * lb;
1057
- sum_ab += la * lb;
1058
- n++;
1059
- }
1060
- }
1633
+ static inline double ip_ssim_window_8x8(const unsigned char *IP_RESTRICT a,
1634
+ const unsigned char *IP_RESTRICT b, int width, int x0,
1635
+ int y0) {
1636
+ int32_t sum_a = 0, sum_b = 0, sum_a2 = 0, sum_b2 = 0, sum_ab = 0;
1637
+
1638
+ for (int y = 0; y < 8; y++) {
1639
+ const unsigned char *pa = a + (size_t)(y0 + y) * (size_t)width + (size_t)x0;
1640
+ const unsigned char *pb = b + (size_t)(y0 + y) * (size_t)width + (size_t)x0;
1641
+ for (int x = 0; x < 8; x++) {
1642
+ int32_t la = pa[x];
1643
+ int32_t lb = pb[x];
1644
+ sum_a += la;
1645
+ sum_b += lb;
1646
+ sum_a2 += la * la;
1647
+ sum_b2 += lb * lb;
1648
+ sum_ab += la * lb;
1649
+ }
1650
+ }
1061
1651
 
1062
- if (n <= 0)
1063
- continue;
1652
+ return ip_ssim_window_score_double(64, sum_a, sum_b, sum_a2, sum_b2, sum_ab);
1653
+ }
1064
1654
 
1065
- double inv_n = 1.0 / (double)n;
1066
- double mean_a = sum_a * inv_n;
1067
- double mean_b = sum_b * inv_n;
1068
- double var_a = (sum_a2 * inv_n) - (mean_a * mean_a);
1069
- double var_b = (sum_b2 * inv_n) - (mean_b * mean_b);
1070
- double cov_ab = (sum_ab * inv_n) - (mean_a * mean_b);
1655
+ static inline double ip_ssim_window_var(const unsigned char *IP_RESTRICT a,
1656
+ const unsigned char *IP_RESTRICT b, int width, int x0,
1657
+ int y0, int x1, int y1) {
1658
+ int32_t sum_a = 0, sum_b = 0, sum_a2 = 0, sum_b2 = 0, sum_ab = 0;
1659
+ int32_t n = 0;
1660
+
1661
+ for (int y = y0; y < y1; y++) {
1662
+ const unsigned char *pa = a + (size_t)y * (size_t)width;
1663
+ const unsigned char *pb = b + (size_t)y * (size_t)width;
1664
+ for (int x = x0; x < x1; x++) {
1665
+ int32_t la = pa[x];
1666
+ int32_t lb = pb[x];
1667
+ sum_a += la;
1668
+ sum_b += lb;
1669
+ sum_a2 += la * la;
1670
+ sum_b2 += lb * lb;
1671
+ sum_ab += la * lb;
1672
+ n++;
1673
+ }
1674
+ }
1071
1675
 
1072
- if (var_a < 0.0)
1073
- var_a = 0.0;
1074
- if (var_b < 0.0)
1075
- var_b = 0.0;
1676
+ if (n <= 0)
1677
+ return 1.0;
1678
+ return ip_ssim_window_score_double(n, sum_a, sum_b, sum_a2, sum_b2, sum_ab);
1679
+ }
1680
+
1681
+ static double ip_compute_ssim_luma_buffer(const unsigned char *a, const unsigned char *b, int width,
1682
+ int height) {
1683
+ const int window = 8;
1684
+ double total_ssim = 0.0;
1685
+ int windows = 0;
1076
1686
 
1077
- double numerator = (2.0 * mean_a * mean_b + c1) * (2.0 * cov_ab + c2);
1078
- double denominator = (mean_a * mean_a + mean_b * mean_b + c1) * (var_a + var_b + c2);
1079
- double ssim = denominator == 0.0 ? 1.0 : numerator / denominator;
1687
+ int full_x = width / window;
1688
+ int full_y = height / window;
1689
+ int rem_x = width - full_x * window;
1690
+ int rem_y = height - full_y * window;
1080
1691
 
1081
- if (ssim < 0.0)
1082
- ssim = 0.0;
1083
- if (ssim > 1.0)
1084
- ssim = 1.0;
1692
+ for (int by = 0; by < full_y; by++) {
1693
+ int y0 = by * window;
1694
+ for (int bx = 0; bx < full_x; bx++) {
1695
+ int x0 = bx * window;
1696
+ total_ssim += ip_ssim_window_8x8(a, b, width, x0, y0);
1697
+ windows++;
1698
+ }
1699
+ }
1700
+
1701
+ if (rem_x > 0) {
1702
+ int x0 = full_x * window;
1703
+ int x1 = width;
1704
+ for (int by = 0; by < full_y; by++) {
1705
+ int y0 = by * window;
1706
+ int y1 = y0 + window;
1707
+ total_ssim += ip_ssim_window_var(a, b, width, x0, y0, x1, y1);
1708
+ windows++;
1709
+ }
1710
+ }
1085
1711
 
1086
- total_ssim += ssim;
1712
+ if (rem_y > 0) {
1713
+ int y0 = full_y * window;
1714
+ int y1 = height;
1715
+ for (int bx = 0; bx < full_x; bx++) {
1716
+ int x0 = bx * window;
1717
+ int x1 = x0 + window;
1718
+ total_ssim += ip_ssim_window_var(a, b, width, x0, y0, x1, y1);
1087
1719
  windows++;
1088
1720
  }
1089
1721
  }
1090
1722
 
1723
+ if (rem_x > 0 && rem_y > 0) {
1724
+ int x0 = full_x * window;
1725
+ int y0 = full_y * window;
1726
+ total_ssim += ip_ssim_window_var(a, b, width, x0, y0, width, height);
1727
+ windows++;
1728
+ }
1729
+
1091
1730
  return windows > 0 ? total_ssim / (double)windows : 0.0;
1092
1731
  }
1093
1732
 
@@ -1098,13 +1737,13 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1098
1737
  int reference_channels = 0;
1099
1738
 
1100
1739
  if (ctx->pixel_data) {
1101
- reference_pixels = ctx->owned_pixel_data;
1740
+ reference_pixels = (unsigned char *)ctx->pixel_data;
1102
1741
  reference_width = ctx->width;
1103
1742
  reference_height = ctx->height;
1104
1743
  reference_channels = ctx->channels;
1105
1744
  } else {
1106
1745
  if (!ip_jpeg_decode_to_pixels(ctx, &reference_pixels, &reference_width, &reference_height,
1107
- &reference_channels, 0)) {
1746
+ &reference_channels, 1)) {
1108
1747
  return 0;
1109
1748
  }
1110
1749
 
@@ -1120,7 +1759,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1120
1759
 
1121
1760
  if (reference_channels == 4) {
1122
1761
  ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1123
- "min_ssim is not supported for RGBA input in v0.2.0");
1762
+ "min_ssim is not supported for RGBA input in v0.2.2");
1124
1763
  return 0;
1125
1764
  }
1126
1765
 
@@ -1163,13 +1802,12 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1163
1802
  ctx->output_capacity = 0;
1164
1803
  ctx->output_owner = IP_OUTPUT_OWNER_NONE;
1165
1804
 
1166
- unsigned char *candidate_pixels = NULL;
1805
+ unsigned char *candidate_luma = NULL;
1167
1806
  int candidate_width = 0;
1168
1807
  int candidate_height = 0;
1169
- int candidate_channels = 0;
1170
- int decoded_ok = ip_decode_jpeg_buffer_preserving_context(
1171
- ctx, candidate_jpeg, candidate_jpeg_size, &candidate_pixels, &candidate_width,
1172
- &candidate_height, &candidate_channels);
1808
+ int decoded_ok =
1809
+ ip_decode_jpeg_to_luma_buffer(ctx, candidate_jpeg, candidate_jpeg_size, &candidate_luma,
1810
+ &candidate_width, &candidate_height);
1173
1811
 
1174
1812
  if (!decoded_ok) {
1175
1813
  free(reference_luma);
@@ -1178,10 +1816,9 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1178
1816
  return 0;
1179
1817
  }
1180
1818
 
1181
- if (candidate_width != reference_width || candidate_height != reference_height ||
1182
- candidate_channels != reference_channels) {
1819
+ if (candidate_width != reference_width || candidate_height != reference_height) {
1183
1820
  free(reference_luma);
1184
- free(candidate_pixels);
1821
+ free(candidate_luma);
1185
1822
  free(candidate_jpeg);
1186
1823
  free(best_jpeg);
1187
1824
  ip_context_set_error(ctx, IP_ERR_ENCODE,
@@ -1189,17 +1826,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1189
1826
  return 0;
1190
1827
  }
1191
1828
 
1192
- unsigned char *candidate_luma = ip_build_luma_buffer(ctx, candidate_pixels, candidate_width,
1193
- candidate_height, candidate_channels);
1194
- free(candidate_pixels);
1195
-
1196
- if (!candidate_luma) {
1197
- free(reference_luma);
1198
- free(candidate_jpeg);
1199
- free(best_jpeg);
1200
- return 0;
1201
- }
1202
-
1203
1829
  double ssim = ip_compute_ssim_luma_buffer(reference_luma, candidate_luma, reference_width,
1204
1830
  reference_height);
1205
1831
  free(candidate_luma);
@@ -1244,119 +1870,176 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1244
1870
  return 1;
1245
1871
  }
1246
1872
 
1247
- #if defined(IMAGE_PACK_ENABLE_LOSSLESS_TRANSCODE_FAST_PATH)
1248
- static int ip_jpeg_transcode_coefficients(ip_context_t *ctx, int mozjpeg_size_mode) {
1249
- if (mozjpeg_size_mode)
1873
+ static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, int strip_metadata) {
1874
+ jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xFFFF);
1875
+ if (!strip_metadata) {
1876
+ jpeg_save_markers(cinfo, JPEG_COM, 0xFFFF);
1877
+ for (int app = 2; app < 16; app++) {
1878
+ jpeg_save_markers(cinfo, JPEG_APP0 + app, 0xFFFF);
1879
+ }
1880
+ }
1881
+ }
1882
+
1883
+ static int ip_validate_lossless_optimize_header(ip_context_t *ctx,
1884
+ struct jpeg_decompress_struct *srcinfo) {
1885
+ if (srcinfo->image_width > (JDIMENSION)INT_MAX || srcinfo->image_height > (JDIMENSION)INT_MAX) {
1886
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
1250
1887
  return 0;
1251
- if (!ctx->input_data || ctx->input_size < 4)
1888
+ }
1889
+
1890
+ ctx->width = (int)srcinfo->image_width;
1891
+ ctx->height = (int)srcinfo->image_height;
1892
+ ctx->channels = srcinfo->num_components;
1893
+ ctx->bit_depth = 8;
1894
+
1895
+ if (ctx->max_width < 0 || ctx->max_height < 0) {
1896
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
1252
1897
  return 0;
1253
- if (ctx->input_data[0] != 0xFF || ctx->input_data[1] != 0xD8)
1898
+ }
1899
+ if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
1900
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
1901
+ return 0;
1902
+ }
1903
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
1904
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
1254
1905
  return 0;
1906
+ }
1907
+ if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
1908
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1909
+ return 0;
1910
+ }
1255
1911
 
1912
+ return 1;
1913
+ }
1914
+
1915
+ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1256
1916
  struct jpeg_decompress_struct srcinfo;
1257
1917
  struct jpeg_compress_struct dstinfo;
1258
- ip_jpeg_error_mgr jsrcerr;
1259
- ip_jpeg_error_mgr jdsterr;
1918
+ ip_jpeg_error_mgr srcerr;
1919
+ ip_jpeg_error_mgr dsterr;
1920
+ jvirt_barray_ptr *coef_arrays = NULL;
1921
+ unsigned long jpeg_size = 0;
1922
+
1260
1923
  memset(&srcinfo, 0, sizeof(srcinfo));
1261
1924
  memset(&dstinfo, 0, sizeof(dstinfo));
1262
- memset(&jsrcerr, 0, sizeof(jsrcerr));
1263
- memset(&jdsterr, 0, sizeof(jdsterr));
1925
+ memset(&srcerr, 0, sizeof(srcerr));
1926
+ memset(&dsterr, 0, sizeof(dsterr));
1927
+ ctx->transient_jpeg_buf = NULL;
1264
1928
 
1265
- srcinfo.err = jpeg_std_error(&jsrcerr.pub);
1266
- jsrcerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1267
- jsrcerr.ctx = ctx;
1929
+ srcinfo.err = jpeg_std_error(&srcerr.pub);
1930
+ srcerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1931
+ srcerr.ctx = ctx;
1268
1932
 
1269
- dstinfo.err = jpeg_std_error(&jdsterr.pub);
1270
- jdsterr.pub.error_exit = ip_jpeg_encode_error_exit;
1271
- jdsterr.ctx = ctx;
1272
-
1273
- unsigned char *jpeg_buf = NULL;
1274
- unsigned long jpeg_size = 0;
1275
- int src_created = 0;
1276
- int dst_created = 0;
1933
+ dstinfo.err = jpeg_std_error(&dsterr.pub);
1934
+ dsterr.pub.error_exit = ip_jpeg_encode_error_exit;
1935
+ dsterr.ctx = ctx;
1277
1936
 
1278
1937
  ctx->jmp_armed = 1;
1279
1938
  if (setjmp(ctx->jmpbuf)) {
1280
1939
  ctx->jmp_armed = 0;
1281
- if (dst_created)
1282
- jpeg_destroy_compress(&dstinfo);
1283
- if (src_created)
1284
- jpeg_destroy_decompress(&srcinfo);
1285
- free(jpeg_buf);
1286
-
1940
+ jpeg_destroy_compress(&dstinfo);
1941
+ jpeg_destroy_decompress(&srcinfo);
1942
+ free(ctx->transient_jpeg_buf);
1943
+ ctx->transient_jpeg_buf = NULL;
1287
1944
  if (ctx->status == IP_OK)
1288
- return 0;
1945
+ ip_context_set_error(ctx, IP_ERR_ENCODE, "lossless JPEG optimize failed");
1289
1946
  return 0;
1290
1947
  }
1291
1948
 
1292
1949
  jpeg_create_decompress(&srcinfo);
1293
- src_created = 1;
1950
+ jpeg_create_compress(&dstinfo);
1294
1951
  jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
1952
+ ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
1295
1953
 
1296
- if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK) {
1954
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1955
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1956
+ jpeg_destroy_compress(&dstinfo);
1297
1957
  jpeg_destroy_decompress(&srcinfo);
1298
1958
  ctx->jmp_armed = 0;
1299
1959
  return 0;
1300
1960
  }
1301
1961
 
1302
- if ((int)srcinfo.image_width > ctx->max_width || (int)srcinfo.image_height > ctx->max_height ||
1303
- (uint64_t)srcinfo.image_width * (uint64_t)srcinfo.image_height > ctx->max_pixels) {
1962
+ int rc = jpeg_read_header(&srcinfo, TRUE);
1963
+ if (rc != JPEG_HEADER_OK) {
1964
+ jpeg_destroy_compress(&dstinfo);
1304
1965
  jpeg_destroy_decompress(&srcinfo);
1305
1966
  ctx->jmp_armed = 0;
1306
- ip_context_set_error(ctx, IP_ERR_LIMIT, "image exceeds configured limits");
1967
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1307
1968
  return 0;
1308
1969
  }
1309
1970
 
1310
- jvirt_barray_ptr *src_coef_arrays = jpeg_read_coefficients(&srcinfo);
1311
- if (!src_coef_arrays) {
1971
+ ctx->source_orientation = ip_read_exif_orientation_from_decompress(&srcinfo);
1972
+ if (ctx->strip_metadata && ctx->source_orientation > 1) {
1973
+ jpeg_destroy_compress(&dstinfo);
1312
1974
  jpeg_destroy_decompress(&srcinfo);
1313
1975
  ctx->jmp_armed = 0;
1976
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1977
+ "lossless optimize cannot strip EXIF Orientation without changing "
1978
+ "visual orientation; use strip_metadata: false or ImagePack.compress");
1314
1979
  return 0;
1315
1980
  }
1316
1981
 
1317
- jpeg_create_compress(&dstinfo);
1318
- dst_created = 1;
1319
- jpeg_mem_dest(&dstinfo, &jpeg_buf, &jpeg_size);
1320
- jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
1982
+ if (!ip_validate_lossless_optimize_header(ctx, &srcinfo)) {
1983
+ jpeg_destroy_compress(&dstinfo);
1984
+ jpeg_destroy_decompress(&srcinfo);
1985
+ ctx->jmp_armed = 0;
1986
+ return 0;
1987
+ }
1988
+
1989
+ if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo)) {
1990
+ jpeg_destroy_compress(&dstinfo);
1991
+ jpeg_destroy_decompress(&srcinfo);
1992
+ ctx->jmp_armed = 0;
1993
+ return 0;
1994
+ }
1321
1995
 
1322
- dstinfo.optimize_coding = FALSE;
1996
+ coef_arrays = jpeg_read_coefficients(&srcinfo);
1997
+ jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
1998
+ dstinfo.optimize_coding = TRUE;
1999
+ dstinfo.num_scans = 0;
2000
+ dstinfo.scan_info = NULL;
1323
2001
  if (ctx->progressive) {
1324
2002
  jpeg_simple_progression(&dstinfo);
1325
2003
  dstinfo.optimize_coding = TRUE;
1326
2004
  }
1327
2005
 
1328
- jpeg_write_coefficients(&dstinfo, src_coef_arrays);
1329
- jpeg_finish_compress(&dstinfo);
1330
- jpeg_destroy_compress(&dstinfo);
1331
- dst_created = 0;
2006
+ jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
2007
+
2008
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
2009
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
2010
+ jpeg_destroy_compress(&dstinfo);
2011
+ jpeg_destroy_decompress(&srcinfo);
2012
+ free(ctx->transient_jpeg_buf);
2013
+ ctx->transient_jpeg_buf = NULL;
2014
+ ctx->jmp_armed = 0;
2015
+ return 0;
2016
+ }
2017
+
2018
+ jpeg_write_coefficients(&dstinfo, coef_arrays);
2019
+ if (!ctx->strip_metadata)
2020
+ ip_write_preserved_markers(ctx, &dstinfo);
1332
2021
 
2022
+ jpeg_finish_compress(&dstinfo);
1333
2023
  jpeg_finish_decompress(&srcinfo);
2024
+ jpeg_destroy_compress(&dstinfo);
1334
2025
  jpeg_destroy_decompress(&srcinfo);
1335
- src_created = 0;
1336
-
1337
2026
  ctx->jmp_armed = 0;
1338
2027
 
1339
- if ((size_t)jpeg_size > ctx->max_output_size) {
1340
- free(jpeg_buf);
1341
- ip_context_set_error(ctx, IP_ERR_LIMIT, "transcoded output exceeds max_output_size");
2028
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size) {
2029
+ free(ctx->transient_jpeg_buf);
2030
+ ctx->transient_jpeg_buf = NULL;
2031
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
1342
2032
  return 0;
1343
2033
  }
1344
2034
 
1345
- ctx->output_data = jpeg_buf;
2035
+ ctx->output_data = ctx->transient_jpeg_buf;
2036
+ ctx->transient_jpeg_buf = NULL;
1346
2037
  ctx->output_size = (size_t)jpeg_size;
1347
2038
  ctx->output_capacity = (size_t)jpeg_size;
1348
2039
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
1349
-
1350
- if (ctx->width == 0 || ctx->height == 0) {
1351
- ctx->width = 0;
1352
- ctx->height = 0;
1353
- ctx->channels = 0;
1354
- }
1355
2040
  return 1;
1356
2041
  }
1357
2042
 
1358
- #endif
1359
-
1360
2043
  static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
1361
2044
  if (ctx->ssim_guard_enabled)
1362
2045
  return guarded_compress_jpeg_input_with_mode(ctx, 0);
@@ -1436,6 +2119,29 @@ static int ip_run_context(ip_context_t *ctx) {
1436
2119
  return ctx->status == IP_OK;
1437
2120
  }
1438
2121
 
2122
+ static void *ip_run_optimize_nogvl(void *data) {
2123
+ ip_context_t *ctx = (ip_context_t *)data;
2124
+ if (ctx->status == IP_OK)
2125
+ ip_lossless_optimize_jpeg(ctx);
2126
+ return NULL;
2127
+ }
2128
+
2129
+ static int ip_run_optimize_context(ip_context_t *ctx) {
2130
+ ip_resolve_execution(ctx);
2131
+
2132
+ if (ctx->resolved_execution == IP_EXEC_DIRECT) {
2133
+ ip_run_optimize_nogvl(ctx);
2134
+ } else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
2135
+ rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, 0);
2136
+ } else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
2137
+ rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
2138
+ } else {
2139
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
2140
+ }
2141
+
2142
+ return ctx->status == IP_OK;
2143
+ }
2144
+
1439
2145
  static size_t config_size_value(VALUE config, ID id, size_t fallback) {
1440
2146
  VALUE value = rb_funcall(config, id, 0);
1441
2147
  if (NIL_P(value))
@@ -1460,6 +2166,7 @@ static void apply_configuration(VALUE self, ip_context_t *ctx) {
1460
2166
  ctx->max_width = config_int_value(config, id_max_width, ctx->max_width);
1461
2167
  ctx->max_height = config_int_value(config, id_max_height, ctx->max_height);
1462
2168
  ctx->max_output_size = config_size_value(config, id_max_output_size, ctx->max_output_size);
2169
+ ctx->max_input_size = config_size_value(config, id_max_input_size, ctx->max_input_size);
1463
2170
  }
1464
2171
 
1465
2172
  static void validate_limits_for_pixels(ip_context_t *ctx) {
@@ -1469,15 +2176,19 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
1469
2176
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "image dimensions must be positive");
1470
2177
  return;
1471
2178
  }
1472
- if (ctx->width > ctx->max_width) {
2179
+ if (ctx->max_width < 0 || ctx->max_height < 0) {
2180
+ ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
2181
+ return;
2182
+ }
2183
+ if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
1473
2184
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
1474
2185
  return;
1475
2186
  }
1476
- if (ctx->height > ctx->max_height) {
2187
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
1477
2188
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
1478
2189
  return;
1479
2190
  }
1480
- if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
2191
+ if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
1481
2192
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1482
2193
  return;
1483
2194
  }
@@ -1489,95 +2200,137 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
1489
2200
  ctx->decoded_bytes = decoded_bytes;
1490
2201
  }
1491
2202
 
1492
- static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
1493
- VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
1494
- VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
1495
- VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2203
+ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
2204
+ ip_compress_jpeg_call_t *call = (ip_compress_jpeg_call_t *)ptr;
1496
2205
  ip_context_t *ctx = ip_context_new();
1497
2206
  if (!ctx)
1498
2207
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2208
+ call->ctx = ctx;
1499
2209
 
1500
- ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1501
- ctx->algo = ip_parse_algo(algo);
1502
- ctx->quality = NUM2INT(quality);
2210
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2211
+ ctx->algo = ip_parse_algo(call->algo);
2212
+ ctx->quality = NUM2INT(call->quality);
1503
2213
  ctx->selected_quality = ctx->quality;
1504
2214
  ip_validate_quality_or_raise(ctx);
1505
- ctx->min_ssim = NUM2DBL(min_ssim);
2215
+ ctx->min_ssim = NUM2DBL(call->min_ssim);
1506
2216
  ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
1507
2217
  ip_validate_min_ssim_or_raise(ctx);
1508
- ctx->mozjpeg_trellis_enabled = ip_bool_value(mozjpeg_trellis);
1509
- ctx->progressive = ip_bool_value(progressive);
1510
- ctx->strip_metadata = ip_bool_value(strip_metadata);
1511
- ctx->requested_execution = ip_parse_execution(execution);
1512
- ctx->cancellable_requested = ip_bool_value(cancellable);
1513
- ctx->has_scheduler = ip_bool_value(has_scheduler);
1514
- apply_configuration(self, ctx);
1515
-
1516
- if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
1517
- !ip_prepare_output_path(ctx, output, out_kind)) {
1518
- VALUE exception = ip_status_to_exception(ctx->status);
1519
- char message[512];
1520
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1521
- ip_context_free(ctx);
1522
- rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
2218
+ ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
2219
+ ctx->progressive = ip_bool_value(call->progressive);
2220
+ ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2221
+ ctx->requested_execution = ip_parse_execution(call->execution);
2222
+ ctx->cancellable_requested = ip_bool_value(call->cancellable);
2223
+ ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2224
+ apply_configuration(call->self, ctx);
2225
+
2226
+ if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
2227
+ !ip_prepare_output_path(ctx, call->output, out_kind)) {
2228
+ ip_raise_for_status(ctx);
2229
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
1523
2230
  }
1524
2231
 
1525
2232
  if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
1526
2233
  !ip_inspect_jpeg_header(ctx)) {
1527
- VALUE exception = ip_status_to_exception(ctx->status);
1528
- char message[512];
1529
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1530
- ip_context_free(ctx);
1531
- rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
2234
+ ip_raise_for_status(ctx);
2235
+ rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
1532
2236
  }
1533
2237
 
1534
2238
  ip_run_context(ctx);
1535
- VALUE result = ip_finish_output(ctx, out_kind);
1536
- ip_context_free(ctx);
1537
- return result;
2239
+ return ip_finish_output(ctx, out_kind);
1538
2240
  }
1539
2241
 
1540
- static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
1541
- VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
1542
- VALUE quality, VALUE progressive, VALUE execution,
1543
- VALUE cancellable, VALUE has_scheduler) {
2242
+ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
2243
+ VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
2244
+ VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
2245
+ VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2246
+ ip_compress_jpeg_call_t call = {
2247
+ self, input, input_kind, output, output_kind,
2248
+ algo, quality, min_ssim, mozjpeg_trellis, progressive,
2249
+ strip_metadata, execution, cancellable, has_scheduler, NULL};
2250
+ return rb_ensure(ip_compress_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
2251
+ }
2252
+
2253
+ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
2254
+ ip_compress_pixels_call_t *call = (ip_compress_pixels_call_t *)ptr;
1544
2255
  ip_context_t *ctx = ip_context_new();
1545
2256
  if (!ctx)
1546
2257
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2258
+ call->ctx = ctx;
1547
2259
 
1548
- ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1549
- ctx->algo = ip_parse_algo(algo);
1550
- ctx->quality = NUM2INT(quality);
2260
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2261
+ ctx->algo = ip_parse_algo(call->algo);
2262
+ ctx->quality = NUM2INT(call->quality);
2263
+ ctx->selected_quality = ctx->quality;
1551
2264
  ip_validate_quality_or_raise(ctx);
1552
- ctx->progressive = ip_bool_value(progressive);
2265
+ ctx->min_ssim = NUM2DBL(call->min_ssim);
2266
+ ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
2267
+ ip_validate_min_ssim_or_raise(ctx);
2268
+ ctx->progressive = ip_bool_value(call->progressive);
1553
2269
  ctx->strip_metadata = 1;
1554
- ctx->requested_execution = ip_parse_execution(execution);
1555
- ctx->cancellable_requested = ip_bool_value(cancellable);
1556
- ctx->has_scheduler = ip_bool_value(has_scheduler);
1557
- apply_configuration(self, ctx);
1558
-
1559
- if (!ip_prepare_pixels(ctx, buffer, NUM2INT(width), NUM2INT(height), NUM2INT(channels)) ||
1560
- !ip_prepare_output_path(ctx, output, out_kind)) {
1561
- VALUE exception = ip_status_to_exception(ctx->status);
1562
- char message[512];
1563
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1564
- ip_context_free(ctx);
1565
- rb_raise(exception, "%s", message[0] ? message : "invalid pixel input");
2270
+ ctx->requested_execution = ip_parse_execution(call->execution);
2271
+ ctx->cancellable_requested = ip_bool_value(call->cancellable);
2272
+ ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2273
+ apply_configuration(call->self, ctx);
2274
+
2275
+ if (!ip_prepare_pixels(ctx, call->buffer, NUM2INT(call->width), NUM2INT(call->height),
2276
+ NUM2INT(call->channels)) ||
2277
+ !ip_prepare_output_path(ctx, call->output, out_kind)) {
2278
+ ip_raise_for_status(ctx);
2279
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
1566
2280
  }
1567
2281
 
1568
2282
  validate_limits_for_pixels(ctx);
1569
- if (ctx->status != IP_OK) {
1570
- VALUE exception = ip_status_to_exception(ctx->status);
1571
- char message[512];
1572
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1573
- ip_context_free(ctx);
1574
- rb_raise(exception, "%s", message);
1575
- }
2283
+ if (ctx->status != IP_OK)
2284
+ ip_raise_for_status(ctx);
1576
2285
 
1577
2286
  ip_run_context(ctx);
1578
- VALUE result = ip_finish_output(ctx, out_kind);
1579
- ip_context_free(ctx);
1580
- return result;
2287
+ return ip_finish_output(ctx, out_kind);
2288
+ }
2289
+
2290
+ static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
2291
+ VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
2292
+ VALUE quality, VALUE min_ssim, VALUE progressive,
2293
+ VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2294
+ ip_compress_pixels_call_t call = {
2295
+ self, buffer, width, height, channels, output, output_kind, algo,
2296
+ quality, min_ssim, progressive, execution, cancellable, has_scheduler, NULL};
2297
+ return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
2298
+ (VALUE)&call.ctx);
2299
+ }
2300
+
2301
+ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
2302
+ ip_optimize_jpeg_call_t *call = (ip_optimize_jpeg_call_t *)ptr;
2303
+ ip_context_t *ctx = ip_context_new();
2304
+ if (!ctx)
2305
+ rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2306
+ call->ctx = ctx;
2307
+
2308
+ ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
2309
+ ctx->progressive = ip_bool_value(call->progressive);
2310
+ ctx->strip_metadata = ip_bool_value(call->strip_metadata);
2311
+ ctx->requested_execution = ip_parse_execution(call->execution);
2312
+ ctx->cancellable_requested = ip_bool_value(call->cancellable);
2313
+ ctx->has_scheduler = ip_bool_value(call->has_scheduler);
2314
+ ctx->ssim_guard_enabled = 0;
2315
+ apply_configuration(call->self, ctx);
2316
+
2317
+ if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
2318
+ !ip_prepare_output_path(ctx, call->output, out_kind)) {
2319
+ ip_raise_for_status(ctx);
2320
+ rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
2321
+ }
2322
+
2323
+ ip_run_optimize_context(ctx);
2324
+ return ip_finish_output(ctx, out_kind);
2325
+ }
2326
+
2327
+ static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
2328
+ VALUE output_kind, VALUE progressive, VALUE strip_metadata,
2329
+ VALUE execution, VALUE cancellable, VALUE has_scheduler) {
2330
+ ip_optimize_jpeg_call_t call = {
2331
+ self, input, input_kind, output, output_kind, progressive,
2332
+ strip_metadata, execution, cancellable, has_scheduler, NULL};
2333
+ return rb_ensure(ip_optimize_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
1581
2334
  }
1582
2335
 
1583
2336
  IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
@@ -1598,6 +2351,7 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
1598
2351
  id_max_width = rb_intern("max_width");
1599
2352
  id_max_height = rb_intern("max_height");
1600
2353
  id_max_output_size = rb_intern("max_output_size");
2354
+ id_max_input_size = rb_intern("max_input_size");
1601
2355
 
1602
2356
  rb_mImagePack = rb_define_module("ImagePack");
1603
2357
  rb_eImagePackError = rb_const_get(rb_mImagePack, rb_intern("Error"));
@@ -1612,7 +2366,23 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
1612
2366
  rb_eImagePackOutOfMemoryError = rb_const_get(rb_mImagePack, rb_intern("OutOfMemoryError"));
1613
2367
  rb_eImagePackCancelledError = rb_const_get(rb_mImagePack, rb_intern("CancelledError"));
1614
2368
 
2369
+ rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION", rb_str_new_cstr(VERSION));
2370
+ #if defined(IMAGE_PACK_HAS_SIMD)
2371
+ rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qtrue);
2372
+ #else
2373
+ rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qfalse);
2374
+ #endif
2375
+
1615
2376
  rb_define_singleton_method(rb_mImagePack, "__compress_jpeg", ip_compress_jpeg_entry, 13);
1616
- rb_define_singleton_method(rb_mImagePack, "__compress_pixels", ip_compress_pixels_entry, 12);
2377
+ rb_define_singleton_method(rb_mImagePack, "__compress_pixels", ip_compress_pixels_entry, 13);
2378
+ rb_define_singleton_method(rb_mImagePack, "__optimize_jpeg", ip_optimize_jpeg_entry, 9);
1617
2379
  rb_define_singleton_method(rb_mImagePack, "__inspect_image", ip_inspect_image_entry, 2);
2380
+ rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2381
+ ID2SYM(rb_intern("__compress_jpeg")));
2382
+ rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2383
+ ID2SYM(rb_intern("__compress_pixels")));
2384
+ rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2385
+ ID2SYM(rb_intern("__optimize_jpeg")));
2386
+ rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
2387
+ ID2SYM(rb_intern("__inspect_image")));
1618
2388
  }