image_pack 0.2.1 → 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,10 +16,7 @@
16
16
  #include <stdlib.h>
17
17
  #include <string.h>
18
18
  #include <jpeglib.h>
19
-
20
- #if defined(__linux__)
21
- #include <sys/mman.h>
22
- #endif
19
+ #include <jconfigint.h>
23
20
 
24
21
  #ifndef IMAGE_PACK_INIT_EXPORT
25
22
  #if defined(_WIN32)
@@ -138,6 +135,11 @@ typedef struct {
138
135
  } *preserved_markers;
139
136
  size_t preserved_marker_count;
140
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;
141
143
  } ip_context_t;
142
144
 
143
145
  typedef struct {
@@ -214,6 +216,40 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
214
216
  static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode);
215
217
  static int ip_jpeg_turbo_compress(ip_context_t *ctx);
216
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);
221
+
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
+ }
217
253
 
218
254
  static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
219
255
  VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
@@ -221,8 +257,11 @@ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, V
221
257
  VALUE execution, VALUE cancellable, VALUE has_scheduler);
222
258
  static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
223
259
  VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
224
- VALUE quality, VALUE progressive, VALUE execution,
225
- 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);
226
265
 
227
266
  static VALUE ip_status_to_exception(ip_status_t status) {
228
267
  switch (status) {
@@ -272,15 +311,7 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
272
311
  }
273
312
 
274
313
  static void *ip_malloc_hot(size_t size) {
275
- void *p = malloc(size);
276
- if (!p)
277
- return NULL;
278
- #if defined(__linux__) && defined(MADV_HUGEPAGE)
279
- if (size >= (256u * 1024u)) {
280
- (void)madvise(p, size, MADV_HUGEPAGE);
281
- }
282
- #endif
283
- return p;
314
+ return malloc(size);
284
315
  }
285
316
 
286
317
  static void ip_validate_quality_or_raise(ip_context_t *ctx) {
@@ -288,7 +319,6 @@ static void ip_validate_quality_or_raise(ip_context_t *ctx) {
288
319
  return;
289
320
 
290
321
  int quality = ctx->quality;
291
- ip_context_free(ctx);
292
322
  rb_raise(rb_eImagePackInvalidArgumentError, "quality must be Integer 1..100, got: %d", quality);
293
323
  }
294
324
 
@@ -299,7 +329,6 @@ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
299
329
  return;
300
330
 
301
331
  double min_ssim = ctx->min_ssim;
302
- ip_context_free(ctx);
303
332
  rb_raise(rb_eImagePackInvalidArgumentError,
304
333
  "min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
305
334
  }
@@ -330,6 +359,7 @@ static ip_context_t *ip_context_new(void) {
330
359
  ctx->max_height = 30000;
331
360
  ctx->max_output_size = 256 * 1024 * 1024;
332
361
  ctx->max_input_size = 256 * 1024 * 1024;
362
+ ctx->source_orientation = 1;
333
363
  atomic_init(&ctx->cancelled, 0);
334
364
  return ctx;
335
365
  }
@@ -342,6 +372,9 @@ static void ip_context_free(ip_context_t *ctx) {
342
372
  free(ctx->owned_pixel_data);
343
373
  free(ctx->output_path);
344
374
  free(ctx->scratch_row);
375
+ free(ctx->transient_jpeg_buf);
376
+ free(ctx->transient_decode_buf);
377
+ free(ctx->transient_scratch_buf);
345
378
 
346
379
  if (ctx->preserved_markers) {
347
380
  for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
@@ -485,6 +518,12 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
485
518
  ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
486
519
  return 0;
487
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
+
488
527
  unsigned char *copy = (unsigned char *)malloc(len);
489
528
  if (!copy && len > 0) {
490
529
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
@@ -546,6 +585,17 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
546
585
  return 0;
547
586
  }
548
587
 
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
+
549
599
  unsigned char *copy = (unsigned char *)ip_malloc_hot(expected);
550
600
  if (!copy && expected > 0) {
551
601
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
@@ -584,23 +634,45 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
584
634
  ip_raise_for_status(ctx);
585
635
 
586
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");
587
639
  VALUE out = rb_str_new((const char *)ctx->output_data, (long)ctx->output_size);
588
640
  rb_enc_associate(out, rb_ascii8bit_encoding());
589
641
  return out;
590
642
  }
591
643
 
592
- 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");
593
654
  if (!fp) {
655
+ free(tmp_path);
594
656
  rb_raise(rb_eImagePackInvalidArgumentError, "failed to open output path: %s",
595
657
  ctx->output_path);
596
658
  }
597
659
 
598
660
  size_t written = fwrite(ctx->output_data, 1, ctx->output_size, fp);
599
- fclose(fp);
600
- 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);
601
666
  rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
602
667
  }
603
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);
604
676
  return Qtrue;
605
677
  }
