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