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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +53 -13
- data/ext/image_pack/extconf.rb +6 -2
- data/ext/image_pack/image_pack.c +721 -209
- data/lib/image_pack/version.rb +1 -1
- data/lib/image_pack.rb +62 -26
- metadata +1 -2
- data/lib/image_pack/backend.rb +0 -8
data/ext/image_pack/image_pack.c
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
600
|
-
|
|
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
|
|
633
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
747
|
-
(
|
|
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
|
-
|
|
755
|
-
|
|
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(
|
|
764
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("height")), INT2NUM(
|
|
765
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("channels")), INT2NUM(
|
|
766
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("bit_depth")), INT2NUM(
|
|
767
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("decoded_bytes")), SIZET2NUM(
|
|
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
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1058
|
-
if (!
|
|
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] =
|
|
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 =
|
|
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
|
-
|
|
1223
|
-
|
|
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
|
-
|
|
1233
|
-
if (!
|
|
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
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
|
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
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
-
|
|
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 =
|
|
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->
|
|
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.
|
|
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->
|
|
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
|
|
1750
|
-
|
|
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
|
-
|
|
1776
|
-
|
|
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
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
1793
|
-
ip_context_free(ctx);
|
|
1794
|
-
return result;
|
|
2239
|
+
return ip_finish_output(ctx, out_kind);
|
|
1795
2240
|
}
|
|
1796
2241
|
|
|
1797
|
-
static VALUE
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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->
|
|
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),
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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,
|
|
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
|
}
|