606
678
 
@@ -629,17 +701,171 @@ static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *da
629
701
  return 1;
630
702
  }
631
703
 
632
- static void ip_save_markers_from_decompress(ip_context_t *ctx,
633
- struct jpeg_decompress_struct *cinfo) {
704
+ static int ip_save_markers_from_decompress(ip_context_t *ctx,
705
+ struct jpeg_decompress_struct *cinfo) {
634
706
  jpeg_saved_marker_ptr m;
635
707
  for (m = cinfo->marker_list; m != NULL; m = m->next) {
636
708
  if (m->marker == (JPEG_APP0 + 0))
637
709
  continue;
638
710
 
639
711
  if (!ip_save_marker(ctx, m->marker, m->data, m->data_length)) {
640
- return;
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);
641
861
  }
642
862
  }
863
+
864
+ free(src);
865
+ *pixels = dst;
866
+ *width = dst_w;
867
+ *height = dst_h;
868
+ return 1;
643
869
  }
644
870
 
645
871
  static void ip_write_preserved_markers(ip_context_t *ctx, struct jpeg_compress_struct *cinfo) {
@@ -714,7 +940,10 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
714
940
  ctx->height = (int)cinfo.image_height;
715
941
  ctx->channels = cinfo.num_components;
716
942
  if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4) {
717
- 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;
718
947
  }
719
948
  ctx->bit_depth = 8;
720
949
  ctx->decoded_bytes = (size_t)ctx->width * (size_t)ctx->height * (size_t)ctx->channels;
@@ -727,15 +956,15 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
727
956
  return 0;
728
957
  }
729
958
 
730
- if (ctx->width > ctx->max_width) {
959
+ if (ctx->max_width > 0 && ctx->width > ctx->max_width) {
731
960
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
732
961
  return 0;
733
962
  }
734
- if (ctx->height > ctx->max_height) {
963
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
735
964
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
736
965
  return 0;
737
966
  }
738
- 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) {
739
968
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
740
969
  return 0;
741
970
  }
@@ -743,32 +972,43 @@ static int ip_inspect_jpeg_header(ip_context_t *ctx) {
743
972
  return 1;
744
973
  }
745
974
 
746
- static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
747
- (void)self;
975
+ static VALUE ip_inspect_image_entry_body(VALUE ptr) {
976
+ ip_inspect_call_t *call = (ip_inspect_call_t *)ptr;
748
977
  ip_context_t *ctx = ip_context_new();
749
978
  if (!ctx)
750
979
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
980
+ call->ctx = ctx;
751
981
 
752
- 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)) ||
753
983
  !ip_inspect_jpeg_header(ctx)) {
754
- VALUE exception = ip_status_to_exception(ctx->status);
755
- char message[512];
756
- snprintf(message, sizeof(message), "%s", ctx->error_message);
757
- ip_context_free(ctx);
758
- rb_raise(exception, "%s", message[0] ? message : "failed to inspect JPEG image");
984
+ ip_raise_for_status(ctx);
985
+ rb_raise(rb_eImagePackInvalidImageError, "failed to inspect JPEG image");
759
986
  }
760
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
+
761
997
  VALUE hash = rb_hash_new();
762
998
  rb_hash_aset(hash, ID2SYM(rb_intern("format")), ID2SYM(rb_intern("jpeg")));
763
- rb_hash_aset(hash, ID2SYM(rb_intern("width")), INT2NUM(ctx->width));
764
- rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(ctx->height));
765
- rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(ctx->channels));
766
- rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(ctx->bit_depth));
767
- rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(ctx->decoded_bytes));
768
- 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));
769
1004
  return hash;
770
1005
  }
771
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
+
772
1012
  static J_COLOR_SPACE color_space_for_channels(int channels) {
773
1013
  return channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
774
1014
  }
@@ -786,6 +1026,15 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
786
1026
  if (mozjpeg_size_mode) {
787
1027
  cinfo->optimize_coding = TRUE;
788
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
+
789
1038
  if (!mozjpeg_trellis_enabled) {
790
1039
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
791
1040
  jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
@@ -819,65 +1068,77 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
819
1068
  }
820
1069
  }
821
1070
 
822
- static int prepare_encode_row(ip_context_t *ctx, JDIMENSION y, JSAMPROW *row) {
823
- if (ctx->channels == 4) {
824
- size_t rgb_row_size = 0;
825
- if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size)) {
826
- ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
827
- return 0;
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));
828
1078
  }
1079
+ return 1;
1080
+ }
829
1081
 
830
- if (ctx->scratch_row_size < rgb_row_size) {
831
- unsigned char *new_row = (unsigned char *)realloc(ctx->scratch_row, rgb_row_size);
832
- if (!new_row) {
833
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch row");
834
- return 0;
835
- }
836
- ctx->scratch_row = new_row;
837
- ctx->scratch_row_size = rgb_row_size;
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;
838
1095
  }
1096
+ ctx->scratch_row = new_rows;
1097
+ ctx->scratch_row_size = scratch_size;
1098
+ }
839
1099
 
1100
+ for (JDIMENSION i = 0; i < batch; i++) {
1101
+ JDIMENSION y = start + i;
840
1102
  const unsigned char *IP_RESTRICT src =
841
1103
  ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
842
- unsigned char *IP_RESTRICT dst = ctx->scratch_row;
1104
+ unsigned char *IP_RESTRICT dst = ctx->scratch_row + ((size_t)i * rgb_row_size);
843
1105
  const int w = ctx->width;
844
1106
  for (int x = 0; x < w; x++) {
845
1107
  dst[x * 3 + 0] = src[x * 4 + 0];
846
1108
  dst[x * 3 + 1] = src[x * 4 + 1];
847
1109
  dst[x * 3 + 2] = src[x * 4 + 2];
848
1110
  }
849
- *row = ctx->scratch_row;
850
- return 1;
1111
+ rows[i] = dst;
851
1112
  }
852
1113
 
853
- *row = (JSAMPROW)(ctx->pixel_data + ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
854
1114
  return 1;
855
1115
  }
856
1116
 
857
1117
  static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
858
1118
  struct jpeg_compress_struct cinfo;
859
1119
  ip_jpeg_error_mgr jerr;
1120
+ unsigned long jpeg_size = 0;
860
1121
  memset(&cinfo, 0, sizeof(cinfo));
861
1122
  memset(&jerr, 0, sizeof(jerr));
862
1123
 
863
1124
  cinfo.err = jpeg_std_error(&jerr.pub);
864
1125
  jerr.pub.error_exit = ip_jpeg_encode_error_exit;
865
1126
  jerr.ctx = ctx;
1127
+ ctx->transient_jpeg_buf = NULL;
866
1128
 
867
1129
  ctx->jmp_armed = 1;
868
1130
  if (setjmp(ctx->jmpbuf)) {
869
1131
  ctx->jmp_armed = 0;
870
1132
  jpeg_destroy_compress(&cinfo);
1133
+ free(ctx->transient_jpeg_buf);
1134
+ ctx->transient_jpeg_buf = NULL;
871
1135
  if (ctx->status == IP_OK)
872
1136
  ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
873
1137
  return 0;
874
1138
  }
875
1139
 
876
1140
  jpeg_create_compress(&cinfo);
877
-
878
- unsigned char *jpeg_buf = NULL;
879
- unsigned long jpeg_size = 0;
880
- jpeg_mem_dest(&cinfo, &jpeg_buf, &jpeg_size);
1141
+ jpeg_mem_dest(&cinfo, &ctx->transient_jpeg_buf, &jpeg_size);
881
1142
 
882
1143
  cinfo.image_width = (JDIMENSION)ctx->width;
883
1144
  cinfo.image_height = (JDIMENSION)ctx->height;
@@ -901,34 +1162,25 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
901
1162
  ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
902
1163
  jpeg_abort_compress(&cinfo);
903
1164
  jpeg_destroy_compress(&cinfo);
904
- free(jpeg_buf);
1165
+ free(ctx->transient_jpeg_buf);
1166
+ ctx->transient_jpeg_buf = NULL;
905
1167
  ctx->jmp_armed = 0;
906
1168
  return 0;
907
1169
  }
908
1170
 
909
- if (ctx->channels == 4) {
910
- JSAMPROW row = NULL;
911
- if (!prepare_encode_row(ctx, cinfo.next_scanline, &row)) {
912
- jpeg_abort_compress(&cinfo);
913
- jpeg_destroy_compress(&cinfo);
914
- free(jpeg_buf);
915
- ctx->jmp_armed = 0;
916
- return 0;
917
- }
918
-
919
- jpeg_write_scanlines(&cinfo, &row, 1);
920
- continue;
921
- }
922
-
923
1171
  JSAMPROW rows[16];
924
- JDIMENSION batch = cinfo.image_height - cinfo.next_scanline;
1172
+ JDIMENSION start_scanline = cinfo.next_scanline;
1173
+ JDIMENSION batch = cinfo.image_height - start_scanline;
925
1174
  if (batch > 16)
926
1175
  batch = 16;
927
1176
 
928
- for (JDIMENSION i = 0; i < batch; i++) {
929
- JDIMENSION y = cinfo.next_scanline + i;
930
- rows[i] = (JSAMPROW)(ctx->pixel_data +
931
- ((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
1177
+ if (!prepare_encode_rows(ctx, start_scanline, batch, rows)) {
1178
+ jpeg_abort_compress(&cinfo);
1179
+ jpeg_destroy_compress(&cinfo);
1180
+ free(ctx->transient_jpeg_buf);
1181
+ ctx->transient_jpeg_buf = NULL;
1182
+ ctx->jmp_armed = 0;
1183
+ return 0;
932
1184
  }
933
1185
 
934
1186
  jpeg_write_scanlines(&cinfo, rows, batch);
@@ -938,13 +1190,15 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
938
1190
  jpeg_destroy_compress(&cinfo);
939
1191
  ctx->jmp_armed = 0;
940
1192
 
941
- if ((size_t)jpeg_size > ctx->max_output_size) {
942
- 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;
943
1196
  ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
944
1197
  return 0;
945
1198
  }
946
1199
 
947
- ctx->output_data = jpeg_buf;
1200
+ ctx->output_data = ctx->transient_jpeg_buf;
1201
+ ctx->transient_jpeg_buf = NULL;
948
1202
  ctx->output_size = (size_t)jpeg_size;
949
1203
  ctx->output_capacity = (size_t)jpeg_size;
950
1204
  ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
@@ -961,11 +1215,15 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
961
1215
  cinfo.err = jpeg_std_error(&jerr.pub);
962
1216
  jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
963
1217
  jerr.ctx = ctx;
1218
+ ctx->transient_decode_buf = NULL;
1219
+ ctx->source_orientation = 1;
964
1220
 
965
1221
  ctx->jmp_armed = 1;
966
1222
  if (setjmp(ctx->jmpbuf)) {
967
1223
  ctx->jmp_armed = 0;
968
1224
  jpeg_destroy_decompress(&cinfo);
1225
+ free(ctx->transient_decode_buf);
1226
+ ctx->transient_decode_buf = NULL;
969
1227
  if (ctx->status == IP_OK)
970
1228
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
971
1229
  return 0;
@@ -974,9 +1232,10 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
974
1232
  jpeg_create_decompress(&cinfo);
975
1233
  jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
976
1234
 
1235
+ jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
977
1236
  if (!ctx->strip_metadata) {
978
1237
  jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
979
- for (int app = 1; app < 16; app++) {
1238
+ for (int app = 2; app < 16; app++) {
980
1239
  jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
981
1240
  }
982
1241
  }
@@ -989,6 +1248,8 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
989
1248
  return 0;
990
1249
  }
991
1250
 
1251
+ ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
1252
+
992
1253
  if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX) {
993
1254
  jpeg_destroy_decompress(&cinfo);
994
1255
  ctx->jmp_armed = 0;
@@ -1040,8 +1301,12 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1040
1301
 
1041
1302
  jpeg_start_decompress(&cinfo);
1042
1303
 
1043
- if (!ctx->strip_metadata && !fast_decode_mode) {
1044
- ip_save_markers_from_decompress(ctx, &cinfo);
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
+ }
1045
1310
  }
1046
1311
 
1047
1312
  size_t row_stride = 0;
@@ -1054,8 +1319,8 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1054
1319
  ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
1055
1320
  return 0;
1056
1321
  }
1057
- unsigned char *buf = (unsigned char *)ip_malloc_hot(size);
1058
- if (!buf && size > 0) {
1322
+ ctx->transient_decode_buf = (unsigned char *)ip_malloc_hot(size);
1323
+ if (!ctx->transient_decode_buf && size > 0) {
1059
1324
  jpeg_destroy_decompress(&cinfo);
1060
1325
  ctx->jmp_armed = 0;
1061
1326
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
@@ -1063,13 +1328,24 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1063
1328
  }
1064
1329
 
1065
1330
  while (cinfo.output_scanline < cinfo.output_height) {
1331
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1332
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
1333
+ jpeg_abort_decompress(&cinfo);
1334
+ jpeg_destroy_decompress(&cinfo);
1335
+ free(ctx->transient_decode_buf);
1336
+ ctx->transient_decode_buf = NULL;
1337
+ ctx->jmp_armed = 0;
1338
+ return 0;
1339
+ }
1340
+
1066
1341
  JSAMPROW rows[16];
1067
1342
  JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1068
1343
  if (batch > 16)
1069
1344
  batch = 16;
1070
1345
 
1071
1346
  for (JDIMENSION i = 0; i < batch; i++) {
1072
- rows[i] = buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1347
+ rows[i] =
1348
+ ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
1073
1349
  }
1074
1350
 
1075
1351
  jpeg_read_scanlines(&cinfo, rows, batch);
@@ -1082,6 +1358,16 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
1082
1358
  jpeg_destroy_decompress(&cinfo);
1083
1359
  ctx->jmp_armed = 0;
1084
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
+
1085
1371
  *pixels = buf;
1086
1372
  *width = out_width;
1087
1373
  *height = out_height;
@@ -1135,11 +1421,14 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1135
1421
  cinfo.err = jpeg_std_error(&jerr.pub);
1136
1422
  jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1137
1423
  jerr.ctx = ctx;
1424
+ ctx->transient_decode_buf = NULL;
1138
1425
 
1139
1426
  ctx->jmp_armed = 1;
1140
1427
  if (setjmp(ctx->jmpbuf)) {
1141
1428
  ctx->jmp_armed = 0;
1142
1429
  jpeg_destroy_decompress(&cinfo);
1430
+ free(ctx->transient_decode_buf);
1431
+ ctx->transient_decode_buf = NULL;
1143
1432
  if (ctx->status == IP_OK)
1144
1433
  ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG luma decode failed");
1145
1434
  return 0;
@@ -1172,7 +1461,6 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1172
1461
  return 0;
1173
1462
  }
1174
1463
 
1175
- int out_channels = cinfo.num_components == 1 ? 1 : 3;
1176
1464
  int old_width = ctx->width;
1177
1465
  int old_height = ctx->height;
1178
1466
  int old_channels = ctx->channels;
@@ -1204,7 +1492,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1204
1492
  return 0;
1205
1493
  }
1206
1494
 
1207
- cinfo.out_color_space = out_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
1495
+ cinfo.out_color_space = JCS_GRAYSCALE;
1208
1496
  #if defined(IMAGE_PACK_HAS_SIMD)
1209
1497
  cinfo.dct_method = JDCT_ISLOW;
1210
1498
  #else
@@ -1219,86 +1507,55 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
1219
1507
  jpeg_start_decompress(&cinfo);
1220
1508
 
1221
1509
  size_t luma_stride = (size_t)cinfo.output_width;
1222
- size_t row_stride = 0;
1223
- if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
1224
- &row_stride) ||
1225
- luma_stride == 0 || luma_size != luma_stride * (size_t)cinfo.output_height) {
1510
+ if (cinfo.output_components != 1 || luma_stride == 0 ||
1511
+ luma_size != luma_stride * (size_t)cinfo.output_height) {
1226
1512
  jpeg_destroy_decompress(&cinfo);
1227
1513
  ctx->jmp_armed = 0;
1228
1514
  ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
1229
1515
  return 0;
1230
1516
  }
1231
1517
 
1232
- unsigned char *buf = (unsigned char *)ip_malloc_hot(luma_size);
1233
- if (!buf && luma_size > 0) {
1518
+ ctx->transient_decode_buf = (unsigned char *)ip_malloc_hot(luma_size);
1519
+ if (!ctx->transient_decode_buf && luma_size > 0) {
1234
1520
  jpeg_destroy_decompress(&cinfo);
1235
1521
  ctx->jmp_armed = 0;
1236
1522
  ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
1237
1523
  return 0;
1238
1524
  }
1239
1525
 
1240
- unsigned char *scratch = NULL;
1241
- if (out_channels != 1) {
1242
- size_t scratch_size = 0;
1243
- if (!ip_checked_mul_size(row_stride, 16, &scratch_size)) {
1244
- free(buf);
1245
- jpeg_destroy_decompress(&cinfo);
1246
- ctx->jmp_armed = 0;
1247
- ip_context_set_error(ctx, IP_ERR_LIMIT, "luma decode scratch size overflow");
1248
- return 0;
1249
- }
1250
- scratch = (unsigned char *)ip_malloc_hot(scratch_size);
1251
- if (!scratch) {
1252
- free(buf);
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);
1253
1530
  jpeg_destroy_decompress(&cinfo);
1531
+ free(ctx->transient_decode_buf);
1532
+ ctx->transient_decode_buf = NULL;
1254
1533
  ctx->jmp_armed = 0;
1255
- ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma decode scratch row");
1256
1534
  return 0;
1257
1535
  }
1258
- }
1259
1536
 
1260
- while (cinfo.output_scanline < cinfo.output_height) {
1261
1537
  JSAMPROW rows[16];
1262
- JDIMENSION start_scanline = cinfo.output_scanline;
1263
- JDIMENSION batch = cinfo.output_height - start_scanline;
1538
+ JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
1264
1539
  if (batch > 16)
1265
1540
  batch = 16;
1266
1541
 
1267
- if (out_channels == 1) {
1268
- for (JDIMENSION i = 0; i < batch; i++) {
1269
- rows[i] = buf + ((size_t)(start_scanline + i) * luma_stride);
1270
- }
1271
- } else {
1272
- for (JDIMENSION i = 0; i < batch; i++) {
1273
- rows[i] = scratch + ((size_t)i * row_stride);
1274
- }
1542
+ for (JDIMENSION i = 0; i < batch; i++) {
1543
+ rows[i] =
1544
+ ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * luma_stride);
1275
1545
  }
1276
1546
 
1277
- JDIMENSION lines_read = jpeg_read_scanlines(&cinfo, rows, batch);
1278
-
1279
- if (out_channels != 1) {
1280
- for (JDIMENSION y = 0; y < lines_read; y++) {
1281
- const unsigned char *IP_RESTRICT src = scratch + ((size_t)y * row_stride);
1282
- unsigned char *IP_RESTRICT dst = buf + ((size_t)(start_scanline + y) * luma_stride);
1283
- for (size_t x = 0; x < luma_stride; x++) {
1284
- unsigned int r = src[x * 3 + 0];
1285
- unsigned int g = src[x * 3 + 1];
1286
- unsigned int b = src[x * 3 + 2];
1287
- dst[x] = (unsigned char)((77u * r + 150u * g + 29u * b + 128u) >> 8);
1288
- }
1289
- }
1290
- }
1547
+ jpeg_read_scanlines(&cinfo, rows, batch);
1291
1548
  }
1292
1549
 
1293
1550
  int out_width = (int)cinfo.output_width;
1294
1551
  int out_height = (int)cinfo.output_height;
1295
1552
 
1296
- free(scratch);
1297
1553
  jpeg_finish_decompress(&cinfo);
1298
1554
  jpeg_destroy_decompress(&cinfo);
1299
1555
  ctx->jmp_armed = 0;
1300
1556
 
1301
- *luma = buf;
1557
+ *luma = ctx->transient_decode_buf;
1558
+ ctx->transient_decode_buf = NULL;
1302
1559
  *width = out_width;
1303
1560
  *height = out_height;
1304
1561
  return 1;
@@ -1480,7 +1737,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1480
1737
  int reference_channels = 0;
1481
1738
 
1482
1739
  if (ctx->pixel_data) {
1483
- reference_pixels = ctx->owned_pixel_data;
1740
+ reference_pixels = (unsigned char *)ctx->pixel_data;
1484
1741
  reference_width = ctx->width;
1485
1742
  reference_height = ctx->height;
1486
1743
  reference_channels = ctx->channels;
@@ -1502,7 +1759,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1502
1759
 
1503
1760
  if (reference_channels == 4) {
1504
1761
  ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1505
- "min_ssim is not supported for RGBA input in v0.2.1");
1762
+ "min_ssim is not supported for RGBA input in v0.2.2");
1506
1763
  return 0;
1507
1764
  }
1508
1765
 
@@ -1613,6 +1870,176 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
1613
1870
  return 1;
1614
1871
  }
1615
1872
 
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");
1887
+ return 0;
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");
1897
+ return 0;
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");
1905
+ return 0;
1906
+ }
1907
+ if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
1908
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1909
+ return 0;
1910
+ }
1911
+
1912
+ return 1;
1913
+ }
1914
+
1915
+ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
1916
+ struct jpeg_decompress_struct srcinfo;
1917
+ struct jpeg_compress_struct dstinfo;
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
+
1923
+ memset(&srcinfo, 0, sizeof(srcinfo));
1924
+ memset(&dstinfo, 0, sizeof(dstinfo));
1925
+ memset(&srcerr, 0, sizeof(srcerr));
1926
+ memset(&dsterr, 0, sizeof(dsterr));
1927
+ ctx->transient_jpeg_buf = NULL;
1928
+
1929
+ srcinfo.err = jpeg_std_error(&srcerr.pub);
1930
+ srcerr.pub.error_exit = ip_jpeg_invalid_error_exit;
1931
+ srcerr.ctx = ctx;
1932
+
1933
+ dstinfo.err = jpeg_std_error(&dsterr.pub);
1934
+ dsterr.pub.error_exit = ip_jpeg_encode_error_exit;
1935
+ dsterr.ctx = ctx;
1936
+
1937
+ ctx->jmp_armed = 1;
1938
+ if (setjmp(ctx->jmpbuf)) {
1939
+ ctx->jmp_armed = 0;
1940
+ jpeg_destroy_compress(&dstinfo);
1941
+ jpeg_destroy_decompress(&srcinfo);
1942
+ free(ctx->transient_jpeg_buf);
1943
+ ctx->transient_jpeg_buf = NULL;
1944
+ if (ctx->status == IP_OK)
1945
+ ip_context_set_error(ctx, IP_ERR_ENCODE, "lossless JPEG optimize failed");
1946
+ return 0;
1947
+ }
1948
+
1949
+ jpeg_create_decompress(&srcinfo);
1950
+ jpeg_create_compress(&dstinfo);
1951
+ jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
1952
+ ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
1953
+
1954
+ if (ctx->cancellable_requested && atomic_load(&ctx->cancelled)) {
1955
+ ip_context_set_error(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
1956
+ jpeg_destroy_compress(&dstinfo);
1957
+ jpeg_destroy_decompress(&srcinfo);
1958
+ ctx->jmp_armed = 0;
1959
+ return 0;
1960
+ }
1961
+
1962
+ int rc = jpeg_read_header(&srcinfo, TRUE);
1963
+ if (rc != JPEG_HEADER_OK) {
1964
+ jpeg_destroy_compress(&dstinfo);
1965
+ jpeg_destroy_decompress(&srcinfo);
1966
+ ctx->jmp_armed = 0;
1967
+ ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
1968
+ return 0;
1969
+ }
1970
+
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);
1974
+ jpeg_destroy_decompress(&srcinfo);
1975
+ ctx->jmp_armed = 0;
1976
+ ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
1977
+ "lossless optimize cannot strip EXIF Orientation without changing "
1978
+ "visual orientation; use strip_metadata: false or ImagePack.compress");
1979
+ return 0;
1980
+ }
1981
+
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
+ }
1995
+
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;
2001
+ if (ctx->progressive) {
2002
+ jpeg_simple_progression(&dstinfo);
2003
+ dstinfo.optimize_coding = TRUE;
2004
+ }
2005
+
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);
2021
+
2022
+ jpeg_finish_compress(&dstinfo);
2023
+ jpeg_finish_decompress(&srcinfo);
2024
+ jpeg_destroy_compress(&dstinfo);
2025
+ jpeg_destroy_decompress(&srcinfo);
2026
+ ctx->jmp_armed = 0;
2027
+
2028
+ if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size) {
2029
+ free(ctx->transient_jpeg_buf);
2030
+ ctx->transient_jpeg_buf = NULL;
2031
+ ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
2032
+ return 0;
2033
+ }
2034
+
2035
+ ctx->output_data = ctx->transient_jpeg_buf;
2036
+ ctx->transient_jpeg_buf = NULL;
2037
+ ctx->output_size = (size_t)jpeg_size;
2038
+ ctx->output_capacity = (size_t)jpeg_size;
2039
+ ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
2040
+ return 1;
2041
+ }
2042
+
1616
2043
  static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
1617
2044
  if (ctx->ssim_guard_enabled)
1618
2045
  return guarded_compress_jpeg_input_with_mode(ctx, 0);
@@ -1692,6 +2119,29 @@ static int ip_run_context(ip_context_t *ctx) {
1692
2119
  return ctx->status == IP_OK;
1693
2120
  }
1694
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
+
1695
2145
  static size_t config_size_value(VALUE config, ID id, size_t fallback) {
1696
2146
  VALUE value = rb_funcall(config, id, 0);
1697
2147
  if (NIL_P(value))
@@ -1726,15 +2176,19 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
1726
2176
  ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "image dimensions must be positive");
1727
2177
  return;
1728
2178
  }
1729
- 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) {
1730
2184
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
1731
2185
  return;
1732
2186
  }
1733
- if (ctx->height > ctx->max_height) {
2187
+ if (ctx->max_height > 0 && ctx->height > ctx->max_height) {
1734
2188
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
1735
2189
  return;
1736
2190
  }
1737
- 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) {
1738
2192
  ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
1739
2193
  return;
1740
2194
  }
@@ -1746,95 +2200,137 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
1746
2200
  ctx->decoded_bytes = decoded_bytes;
1747
2201
  }
1748
2202
 
1749
- static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
1750
- VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
1751
- VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
1752
- 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;
1753
2205
  ip_context_t *ctx = ip_context_new();
1754
2206
  if (!ctx)
1755
2207
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2208
+ call->ctx = ctx;
1756
2209
 
1757
- ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1758
- ctx->algo = ip_parse_algo(algo);
1759
- ctx->quality = NUM2INT(quality);
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);
1760
2213
  ctx->selected_quality = ctx->quality;
1761
2214
  ip_validate_quality_or_raise(ctx);
1762
- ctx->min_ssim = NUM2DBL(min_ssim);
2215
+ ctx->min_ssim = NUM2DBL(call->min_ssim);
1763
2216
  ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
1764
2217
  ip_validate_min_ssim_or_raise(ctx);
1765
- ctx->mozjpeg_trellis_enabled = ip_bool_value(mozjpeg_trellis);
1766
- ctx->progressive = ip_bool_value(progressive);
1767
- ctx->strip_metadata = ip_bool_value(strip_metadata);
1768
- ctx->requested_execution = ip_parse_execution(execution);
1769
- ctx->cancellable_requested = ip_bool_value(cancellable);
1770
- ctx->has_scheduler = ip_bool_value(has_scheduler);
1771
- apply_configuration(self, ctx);
1772
-
1773
- if (!ip_prepare_input_bytes(ctx, input, ip_parse_input_kind(input_kind)) ||
1774
- !ip_prepare_output_path(ctx, output, out_kind)) {
1775
- VALUE exception = ip_status_to_exception(ctx->status);
1776
- char message[512];
1777
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1778
- ip_context_free(ctx);
1779
- rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
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");
1780
2230
  }
1781
2231
 
1782
2232
  if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
1783
2233
  !ip_inspect_jpeg_header(ctx)) {
1784
- VALUE exception = ip_status_to_exception(ctx->status);
1785
- char message[512];
1786
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1787
- ip_context_free(ctx);
1788
- rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
2234
+ ip_raise_for_status(ctx);
2235
+ rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
1789
2236
  }
1790
2237
 
1791
2238
  ip_run_context(ctx);
1792
- VALUE result = ip_finish_output(ctx, out_kind);
1793
- ip_context_free(ctx);
1794
- return result;
2239
+ return ip_finish_output(ctx, out_kind);
1795
2240
  }
1796
2241
 
1797
- static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
1798
- VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
1799
- VALUE quality, VALUE progressive, VALUE execution,
1800
- 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;
1801
2255
  ip_context_t *ctx = ip_context_new();
1802
2256
  if (!ctx)
1803
2257
  rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
2258
+ call->ctx = ctx;
1804
2259
 
1805
- ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
1806
- ctx->algo = ip_parse_algo(algo);
1807
- ctx->quality = NUM2INT(quality);
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;
1808
2264
  ip_validate_quality_or_raise(ctx);
1809
- 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);
1810
2269
  ctx->strip_metadata = 1;
1811
- ctx->requested_execution = ip_parse_execution(execution);
1812
- ctx->cancellable_requested = ip_bool_value(cancellable);
1813
- ctx->has_scheduler = ip_bool_value(has_scheduler);
1814
- apply_configuration(self, ctx);
1815
-
1816
- if (!ip_prepare_pixels(ctx, buffer, NUM2INT(width), NUM2INT(height), NUM2INT(channels)) ||
1817
- !ip_prepare_output_path(ctx, output, out_kind)) {
1818
- VALUE exception = ip_status_to_exception(ctx->status);
1819
- char message[512];
1820
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1821
- ip_context_free(ctx);
1822
- rb_raise(exception, "%s", message[0] ? message : "invalid pixel input");
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");
1823
2280
  }
1824
2281
 
1825
2282
  validate_limits_for_pixels(ctx);
1826
- if (ctx->status != IP_OK) {
1827
- VALUE exception = ip_status_to_exception(ctx->status);
1828
- char message[512];
1829
- snprintf(message, sizeof(message), "%s", ctx->error_message);
1830
- ip_context_free(ctx);
1831
- rb_raise(exception, "%s", message);
1832
- }
2283
+ if (ctx->status != IP_OK)
2284
+ ip_raise_for_status(ctx);
1833
2285
 
1834
2286
  ip_run_context(ctx);
1835
- VALUE result = ip_finish_output(ctx, out_kind);
1836
- ip_context_free(ctx);
1837
- 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);
1838
2334
  }
1839
2335
 
1840
2336
  IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
@@ -1870,7 +2366,23 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
1870
2366
  rb_eImagePackOutOfMemoryError = rb_const_get(rb_mImagePack, rb_intern("OutOfMemoryError"));
1871
2367
  rb_eImagePackCancelledError = rb_const_get(rb_mImagePack, rb_intern("CancelledError"));
1872
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
+
1873
2376
  rb_define_singleton_method(rb_mImagePack, "__compress_jpeg", ip_compress_jpeg_entry, 13);
1874
- rb_define_singleton_method(rb_mImagePack, "__compress_pixels", ip_compress_pixels_entry, 12);
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);
1875
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")));
1876
2388
  }