image_pack 0.2.1 → 0.2.3
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 +20 -0
- data/README.md +75 -16
- data/ext/image_pack/extconf.rb +41 -126
- data/ext/image_pack/image_pack.c +1151 -596
- data/ext/image_pack/mozjpeg_sources.rb +178 -0
- data/ext/image_pack/vendor/mozjpeg/BUILDING.md +744 -0
- data/ext/image_pack/vendor/mozjpeg/CODE_OF_CONDUCT.md +15 -0
- data/ext/image_pack/vendor/mozjpeg/ChangeLog.md +1996 -0
- data/lib/image_pack/configuration.rb +54 -8
- data/lib/image_pack/version.rb +1 -1
- data/lib/image_pack.rb +124 -41
- metadata +13 -79
- data/ext/image_pack/vendor/mozjpeg/README.ijg +0 -258
- data/ext/image_pack/vendor/mozjpeg/cdjpeg.c +0 -156
- data/ext/image_pack/vendor/mozjpeg/cjpeg.c +0 -961
- data/ext/image_pack/vendor/mozjpeg/djpeg.c +0 -855
- data/ext/image_pack/vendor/mozjpeg/jaricom.c +0 -157
- data/ext/image_pack/vendor/mozjpeg/jcarith.c +0 -972
- data/ext/image_pack/vendor/mozjpeg/jcstest.c +0 -126
- data/ext/image_pack/vendor/mozjpeg/jdarith.c +0 -782
- data/ext/image_pack/vendor/mozjpeg/jdatadst-tj.c +0 -198
- data/ext/image_pack/vendor/mozjpeg/jdatasrc-tj.c +0 -194
- data/ext/image_pack/vendor/mozjpeg/jpegtran.c +0 -827
- data/ext/image_pack/vendor/mozjpeg/jpegyuv.c +0 -172
- data/ext/image_pack/vendor/mozjpeg/rdbmp.c +0 -690
- data/ext/image_pack/vendor/mozjpeg/rdcolmap.c +0 -253
- data/ext/image_pack/vendor/mozjpeg/rdgif.c +0 -720
- data/ext/image_pack/vendor/mozjpeg/rdjpeg.c +0 -160
- data/ext/image_pack/vendor/mozjpeg/rdjpgcom.c +0 -494
- data/ext/image_pack/vendor/mozjpeg/rdpng.c +0 -194
- data/ext/image_pack/vendor/mozjpeg/rdppm.c +0 -781
- data/ext/image_pack/vendor/mozjpeg/rdswitch.c +0 -642
- data/ext/image_pack/vendor/mozjpeg/rdtarga.c +0 -508
- data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jccolext-neon.c +0 -148
- data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jchuff-neon.c +0 -334
- data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jsimd.c +0 -976
- data/ext/image_pack/vendor/mozjpeg/simd/i386/jsimd.c +0 -1312
- data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd.c +0 -1143
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolext-mmi.c +0 -455
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolor-mmi.c +0 -148
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgray-mmi.c +0 -132
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgryext-mmi.c +0 -374
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcsample-mmi.c +0 -98
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolext-mmi.c +0 -415
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolor-mmi.c +0 -139
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmerge-mmi.c +0 -149
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmrgext-mmi.c +0 -615
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdsample-mmi.c +0 -304
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctfst-mmi.c +0 -255
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctint-mmi.c +0 -398
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctfst-mmi.c +0 -395
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctint-mmi.c +0 -571
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jquanti-mmi.c +0 -124
- data/ext/image_pack/vendor/mozjpeg/simd/mips64/jsimd.c +0 -866
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolext-altivec.c +0 -269
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolor-altivec.c +0 -116
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgray-altivec.c +0 -111
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgryext-altivec.c +0 -228
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcsample-altivec.c +0 -159
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolext-altivec.c +0 -276
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolor-altivec.c +0 -106
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmerge-altivec.c +0 -130
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmrgext-altivec.c +0 -329
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdsample-altivec.c +0 -400
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctfst-altivec.c +0 -154
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctint-altivec.c +0 -258
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctfst-altivec.c +0 -255
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctint-altivec.c +0 -357
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jquanti-altivec.c +0 -250
- data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jsimd.c +0 -884
- data/ext/image_pack/vendor/mozjpeg/strtest.c +0 -170
- data/ext/image_pack/vendor/mozjpeg/tjbench.c +0 -1044
- data/ext/image_pack/vendor/mozjpeg/tjexample.c +0 -406
- data/ext/image_pack/vendor/mozjpeg/tjunittest.c +0 -961
- data/ext/image_pack/vendor/mozjpeg/tjutil.c +0 -70
- data/ext/image_pack/vendor/mozjpeg/transupp.c +0 -2373
- data/ext/image_pack/vendor/mozjpeg/turbojpeg-jni.c +0 -1259
- data/ext/image_pack/vendor/mozjpeg/turbojpeg.c +0 -2320
- data/ext/image_pack/vendor/mozjpeg/wrbmp.c +0 -552
- data/ext/image_pack/vendor/mozjpeg/wrgif.c +0 -580
- data/ext/image_pack/vendor/mozjpeg/wrjpgcom.c +0 -577
- data/ext/image_pack/vendor/mozjpeg/wrppm.c +0 -366
- data/ext/image_pack/vendor/mozjpeg/wrtarga.c +0 -258
- data/ext/image_pack/vendor/mozjpeg/yuvjpeg.c +0 -268
- data/lib/image_pack/backend.rb +0 -8
data/ext/image_pack/image_pack.c
CHANGED
|
@@ -3,23 +3,28 @@
|
|
|
3
3
|
#include <ruby/version.h>
|
|
4
4
|
#include <ruby/encoding.h>
|
|
5
5
|
|
|
6
|
-
#if
|
|
7
|
-
#
|
|
6
|
+
#if defined(RB_NOGVL_OFFLOAD_SAFE)
|
|
7
|
+
#define IMAGE_PACK_HAS_OFFLOAD_SAFE 1
|
|
8
|
+
#else
|
|
9
|
+
#define IMAGE_PACK_HAS_OFFLOAD_SAFE 0
|
|
10
|
+
#define RB_NOGVL_OFFLOAD_SAFE 0
|
|
8
11
|
#endif
|
|
9
12
|
|
|
10
13
|
#include <errno.h>
|
|
11
14
|
#include <setjmp.h>
|
|
12
15
|
#include <stdatomic.h>
|
|
13
16
|
#include <stdint.h>
|
|
17
|
+
#include <inttypes.h>
|
|
14
18
|
#include <limits.h>
|
|
19
|
+
#include <sys/types.h>
|
|
15
20
|
#include <stdio.h>
|
|
16
21
|
#include <stdlib.h>
|
|
17
22
|
#include <string.h>
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
#if defined(__linux__)
|
|
21
|
-
#include <sys/mman.h>
|
|
23
|
+
#if defined(_WIN32)
|
|
24
|
+
#include <windows.h>
|
|
22
25
|
#endif
|
|
26
|
+
#include <jpeglib.h>
|
|
27
|
+
#include <jconfigint.h>
|
|
23
28
|
|
|
24
29
|
#ifndef IMAGE_PACK_INIT_EXPORT
|
|
25
30
|
#if defined(_WIN32)
|
|
@@ -31,10 +36,6 @@
|
|
|
31
36
|
#endif
|
|
32
37
|
#endif
|
|
33
38
|
|
|
34
|
-
#ifndef RB_NOGVL_OFFLOAD_SAFE
|
|
35
|
-
#error "RB_NOGVL_OFFLOAD_SAFE is required by image_pack"
|
|
36
|
-
#endif
|
|
37
|
-
|
|
38
39
|
#ifndef TRUE
|
|
39
40
|
#define TRUE 1
|
|
40
41
|
#endif
|
|
@@ -51,6 +52,32 @@
|
|
|
51
52
|
#define IP_RESTRICT
|
|
52
53
|
#endif
|
|
53
54
|
|
|
55
|
+
#if defined(IMAGE_PACK_HAS_SIMD)
|
|
56
|
+
#define IP_FAST_DCT JDCT_ISLOW
|
|
57
|
+
#else
|
|
58
|
+
#define IP_FAST_DCT JDCT_FASTEST
|
|
59
|
+
#endif
|
|
60
|
+
|
|
61
|
+
#define IP_FAIL(ctx, st, msg) \
|
|
62
|
+
do { \
|
|
63
|
+
ip_context_set_error((ctx), (st), (msg)); \
|
|
64
|
+
return 0; \
|
|
65
|
+
} while (0)
|
|
66
|
+
|
|
67
|
+
#define IP_FAIL_NULL(ctx, st, msg) \
|
|
68
|
+
do { \
|
|
69
|
+
ip_context_set_error((ctx), (st), (msg)); \
|
|
70
|
+
return NULL; \
|
|
71
|
+
} while (0)
|
|
72
|
+
|
|
73
|
+
#define IP_FAIL_GOTO(ctx, st, msg) \
|
|
74
|
+
do { \
|
|
75
|
+
ip_context_set_error((ctx), (st), (msg)); \
|
|
76
|
+
goto fail; \
|
|
77
|
+
} while (0)
|
|
78
|
+
|
|
79
|
+
#define IP_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
|
|
80
|
+
|
|
54
81
|
typedef enum { IP_ALGO_JPEG_TURBO = 1, IP_ALGO_MOZJPEG = 2 } ip_algo_t;
|
|
55
82
|
|
|
56
83
|
typedef enum {
|
|
@@ -90,11 +117,11 @@ typedef struct {
|
|
|
90
117
|
int height;
|
|
91
118
|
int channels;
|
|
92
119
|
int bit_depth;
|
|
120
|
+
int jpeg_color_space;
|
|
93
121
|
size_t decoded_bytes;
|
|
94
122
|
|
|
95
123
|
unsigned char *output_data;
|
|
96
124
|
size_t output_size;
|
|
97
|
-
size_t output_capacity;
|
|
98
125
|
ip_output_owner_t output_owner;
|
|
99
126
|
char *output_path;
|
|
100
127
|
|
|
@@ -128,9 +155,6 @@ typedef struct {
|
|
|
128
155
|
jmp_buf jmpbuf;
|
|
129
156
|
int jmp_armed;
|
|
130
157
|
|
|
131
|
-
unsigned char *scratch_row;
|
|
132
|
-
size_t scratch_row_size;
|
|
133
|
-
|
|
134
158
|
struct {
|
|
135
159
|
int marker;
|
|
136
160
|
unsigned char *data;
|
|
@@ -138,6 +162,10 @@ typedef struct {
|
|
|
138
162
|
} *preserved_markers;
|
|
139
163
|
size_t preserved_marker_count;
|
|
140
164
|
size_t preserved_marker_capacity;
|
|
165
|
+
|
|
166
|
+
unsigned char *transient_jpeg_buf;
|
|
167
|
+
unsigned char *transient_decode_buf;
|
|
168
|
+
int source_orientation;
|
|
141
169
|
} ip_context_t;
|
|
142
170
|
|
|
143
171
|
typedef struct {
|
|
@@ -186,6 +214,9 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
|
|
|
186
214
|
static void ip_validate_quality_or_raise(ip_context_t *ctx);
|
|
187
215
|
static void ip_validate_min_ssim_or_raise(ip_context_t *ctx);
|
|
188
216
|
static int ip_bool_value(VALUE value);
|
|
217
|
+
static int ip_value_negative(VALUE value);
|
|
218
|
+
static char *ip_strdup(const char *source);
|
|
219
|
+
static int ip_replace_file(const char *tmp_path, const char *output_path);
|
|
189
220
|
|
|
190
221
|
static ip_algo_t ip_parse_algo(VALUE sym);
|
|
191
222
|
static ip_execution_t ip_parse_execution(VALUE sym);
|
|
@@ -193,11 +224,15 @@ static ip_input_kind_t ip_parse_input_kind(VALUE sym);
|
|
|
193
224
|
static ip_output_kind_t ip_parse_output_kind(VALUE sym);
|
|
194
225
|
|
|
195
226
|
static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
|
|
196
|
-
static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels
|
|
227
|
+
static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels,
|
|
228
|
+
int exact_size);
|
|
229
|
+
static int ip_ensure_owned_input_for_async(ip_context_t *ctx, VALUE input, ip_input_kind_t kind);
|
|
230
|
+
static int ip_ensure_owned_pixels_for_async(ip_context_t *ctx, VALUE buffer);
|
|
197
231
|
static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind);
|
|
198
232
|
static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind);
|
|
233
|
+
static void apply_configuration(VALUE self, ip_context_t *ctx);
|
|
199
234
|
|
|
200
|
-
static int ip_inspect_jpeg_header(ip_context_t *ctx);
|
|
235
|
+
static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck);
|
|
201
236
|
static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind);
|
|
202
237
|
|
|
203
238
|
static void ip_resolve_execution(ip_context_t *ctx);
|
|
@@ -214,6 +249,40 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
|
|
|
214
249
|
static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode);
|
|
215
250
|
static int ip_jpeg_turbo_compress(ip_context_t *ctx);
|
|
216
251
|
static int ip_mozjpeg_compress(ip_context_t *ctx);
|
|
252
|
+
static int ip_lossless_optimize_jpeg(ip_context_t *ctx);
|
|
253
|
+
static int ip_run_optimize_context(ip_context_t *ctx);
|
|
254
|
+
|
|
255
|
+
typedef struct {
|
|
256
|
+
VALUE self, input, input_kind, output, output_kind, algo, quality, min_ssim;
|
|
257
|
+
VALUE mozjpeg_trellis, progressive, strip_metadata, execution, cancellable, has_scheduler;
|
|
258
|
+
ip_context_t *ctx;
|
|
259
|
+
} ip_compress_jpeg_call_t;
|
|
260
|
+
|
|
261
|
+
typedef struct {
|
|
262
|
+
VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
|
|
263
|
+
VALUE mozjpeg_trellis, progressive, exact_size, execution, cancellable, has_scheduler;
|
|
264
|
+
ip_context_t *ctx;
|
|
265
|
+
} ip_compress_pixels_call_t;
|
|
266
|
+
|
|
267
|
+
typedef struct {
|
|
268
|
+
VALUE self, input, input_kind, output, output_kind, progressive, strip_metadata;
|
|
269
|
+
VALUE execution, cancellable, has_scheduler;
|
|
270
|
+
ip_context_t *ctx;
|
|
271
|
+
} ip_optimize_jpeg_call_t;
|
|
272
|
+
|
|
273
|
+
typedef struct {
|
|
274
|
+
VALUE self, input, input_kind;
|
|
275
|
+
ip_context_t *ctx;
|
|
276
|
+
} ip_inspect_call_t;
|
|
277
|
+
|
|
278
|
+
static VALUE ip_call_cleanup(VALUE ptr) {
|
|
279
|
+
ip_context_t **ctx_ptr = (ip_context_t **)ptr;
|
|
280
|
+
if (ctx_ptr && *ctx_ptr) {
|
|
281
|
+
ip_context_free(*ctx_ptr);
|
|
282
|
+
*ctx_ptr = NULL;
|
|
283
|
+
}
|
|
284
|
+
return Qnil;
|
|
285
|
+
}
|
|
217
286
|
|
|
218
287
|
static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
|
|
219
288
|
VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
|
|
@@ -221,8 +290,12 @@ static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, V
|
|
|
221
290
|
VALUE execution, VALUE cancellable, VALUE has_scheduler);
|
|
222
291
|
static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
|
|
223
292
|
VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
|
|
224
|
-
VALUE quality, VALUE
|
|
293
|
+
VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
|
|
294
|
+
VALUE progressive, VALUE exact_size, VALUE execution,
|
|
225
295
|
VALUE cancellable, VALUE has_scheduler);
|
|
296
|
+
static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
|
|
297
|
+
VALUE output_kind, VALUE progressive, VALUE strip_metadata,
|
|
298
|
+
VALUE execution, VALUE cancellable, VALUE has_scheduler);
|
|
226
299
|
|
|
227
300
|
static VALUE ip_status_to_exception(ip_status_t status) {
|
|
228
301
|
switch (status) {
|
|
@@ -271,24 +344,11 @@ static int ip_checked_image_size(int width, int height, int channels, size_t *ou
|
|
|
271
344
|
return ip_checked_mul_size(pixels, (size_t)channels, out);
|
|
272
345
|
}
|
|
273
346
|
|
|
274
|
-
static void *ip_malloc_hot(size_t size) {
|
|
275
|
-
void *p = malloc(size);
|
|
276
|
-
if (!p)
|
|
277
|
-
return NULL;
|
|
278
|
-
#if defined(__linux__) && defined(MADV_HUGEPAGE)
|
|
279
|
-
if (size >= (256u * 1024u)) {
|
|
280
|
-
(void)madvise(p, size, MADV_HUGEPAGE);
|
|
281
|
-
}
|
|
282
|
-
#endif
|
|
283
|
-
return p;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
347
|
static void ip_validate_quality_or_raise(ip_context_t *ctx) {
|
|
287
348
|
if (ctx->quality >= 1 && ctx->quality <= 100)
|
|
288
349
|
return;
|
|
289
350
|
|
|
290
351
|
int quality = ctx->quality;
|
|
291
|
-
ip_context_free(ctx);
|
|
292
352
|
rb_raise(rb_eImagePackInvalidArgumentError, "quality must be Integer 1..100, got: %d", quality);
|
|
293
353
|
}
|
|
294
354
|
|
|
@@ -299,11 +359,71 @@ static void ip_validate_min_ssim_or_raise(ip_context_t *ctx) {
|
|
|
299
359
|
return;
|
|
300
360
|
|
|
301
361
|
double min_ssim = ctx->min_ssim;
|
|
302
|
-
ip_context_free(ctx);
|
|
303
362
|
rb_raise(rb_eImagePackInvalidArgumentError,
|
|
304
363
|
"min_ssim must be Numeric > 0.0 and <= 1.0, got: %.17g", min_ssim);
|
|
305
364
|
}
|
|
306
365
|
|
|
366
|
+
static int ip_value_negative(VALUE value) {
|
|
367
|
+
return RTEST(rb_funcall(value, rb_intern("<"), 1, INT2FIX(0)));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
static char *ip_strdup(const char *source) {
|
|
371
|
+
size_t len = strlen(source) + 1;
|
|
372
|
+
char *copy = (char *)malloc(len);
|
|
373
|
+
if (!copy)
|
|
374
|
+
return NULL;
|
|
375
|
+
memcpy(copy, source, len);
|
|
376
|
+
return copy;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#if defined(_WIN32)
|
|
380
|
+
static int ip_replace_file(const char *tmp_path, const char *output_path) {
|
|
381
|
+
return MoveFileExA(tmp_path, output_path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)
|
|
382
|
+
? 0
|
|
383
|
+
: -1;
|
|
384
|
+
}
|
|
385
|
+
#else
|
|
386
|
+
static int ip_replace_file(const char *tmp_path, const char *output_path) {
|
|
387
|
+
return rename(tmp_path, output_path);
|
|
388
|
+
}
|
|
389
|
+
#endif
|
|
390
|
+
|
|
391
|
+
static int ip_file_seek_end(FILE *fp) {
|
|
392
|
+
#if defined(_WIN32)
|
|
393
|
+
return _fseeki64(fp, 0, SEEK_END);
|
|
394
|
+
#else
|
|
395
|
+
return fseeko(fp, 0, SEEK_END);
|
|
396
|
+
#endif
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
static int ip_file_rewind(FILE *fp) {
|
|
400
|
+
#if defined(_WIN32)
|
|
401
|
+
return _fseeki64(fp, 0, SEEK_SET);
|
|
402
|
+
#else
|
|
403
|
+
return fseeko(fp, 0, SEEK_SET);
|
|
404
|
+
#endif
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
static int ip_file_tell(FILE *fp, size_t *out) {
|
|
408
|
+
#if defined(_WIN32)
|
|
409
|
+
__int64 pos = _ftelli64(fp);
|
|
410
|
+
if (pos < 0)
|
|
411
|
+
return 0;
|
|
412
|
+
if ((unsigned long long)pos > (unsigned long long)SIZE_MAX)
|
|
413
|
+
return 0;
|
|
414
|
+
*out = (size_t)pos;
|
|
415
|
+
return 1;
|
|
416
|
+
#else
|
|
417
|
+
off_t pos = ftello(fp);
|
|
418
|
+
if (pos < 0)
|
|
419
|
+
return 0;
|
|
420
|
+
if ((uintmax_t)pos > (uintmax_t)SIZE_MAX)
|
|
421
|
+
return 0;
|
|
422
|
+
*out = (size_t)pos;
|
|
423
|
+
return 1;
|
|
424
|
+
#endif
|
|
425
|
+
}
|
|
426
|
+
|
|
307
427
|
static int ip_bool_value(VALUE value) {
|
|
308
428
|
if (NIL_P(value) || value == Qfalse)
|
|
309
429
|
return 0;
|
|
@@ -330,6 +450,7 @@ static ip_context_t *ip_context_new(void) {
|
|
|
330
450
|
ctx->max_height = 30000;
|
|
331
451
|
ctx->max_output_size = 256 * 1024 * 1024;
|
|
332
452
|
ctx->max_input_size = 256 * 1024 * 1024;
|
|
453
|
+
ctx->source_orientation = 1;
|
|
333
454
|
atomic_init(&ctx->cancelled, 0);
|
|
334
455
|
return ctx;
|
|
335
456
|
}
|
|
@@ -341,7 +462,8 @@ static void ip_context_free(ip_context_t *ctx) {
|
|
|
341
462
|
free(ctx->owned_input_data);
|
|
342
463
|
free(ctx->owned_pixel_data);
|
|
343
464
|
free(ctx->output_path);
|
|
344
|
-
free(ctx->
|
|
465
|
+
free(ctx->transient_jpeg_buf);
|
|
466
|
+
free(ctx->transient_decode_buf);
|
|
345
467
|
|
|
346
468
|
if (ctx->preserved_markers) {
|
|
347
469
|
for (size_t i = 0; i < ctx->preserved_marker_count; i++) {
|
|
@@ -374,46 +496,87 @@ static ID symbol_id(VALUE sym, const char *kind) {
|
|
|
374
496
|
return SYM2ID(sym);
|
|
375
497
|
}
|
|
376
498
|
|
|
499
|
+
typedef struct {
|
|
500
|
+
const ID *id;
|
|
501
|
+
int value;
|
|
502
|
+
} ip_symbol_entry;
|
|
503
|
+
|
|
504
|
+
static int ip_map_symbol(VALUE sym, const char *kind, const ip_symbol_entry *table) {
|
|
505
|
+
ID id = symbol_id(sym, kind);
|
|
506
|
+
for (; table->id != NULL; table++) {
|
|
507
|
+
if (id == *table->id)
|
|
508
|
+
return table->value;
|
|
509
|
+
}
|
|
510
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "unknown %s", kind);
|
|
511
|
+
}
|
|
512
|
+
|
|
377
513
|
static ip_algo_t ip_parse_algo(VALUE sym) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (id == id_mozjpeg)
|
|
382
|
-
return IP_ALGO_MOZJPEG;
|
|
383
|
-
rb_raise(rb_eImagePackInvalidArgumentError, "unknown algo");
|
|
514
|
+
static const ip_symbol_entry table[] = {
|
|
515
|
+
{&id_jpeg_turbo, IP_ALGO_JPEG_TURBO}, {&id_mozjpeg, IP_ALGO_MOZJPEG}, {NULL, 0}};
|
|
516
|
+
return (ip_algo_t)ip_map_symbol(sym, "algo", table);
|
|
384
517
|
}
|
|
385
518
|
|
|
386
519
|
static ip_execution_t ip_parse_execution(VALUE sym) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return IP_EXEC_OFFLOAD;
|
|
394
|
-
if (id == id_auto)
|
|
395
|
-
return IP_EXEC_AUTO;
|
|
396
|
-
rb_raise(rb_eImagePackInvalidArgumentError, "unknown execution");
|
|
520
|
+
static const ip_symbol_entry table[] = {{&id_direct, IP_EXEC_DIRECT},
|
|
521
|
+
{&id_nogvl, IP_EXEC_NOGVL},
|
|
522
|
+
{&id_offload, IP_EXEC_OFFLOAD},
|
|
523
|
+
{&id_auto, IP_EXEC_AUTO},
|
|
524
|
+
{NULL, 0}};
|
|
525
|
+
return (ip_execution_t)ip_map_symbol(sym, "execution", table);
|
|
397
526
|
}
|
|
398
527
|
|
|
399
528
|
static ip_input_kind_t ip_parse_input_kind(VALUE sym) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (id == id_io_buffer)
|
|
406
|
-
return IP_INPUT_IO_BUFFER;
|
|
407
|
-
rb_raise(rb_eImagePackInvalidArgumentError, "unknown input kind");
|
|
529
|
+
static const ip_symbol_entry table[] = {{&id_bytes, IP_INPUT_BYTES},
|
|
530
|
+
{&id_path, IP_INPUT_PATH},
|
|
531
|
+
{&id_io_buffer, IP_INPUT_IO_BUFFER},
|
|
532
|
+
{NULL, 0}};
|
|
533
|
+
return (ip_input_kind_t)ip_map_symbol(sym, "input kind", table);
|
|
408
534
|
}
|
|
409
535
|
|
|
410
536
|
static ip_output_kind_t ip_parse_output_kind(VALUE sym) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
537
|
+
static const ip_symbol_entry table[] = {
|
|
538
|
+
{&id_return_string, IP_OUTPUT_RETURN_STRING}, {&id_path, IP_OUTPUT_PATH}, {NULL, 0}};
|
|
539
|
+
return (ip_output_kind_t)ip_map_symbol(sym, "output kind", table);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
static VALUE ip_sym(const char *name) {
|
|
543
|
+
return ID2SYM(rb_intern(name));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
static struct jpeg_error_mgr *ip_use_error(ip_jpeg_error_mgr *jerr, ip_context_t *ctx,
|
|
547
|
+
void (*handler)(j_common_ptr)) {
|
|
548
|
+
struct jpeg_error_mgr *base = jpeg_std_error(&jerr->pub);
|
|
549
|
+
jerr->pub.error_exit = handler;
|
|
550
|
+
jerr->ctx = ctx;
|
|
551
|
+
return base;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static void ip_apply_fast_decode(struct jpeg_decompress_struct *cinfo) {
|
|
555
|
+
cinfo->dct_method = IP_FAST_DCT;
|
|
556
|
+
cinfo->do_fancy_upsampling = FALSE;
|
|
557
|
+
cinfo->do_block_smoothing = FALSE;
|
|
558
|
+
cinfo->quantize_colors = FALSE;
|
|
559
|
+
cinfo->two_pass_quantize = FALSE;
|
|
560
|
+
cinfo->dither_mode = JDITHER_NONE;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
static void ip_disable_mozjpeg_trellis(struct jpeg_compress_struct *cinfo) {
|
|
564
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
|
|
565
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
|
|
566
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
|
|
567
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
|
|
568
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
|
|
569
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
static int ip_check_max_dimension_limits(ip_context_t *ctx) {
|
|
573
|
+
if (ctx->max_width > 0 && ctx->width > ctx->max_width)
|
|
574
|
+
IP_FAIL(ctx, IP_ERR_LIMIT, "image width exceeds max_width");
|
|
575
|
+
if (ctx->max_height > 0 && ctx->height > ctx->max_height)
|
|
576
|
+
IP_FAIL(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
|
|
577
|
+
if (ctx->max_pixels > 0 && (uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels)
|
|
578
|
+
IP_FAIL(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
|
|
579
|
+
return 1;
|
|
417
580
|
}
|
|
418
581
|
|
|
419
582
|
static VALUE pathname_to_s(VALUE object) {
|
|
@@ -431,36 +594,41 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
|
|
|
431
594
|
return 0;
|
|
432
595
|
}
|
|
433
596
|
|
|
434
|
-
if (
|
|
597
|
+
if (ip_file_seek_end(fp) != 0) {
|
|
435
598
|
fclose(fp);
|
|
436
599
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to seek input path");
|
|
437
600
|
return 0;
|
|
438
601
|
}
|
|
439
602
|
|
|
440
|
-
|
|
441
|
-
if (size
|
|
603
|
+
size_t size = 0;
|
|
604
|
+
if (!ip_file_tell(fp, &size)) {
|
|
442
605
|
fclose(fp);
|
|
443
606
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to determine input size");
|
|
444
607
|
return 0;
|
|
445
608
|
}
|
|
446
|
-
rewind(fp);
|
|
447
609
|
|
|
448
|
-
if (
|
|
610
|
+
if (ip_file_rewind(fp) != 0) {
|
|
611
|
+
fclose(fp);
|
|
612
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to rewind input path");
|
|
613
|
+
return 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (ctx->max_input_size > 0 && size > ctx->max_input_size) {
|
|
449
617
|
fclose(fp);
|
|
450
618
|
ip_context_set_error(ctx, IP_ERR_LIMIT, "input file exceeds max_input_size");
|
|
451
619
|
return 0;
|
|
452
620
|
}
|
|
453
621
|
|
|
454
|
-
unsigned char *data = (unsigned char *)malloc(
|
|
622
|
+
unsigned char *data = (unsigned char *)malloc(size);
|
|
455
623
|
if (!data && size > 0) {
|
|
456
624
|
fclose(fp);
|
|
457
625
|
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate input file buffer");
|
|
458
626
|
return 0;
|
|
459
627
|
}
|
|
460
628
|
|
|
461
|
-
size_t read_size = fread(data, 1,
|
|
629
|
+
size_t read_size = fread(data, 1, size, fp);
|
|
462
630
|
fclose(fp);
|
|
463
|
-
if (read_size !=
|
|
631
|
+
if (read_size != size) {
|
|
464
632
|
free(data);
|
|
465
633
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "failed to read input path");
|
|
466
634
|
return 0;
|
|
@@ -472,9 +640,36 @@ static int read_file_to_owned_buffer(ip_context_t *ctx, const char *path) {
|
|
|
472
640
|
return 1;
|
|
473
641
|
}
|
|
474
642
|
|
|
475
|
-
static
|
|
476
|
-
VALUE
|
|
477
|
-
|
|
643
|
+
static size_t io_buffer_size_or_raise(VALUE buffer) {
|
|
644
|
+
VALUE size_value = rb_funcall(buffer, rb_intern("size"), 0);
|
|
645
|
+
if (!RB_INTEGER_TYPE_P(size_value) || ip_value_negative(size_value))
|
|
646
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "IO::Buffer#size must return Integer >= 0");
|
|
647
|
+
return NUM2SIZET(size_value);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
static VALUE io_buffer_to_string_slice(VALUE buffer, size_t len) {
|
|
651
|
+
return rb_funcall(buffer, rb_intern("get_string"), 2, LONG2NUM(0), SIZET2NUM(len));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
static int ip_copy_string_to_owned_input(ip_context_t *ctx, VALUE input) {
|
|
655
|
+
Check_Type(input, T_STRING);
|
|
656
|
+
size_t len = (size_t)RSTRING_LEN(input);
|
|
657
|
+
if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
|
|
658
|
+
ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
|
|
659
|
+
return 0;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
unsigned char *copy = (unsigned char *)malloc(len);
|
|
663
|
+
if (!copy && len > 0) {
|
|
664
|
+
ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy binary String input");
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
if (len > 0)
|
|
668
|
+
memcpy(copy, RSTRING_PTR(input), len);
|
|
669
|
+
ctx->owned_input_data = copy;
|
|
670
|
+
ctx->input_data = copy;
|
|
671
|
+
ctx->input_size = len;
|
|
672
|
+
return 1;
|
|
478
673
|
}
|
|
479
674
|
|
|
480
675
|
static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
|
|
@@ -485,27 +680,32 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
|
|
|
485
680
|
ip_context_set_error(ctx, IP_ERR_LIMIT, "input bytes exceed max_input_size");
|
|
486
681
|
return 0;
|
|
487
682
|
}
|
|
488
|
-
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
|
|
683
|
+
|
|
684
|
+
if (ctx->requested_execution == IP_EXEC_DIRECT ||
|
|
685
|
+
ctx->requested_execution == IP_EXEC_AUTO) {
|
|
686
|
+
ctx->input_data = (const unsigned char *)RSTRING_PTR(input);
|
|
687
|
+
ctx->input_size = len;
|
|
688
|
+
return 1;
|
|
492
689
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
ctx->owned_input_data = copy;
|
|
496
|
-
ctx->input_data = copy;
|
|
497
|
-
ctx->input_size = len;
|
|
498
|
-
return 1;
|
|
690
|
+
|
|
691
|
+
return ip_copy_string_to_owned_input(ctx, input);
|
|
499
692
|
}
|
|
500
693
|
|
|
501
694
|
if (kind == IP_INPUT_IO_BUFFER) {
|
|
502
|
-
|
|
503
|
-
StringValue(str);
|
|
504
|
-
size_t len = (size_t)RSTRING_LEN(str);
|
|
695
|
+
size_t len = io_buffer_size_or_raise(input);
|
|
505
696
|
if (ctx->max_input_size > 0 && len > ctx->max_input_size) {
|
|
506
697
|
ip_context_set_error(ctx, IP_ERR_LIMIT, "input IO::Buffer exceeds max_input_size");
|
|
507
698
|
return 0;
|
|
508
699
|
}
|
|
700
|
+
|
|
701
|
+
VALUE str = io_buffer_to_string_slice(input, len);
|
|
702
|
+
StringValue(str);
|
|
703
|
+
if ((size_t)RSTRING_LEN(str) != len) {
|
|
704
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
|
|
705
|
+
"IO::Buffer#get_string returned unexpected size");
|
|
706
|
+
return 0;
|
|
707
|
+
}
|
|
708
|
+
|
|
509
709
|
unsigned char *copy = (unsigned char *)malloc(len);
|
|
510
710
|
if (!copy && len > 0) {
|
|
511
711
|
ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy IO::Buffer input");
|
|
@@ -524,15 +724,31 @@ static int ip_prepare_input_bytes(ip_context_t *ctx, VALUE input, ip_input_kind_
|
|
|
524
724
|
return read_file_to_owned_buffer(ctx, StringValueCStr(path_value));
|
|
525
725
|
}
|
|
526
726
|
|
|
527
|
-
static int
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
727
|
+
static int ip_ensure_owned_input_for_async(ip_context_t *ctx, VALUE input, ip_input_kind_t kind) {
|
|
728
|
+
if (ctx->resolved_execution == IP_EXEC_DIRECT)
|
|
729
|
+
return 1;
|
|
730
|
+
if (kind != IP_INPUT_BYTES || ctx->owned_input_data)
|
|
731
|
+
return 1;
|
|
732
|
+
return ip_copy_string_to_owned_input(ctx, input);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
static int ip_copy_string_to_owned_pixels(ip_context_t *ctx, VALUE str, size_t expected) {
|
|
736
|
+
unsigned char *copy = (unsigned char *)malloc(expected);
|
|
737
|
+
if (!copy && expected > 0) {
|
|
738
|
+
ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy pixel buffer");
|
|
739
|
+
return 0;
|
|
533
740
|
}
|
|
534
741
|
|
|
535
|
-
|
|
742
|
+
if (expected > 0)
|
|
743
|
+
memcpy(copy, RSTRING_PTR(str), expected);
|
|
744
|
+
ctx->owned_pixel_data = copy;
|
|
745
|
+
ctx->pixel_data = copy;
|
|
746
|
+
ctx->pixel_size = expected;
|
|
747
|
+
return 1;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int height, int channels,
|
|
751
|
+
int exact_size) {
|
|
536
752
|
size_t expected = 0;
|
|
537
753
|
if (!ip_checked_image_size(width, height, channels, &expected)) {
|
|
538
754
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
|
|
@@ -540,23 +756,49 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
|
|
|
540
756
|
return 0;
|
|
541
757
|
}
|
|
542
758
|
|
|
543
|
-
|
|
759
|
+
VALUE str = Qnil;
|
|
760
|
+
size_t actual = 0;
|
|
761
|
+
int buffer_is_string = RB_TYPE_P(buffer, T_STRING);
|
|
762
|
+
|
|
763
|
+
if (buffer_is_string) {
|
|
764
|
+
str = buffer;
|
|
765
|
+
actual = (size_t)RSTRING_LEN(str);
|
|
766
|
+
} else {
|
|
767
|
+
actual = io_buffer_size_or_raise(buffer);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (actual < expected) {
|
|
544
771
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
|
|
545
772
|
"pixel buffer is smaller than width * height * channels");
|
|
546
773
|
return 0;
|
|
547
774
|
}
|
|
548
775
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
776
|
+
if (exact_size && actual != expected) {
|
|
777
|
+
ip_context_set_error(
|
|
778
|
+
ctx, IP_ERR_INVALID_ARGUMENT,
|
|
779
|
+
"pixel buffer size must equal width * height * channels when exact_size is true");
|
|
552
780
|
return 0;
|
|
553
781
|
}
|
|
554
782
|
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
783
|
+
if (!buffer_is_string) {
|
|
784
|
+
str = io_buffer_to_string_slice(buffer, expected);
|
|
785
|
+
StringValue(str);
|
|
786
|
+
if ((size_t)RSTRING_LEN(str) != expected) {
|
|
787
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT,
|
|
788
|
+
"IO::Buffer#get_string returned unexpected pixel size");
|
|
789
|
+
return 0;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if ((ctx->requested_execution == IP_EXEC_DIRECT || ctx->requested_execution == IP_EXEC_AUTO) &&
|
|
794
|
+
buffer_is_string) {
|
|
795
|
+
ctx->pixel_data = (const unsigned char *)RSTRING_PTR(str);
|
|
796
|
+
ctx->pixel_size = expected;
|
|
797
|
+
} else {
|
|
798
|
+
if (!ip_copy_string_to_owned_pixels(ctx, str, expected))
|
|
799
|
+
return 0;
|
|
800
|
+
}
|
|
801
|
+
|
|
560
802
|
ctx->width = width;
|
|
561
803
|
ctx->height = height;
|
|
562
804
|
ctx->channels = channels;
|
|
@@ -565,6 +807,14 @@ static int ip_prepare_pixels(ip_context_t *ctx, VALUE buffer, int width, int hei
|
|
|
565
807
|
return 1;
|
|
566
808
|
}
|
|
567
809
|
|
|
810
|
+
static int ip_ensure_owned_pixels_for_async(ip_context_t *ctx, VALUE buffer) {
|
|
811
|
+
if (ctx->resolved_execution == IP_EXEC_DIRECT)
|
|
812
|
+
return 1;
|
|
813
|
+
if (!RB_TYPE_P(buffer, T_STRING) || ctx->owned_pixel_data)
|
|
814
|
+
return 1;
|
|
815
|
+
return ip_copy_string_to_owned_pixels(ctx, buffer, ctx->pixel_size);
|
|
816
|
+
}
|
|
817
|
+
|
|
568
818
|
static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kind_t kind) {
|
|
569
819
|
if (kind == IP_OUTPUT_RETURN_STRING)
|
|
570
820
|
return 1;
|
|
@@ -572,7 +822,7 @@ static int ip_prepare_output_path(ip_context_t *ctx, VALUE output, ip_output_kin
|
|
|
572
822
|
VALUE path_value = pathname_to_s(output);
|
|
573
823
|
StringValue(path_value);
|
|
574
824
|
const char *path = StringValueCStr(path_value);
|
|
575
|
-
ctx->output_path =
|
|
825
|
+
ctx->output_path = ip_strdup(path);
|
|
576
826
|
if (!ctx->output_path) {
|
|
577
827
|
ip_context_set_error(ctx, IP_ERR_OOM, "failed to copy output path");
|
|
578
828
|
return 0;
|
|
@@ -584,31 +834,57 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
|
|
|
584
834
|
ip_raise_for_status(ctx);
|
|
585
835
|
|
|
586
836
|
if (kind == IP_OUTPUT_RETURN_STRING) {
|
|
837
|
+
if (ctx->output_size > (size_t)LONG_MAX)
|
|
838
|
+
rb_raise(rb_eImagePackLimitExceededError, "output is too large for a Ruby String");
|
|
587
839
|
VALUE out = rb_str_new((const char *)ctx->output_data, (long)ctx->output_size);
|
|
588
840
|
rb_enc_associate(out, rb_ascii8bit_encoding());
|
|
589
841
|
return out;
|
|
590
842
|
}
|
|
591
843
|
|
|
592
|
-
|
|
844
|
+
size_t path_len = strlen(ctx->output_path);
|
|
845
|
+
char suffix[64];
|
|
846
|
+
snprintf(suffix, sizeof(suffix), ".tmp.image_pack.%p", (void *)ctx);
|
|
847
|
+
size_t tmp_len = path_len + strlen(suffix) + 1;
|
|
848
|
+
char *tmp_path = (char *)malloc(tmp_len);
|
|
849
|
+
if (!tmp_path)
|
|
850
|
+
rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate temporary output path");
|
|
851
|
+
snprintf(tmp_path, tmp_len, "%s%s", ctx->output_path, suffix);
|
|
852
|
+
|
|
853
|
+
FILE *fp = fopen(tmp_path, "wb");
|
|
593
854
|
if (!fp) {
|
|
855
|
+
free(tmp_path);
|
|
594
856
|
rb_raise(rb_eImagePackInvalidArgumentError, "failed to open output path: %s",
|
|
595
857
|
ctx->output_path);
|
|
596
858
|
}
|
|
597
859
|
|
|
598
860
|
size_t written = fwrite(ctx->output_data, 1, ctx->output_size, fp);
|
|
599
|
-
|
|
600
|
-
|
|
861
|
+
int write_failed = written != ctx->output_size || ferror(fp);
|
|
862
|
+
int close_failed = fclose(fp) != 0;
|
|
863
|
+
if (write_failed || close_failed) {
|
|
864
|
+
remove(tmp_path);
|
|
865
|
+
free(tmp_path);
|
|
601
866
|
rb_raise(rb_eImagePackEncodeError, "failed to write full JPEG output");
|
|
602
867
|
}
|
|
603
868
|
|
|
869
|
+
if (ip_replace_file(tmp_path, ctx->output_path) != 0) {
|
|
870
|
+
remove(tmp_path);
|
|
871
|
+
free(tmp_path);
|
|
872
|
+
rb_raise(rb_eImagePackEncodeError, "failed to move temporary JPEG output into place");
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
free(tmp_path);
|
|
604
876
|
return Qtrue;
|
|
605
877
|
}
|
|
606
878
|
|
|
607
879
|
static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
|
|
608
880
|
unsigned int len) {
|
|
609
881
|
if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
|
|
882
|
+
if (ctx->preserved_marker_capacity > SIZE_MAX / 2)
|
|
883
|
+
return 0;
|
|
610
884
|
size_t new_cap =
|
|
611
885
|
ctx->preserved_marker_capacity == 0 ? 4 : ctx->preserved_marker_capacity * 2;
|
|
886
|
+
if (new_cap > SIZE_MAX / sizeof(*ctx->preserved_markers))
|
|
887
|
+
return 0;
|
|
612
888
|
void *new_buf = realloc(ctx->preserved_markers, new_cap * sizeof(*ctx->preserved_markers));
|
|
613
889
|
if (!new_buf)
|
|
614
890
|
return 0;
|
|
@@ -629,17 +905,171 @@ static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *da
|
|
|
629
905
|
return 1;
|
|
630
906
|
}
|
|
631
907
|
|
|
632
|
-
static
|
|
633
|
-
|
|
908
|
+
static int ip_save_markers_from_decompress(ip_context_t *ctx,
|
|
909
|
+
struct jpeg_decompress_struct *cinfo) {
|
|
634
910
|
jpeg_saved_marker_ptr m;
|
|
635
911
|
for (m = cinfo->marker_list; m != NULL; m = m->next) {
|
|
636
912
|
if (m->marker == (JPEG_APP0 + 0))
|
|
637
913
|
continue;
|
|
638
914
|
|
|
639
915
|
if (!ip_save_marker(ctx, m->marker, m->data, m->data_length)) {
|
|
640
|
-
|
|
916
|
+
ip_context_set_error(ctx, IP_ERR_OOM, "failed to preserve JPEG metadata marker");
|
|
917
|
+
return 0;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return 1;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
static uint16_t ip_exif_u16(const unsigned char *p, int little_endian) {
|
|
924
|
+
if (little_endian)
|
|
925
|
+
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
|
|
926
|
+
return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
static uint32_t ip_exif_u32(const unsigned char *p, int little_endian) {
|
|
930
|
+
if (little_endian)
|
|
931
|
+
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
|
|
932
|
+
((uint32_t)p[3] << 24);
|
|
933
|
+
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
static int ip_parse_exif_orientation(const unsigned char *data, unsigned int len) {
|
|
937
|
+
if (!data || len < 14)
|
|
938
|
+
return 1;
|
|
939
|
+
if (memcmp(data, "Exif\0\0", 6) != 0)
|
|
940
|
+
return 1;
|
|
941
|
+
|
|
942
|
+
const unsigned char *tiff = data + 6;
|
|
943
|
+
size_t tiff_len = (size_t)len - 6;
|
|
944
|
+
if (tiff_len < 8)
|
|
945
|
+
return 1;
|
|
946
|
+
|
|
947
|
+
int little_endian = 0;
|
|
948
|
+
if (tiff[0] == 'I' && tiff[1] == 'I')
|
|
949
|
+
little_endian = 1;
|
|
950
|
+
else if (tiff[0] == 'M' && tiff[1] == 'M')
|
|
951
|
+
little_endian = 0;
|
|
952
|
+
else
|
|
953
|
+
return 1;
|
|
954
|
+
|
|
955
|
+
if (ip_exif_u16(tiff + 2, little_endian) != 42)
|
|
956
|
+
return 1;
|
|
957
|
+
|
|
958
|
+
uint32_t ifd0_offset = ip_exif_u32(tiff + 4, little_endian);
|
|
959
|
+
if (ifd0_offset > tiff_len || tiff_len - ifd0_offset < 2)
|
|
960
|
+
return 1;
|
|
961
|
+
|
|
962
|
+
const unsigned char *ifd = tiff + ifd0_offset;
|
|
963
|
+
size_t ifd_len = tiff_len - ifd0_offset;
|
|
964
|
+
uint16_t entries = ip_exif_u16(ifd, little_endian);
|
|
965
|
+
ifd += 2;
|
|
966
|
+
ifd_len -= 2;
|
|
967
|
+
|
|
968
|
+
for (uint16_t i = 0; i < entries; i++) {
|
|
969
|
+
if (ifd_len < 12)
|
|
970
|
+
return 1;
|
|
971
|
+
|
|
972
|
+
uint16_t tag = ip_exif_u16(ifd, little_endian);
|
|
973
|
+
uint16_t type = ip_exif_u16(ifd + 2, little_endian);
|
|
974
|
+
uint32_t count = ip_exif_u32(ifd + 4, little_endian);
|
|
975
|
+
|
|
976
|
+
if (tag == 0x0112 && type == 3 && count == 1) {
|
|
977
|
+
uint16_t orientation = ip_exif_u16(ifd + 8, little_endian);
|
|
978
|
+
if (orientation >= 1 && orientation <= 8)
|
|
979
|
+
return (int)orientation;
|
|
980
|
+
return 1;
|
|
641
981
|
}
|
|
982
|
+
|
|
983
|
+
ifd += 12;
|
|
984
|
+
ifd_len -= 12;
|
|
642
985
|
}
|
|
986
|
+
|
|
987
|
+
return 1;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
static int ip_read_exif_orientation_from_decompress(struct jpeg_decompress_struct *cinfo) {
|
|
991
|
+
jpeg_saved_marker_ptr m;
|
|
992
|
+
for (m = cinfo->marker_list; m != NULL; m = m->next) {
|
|
993
|
+
if (m->marker == (JPEG_APP0 + 1)) {
|
|
994
|
+
int orientation = ip_parse_exif_orientation(m->data, m->data_length);
|
|
995
|
+
if (orientation >= 2 && orientation <= 8)
|
|
996
|
+
return orientation;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return 1;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
static int ip_transform_pixels_for_orientation(ip_context_t *ctx, unsigned char **pixels,
|
|
1003
|
+
int *width, int *height, int channels) {
|
|
1004
|
+
int orientation = ctx->source_orientation;
|
|
1005
|
+
if (orientation <= 1 || orientation > 8)
|
|
1006
|
+
return 1;
|
|
1007
|
+
|
|
1008
|
+
int src_w = *width;
|
|
1009
|
+
int src_h = *height;
|
|
1010
|
+
int dst_w = (orientation >= 5 && orientation <= 8) ? src_h : src_w;
|
|
1011
|
+
int dst_h = (orientation >= 5 && orientation <= 8) ? src_w : src_h;
|
|
1012
|
+
size_t out_size = 0;
|
|
1013
|
+
if (!ip_checked_image_size(dst_w, dst_h, channels, &out_size)) {
|
|
1014
|
+
ip_context_set_error(ctx, IP_ERR_LIMIT, "oriented image size overflows native size");
|
|
1015
|
+
return 0;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
unsigned char *src = *pixels;
|
|
1019
|
+
unsigned char *dst = (unsigned char *)malloc(out_size);
|
|
1020
|
+
if (!dst && out_size > 0) {
|
|
1021
|
+
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate EXIF-oriented pixel buffer");
|
|
1022
|
+
return 0;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
for (int y = 0; y < src_h; y++) {
|
|
1026
|
+
for (int x = 0; x < src_w; x++) {
|
|
1027
|
+
int dx = x;
|
|
1028
|
+
int dy = y;
|
|
1029
|
+
switch (orientation) {
|
|
1030
|
+
case 2:
|
|
1031
|
+
dx = src_w - 1 - x;
|
|
1032
|
+
dy = y;
|
|
1033
|
+
break;
|
|
1034
|
+
case 3:
|
|
1035
|
+
dx = src_w - 1 - x;
|
|
1036
|
+
dy = src_h - 1 - y;
|
|
1037
|
+
break;
|
|
1038
|
+
case 4:
|
|
1039
|
+
dx = x;
|
|
1040
|
+
dy = src_h - 1 - y;
|
|
1041
|
+
break;
|
|
1042
|
+
case 5:
|
|
1043
|
+
dx = y;
|
|
1044
|
+
dy = x;
|
|
1045
|
+
break;
|
|
1046
|
+
case 6:
|
|
1047
|
+
dx = src_h - 1 - y;
|
|
1048
|
+
dy = x;
|
|
1049
|
+
break;
|
|
1050
|
+
case 7:
|
|
1051
|
+
dx = src_h - 1 - y;
|
|
1052
|
+
dy = src_w - 1 - x;
|
|
1053
|
+
break;
|
|
1054
|
+
case 8:
|
|
1055
|
+
dx = y;
|
|
1056
|
+
dy = src_w - 1 - x;
|
|
1057
|
+
break;
|
|
1058
|
+
default:
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
memcpy(dst + (((size_t)dy * (size_t)dst_w + (size_t)dx) * (size_t)channels),
|
|
1063
|
+
src + (((size_t)y * (size_t)src_w + (size_t)x) * (size_t)channels),
|
|
1064
|
+
(size_t)channels);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
free(src);
|
|
1069
|
+
*pixels = dst;
|
|
1070
|
+
*width = dst_w;
|
|
1071
|
+
*height = dst_h;
|
|
1072
|
+
return 1;
|
|
643
1073
|
}
|
|
644
1074
|
|
|
645
1075
|
static void ip_write_preserved_markers(ip_context_t *ctx, struct jpeg_compress_struct *cinfo) {
|
|
@@ -668,107 +1098,114 @@ static void ip_jpeg_encode_error_exit(j_common_ptr cinfo) {
|
|
|
668
1098
|
longjmp(err->ctx->jmpbuf, 1);
|
|
669
1099
|
}
|
|
670
1100
|
|
|
671
|
-
static
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1101
|
+
static const char *ip_jpeg_color_space_name(int color_space) {
|
|
1102
|
+
switch ((J_COLOR_SPACE)color_space) {
|
|
1103
|
+
case JCS_GRAYSCALE:
|
|
1104
|
+
return "grayscale";
|
|
1105
|
+
case JCS_RGB:
|
|
1106
|
+
return "rgb";
|
|
1107
|
+
case JCS_YCbCr:
|
|
1108
|
+
return "ycbcr";
|
|
1109
|
+
case JCS_CMYK:
|
|
1110
|
+
return "cmyk";
|
|
1111
|
+
case JCS_YCCK:
|
|
1112
|
+
return "ycck";
|
|
1113
|
+
default:
|
|
1114
|
+
return "unknown";
|
|
676
1115
|
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
static int ip_inspect_jpeg_header(ip_context_t *ctx, int allow_cmyk_ycck) {
|
|
1119
|
+
if (!ctx->input_data || ctx->input_size < 2 || ctx->input_data[0] != 0xFF ||
|
|
1120
|
+
ctx->input_data[1] != 0xD8)
|
|
1121
|
+
IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "input is not a JPEG image");
|
|
677
1122
|
|
|
678
1123
|
struct jpeg_decompress_struct cinfo;
|
|
679
1124
|
ip_jpeg_error_mgr jerr;
|
|
680
1125
|
memset(&cinfo, 0, sizeof(cinfo));
|
|
681
1126
|
memset(&jerr, 0, sizeof(jerr));
|
|
682
|
-
|
|
683
|
-
cinfo.err = jpeg_std_error(&jerr.pub);
|
|
684
|
-
jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
|
|
685
|
-
jerr.ctx = ctx;
|
|
1127
|
+
cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
|
|
686
1128
|
|
|
687
1129
|
ctx->jmp_armed = 1;
|
|
688
|
-
if (setjmp(ctx->jmpbuf))
|
|
689
|
-
|
|
690
|
-
jpeg_destroy_decompress(&cinfo);
|
|
691
|
-
return 0;
|
|
692
|
-
}
|
|
1130
|
+
if (setjmp(ctx->jmpbuf))
|
|
1131
|
+
goto fail;
|
|
693
1132
|
|
|
694
1133
|
jpeg_create_decompress(&cinfo);
|
|
695
1134
|
jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
jpeg_destroy_decompress(&cinfo);
|
|
699
|
-
ctx->jmp_armed = 0;
|
|
700
|
-
ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
701
|
-
return 0;
|
|
702
|
-
}
|
|
1135
|
+
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
|
|
1136
|
+
IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
703
1137
|
|
|
704
|
-
if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
709
|
-
"CMYK/YCCK JPEG input is not supported in this release");
|
|
710
|
-
return 0;
|
|
711
|
-
}
|
|
1138
|
+
if (!allow_cmyk_ycck && (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
|
|
1139
|
+
cinfo.jpeg_color_space == JCS_YCCK))
|
|
1140
|
+
IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
|
|
1141
|
+
"CMYK/YCCK JPEG input is not supported for pixel recompression");
|
|
712
1142
|
|
|
713
1143
|
ctx->width = (int)cinfo.image_width;
|
|
714
1144
|
ctx->height = (int)cinfo.image_height;
|
|
715
1145
|
ctx->channels = cinfo.num_components;
|
|
716
|
-
|
|
717
|
-
ctx->channels = 3;
|
|
718
|
-
}
|
|
719
|
-
ctx->bit_depth = 8;
|
|
720
|
-
ctx->decoded_bytes = (size_t)ctx->width * (size_t)ctx->height * (size_t)ctx->channels;
|
|
1146
|
+
ctx->jpeg_color_space = (int)cinfo.jpeg_color_space;
|
|
721
1147
|
|
|
722
1148
|
jpeg_destroy_decompress(&cinfo);
|
|
723
1149
|
ctx->jmp_armed = 0;
|
|
724
1150
|
|
|
725
|
-
if (ctx->width <= 0 || ctx->height <= 0)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1151
|
+
if (ctx->width <= 0 || ctx->height <= 0)
|
|
1152
|
+
IP_FAIL(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG dimensions");
|
|
1153
|
+
if (ctx->channels != 1 && ctx->channels != 3 && ctx->channels != 4)
|
|
1154
|
+
IP_FAIL(ctx, IP_ERR_UNSUPPORTED, "JPEG component count is not supported");
|
|
729
1155
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
734
|
-
if (ctx->height > ctx->max_height) {
|
|
735
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
|
|
736
|
-
return 0;
|
|
737
|
-
}
|
|
738
|
-
if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
|
|
739
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
|
|
740
|
-
return 0;
|
|
741
|
-
}
|
|
1156
|
+
ctx->bit_depth = 8;
|
|
1157
|
+
if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
|
|
1158
|
+
IP_FAIL(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
|
|
742
1159
|
|
|
743
|
-
return
|
|
1160
|
+
return ip_check_max_dimension_limits(ctx);
|
|
1161
|
+
|
|
1162
|
+
fail:
|
|
1163
|
+
jpeg_destroy_decompress(&cinfo);
|
|
1164
|
+
ctx->jmp_armed = 0;
|
|
1165
|
+
return 0;
|
|
744
1166
|
}
|
|
745
1167
|
|
|
746
|
-
static VALUE
|
|
747
|
-
(
|
|
1168
|
+
static VALUE ip_inspect_image_entry_body(VALUE ptr) {
|
|
1169
|
+
ip_inspect_call_t *call = (ip_inspect_call_t *)ptr;
|
|
748
1170
|
ip_context_t *ctx = ip_context_new();
|
|
749
1171
|
if (!ctx)
|
|
750
1172
|
rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
|
|
1173
|
+
call->ctx = ctx;
|
|
751
1174
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
rb_raise(exception, "%s", message[0] ? message : "failed to inspect JPEG image");
|
|
1175
|
+
apply_configuration(call->self, ctx);
|
|
1176
|
+
|
|
1177
|
+
if (!ip_prepare_input_bytes(ctx, call->input, ip_parse_input_kind(call->input_kind)) ||
|
|
1178
|
+
!ip_inspect_jpeg_header(ctx, 1)) {
|
|
1179
|
+
ip_raise_for_status(ctx);
|
|
1180
|
+
rb_raise(rb_eImagePackInvalidImageError, "failed to inspect JPEG image");
|
|
759
1181
|
}
|
|
760
1182
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1183
|
+
int width = ctx->width;
|
|
1184
|
+
int height = ctx->height;
|
|
1185
|
+
int channels = ctx->channels;
|
|
1186
|
+
int bit_depth = ctx->bit_depth;
|
|
1187
|
+
int color_space = ctx->jpeg_color_space;
|
|
1188
|
+
size_t decoded_bytes = ctx->decoded_bytes;
|
|
1189
|
+
|
|
768
1190
|
ip_context_free(ctx);
|
|
1191
|
+
call->ctx = NULL;
|
|
1192
|
+
|
|
1193
|
+
VALUE hash = rb_hash_new();
|
|
1194
|
+
rb_hash_aset(hash, ip_sym("format"), ip_sym("jpeg"));
|
|
1195
|
+
rb_hash_aset(hash, ip_sym("width"), INT2NUM(width));
|
|
1196
|
+
rb_hash_aset(hash, ip_sym("height"), INT2NUM(height));
|
|
1197
|
+
rb_hash_aset(hash, ip_sym("channels"), INT2NUM(channels));
|
|
1198
|
+
rb_hash_aset(hash, ip_sym("bit_depth"), INT2NUM(bit_depth));
|
|
1199
|
+
rb_hash_aset(hash, ip_sym("color_space"), ip_sym(ip_jpeg_color_space_name(color_space)));
|
|
1200
|
+
rb_hash_aset(hash, ip_sym("decoded_bytes"), SIZET2NUM(decoded_bytes));
|
|
769
1201
|
return hash;
|
|
770
1202
|
}
|
|
771
1203
|
|
|
1204
|
+
static VALUE ip_inspect_image_entry(VALUE self, VALUE input, VALUE input_kind) {
|
|
1205
|
+
ip_inspect_call_t call = {self, input, input_kind, NULL};
|
|
1206
|
+
return rb_ensure(ip_inspect_image_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
772
1209
|
static J_COLOR_SPACE color_space_for_channels(int channels) {
|
|
773
1210
|
return channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
|
|
774
1211
|
}
|
|
@@ -786,103 +1223,57 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
|
|
|
786
1223
|
if (mozjpeg_size_mode) {
|
|
787
1224
|
cinfo->optimize_coding = TRUE;
|
|
788
1225
|
|
|
789
|
-
if (
|
|
790
|
-
|
|
791
|
-
jpeg_c_set_bool_param(cinfo,
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
jpeg_c_set_bool_param(cinfo,
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
cinfo->optimize_coding = FALSE;
|
|
802
|
-
#if defined(IMAGE_PACK_HAS_SIMD)
|
|
803
|
-
cinfo->dct_method = JDCT_ISLOW;
|
|
804
|
-
#else
|
|
805
|
-
cinfo->dct_method = JDCT_FASTEST;
|
|
806
|
-
#endif
|
|
807
|
-
cinfo->smoothing_factor = 0;
|
|
808
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
|
|
809
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT, FALSE);
|
|
810
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_QUANT_DC, FALSE);
|
|
811
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, FALSE);
|
|
812
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, FALSE);
|
|
813
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_Q_OPT, FALSE);
|
|
814
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OVERSHOOT_DERINGING, FALSE);
|
|
815
|
-
|
|
816
|
-
if (progressive_requested) {
|
|
817
|
-
jpeg_simple_progression(cinfo);
|
|
818
|
-
cinfo->optimize_coding = TRUE;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
static int prepare_encode_row(ip_context_t *ctx, JDIMENSION y, JSAMPROW *row) {
|
|
823
|
-
if (ctx->channels == 4) {
|
|
824
|
-
size_t rgb_row_size = 0;
|
|
825
|
-
if (!ip_checked_mul_size((size_t)ctx->width, 3, &rgb_row_size)) {
|
|
826
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "RGBA scratch row size overflow");
|
|
827
|
-
return 0;
|
|
1226
|
+
if (progressive_requested) {
|
|
1227
|
+
jpeg_simple_progression(cinfo);
|
|
1228
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, TRUE);
|
|
1229
|
+
} else {
|
|
1230
|
+
cinfo->scan_info = NULL;
|
|
1231
|
+
cinfo->num_scans = 0;
|
|
1232
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
|
|
828
1233
|
}
|
|
829
1234
|
|
|
830
|
-
if (
|
|
831
|
-
|
|
832
|
-
if (!new_row) {
|
|
833
|
-
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate RGBA scratch row");
|
|
834
|
-
return 0;
|
|
835
|
-
}
|
|
836
|
-
ctx->scratch_row = new_row;
|
|
837
|
-
ctx->scratch_row_size = rgb_row_size;
|
|
838
|
-
}
|
|
1235
|
+
if (!mozjpeg_trellis_enabled)
|
|
1236
|
+
ip_disable_mozjpeg_trellis(cinfo);
|
|
839
1237
|
|
|
840
|
-
|
|
841
|
-
ctx->pixel_data + ((size_t)y * (size_t)ctx->width * 4);
|
|
842
|
-
unsigned char *IP_RESTRICT dst = ctx->scratch_row;
|
|
843
|
-
const int w = ctx->width;
|
|
844
|
-
for (int x = 0; x < w; x++) {
|
|
845
|
-
dst[x * 3 + 0] = src[x * 4 + 0];
|
|
846
|
-
dst[x * 3 + 1] = src[x * 4 + 1];
|
|
847
|
-
dst[x * 3 + 2] = src[x * 4 + 2];
|
|
848
|
-
}
|
|
849
|
-
*row = ctx->scratch_row;
|
|
850
|
-
return 1;
|
|
1238
|
+
return;
|
|
851
1239
|
}
|
|
852
1240
|
|
|
853
|
-
|
|
854
|
-
|
|
1241
|
+
cinfo->optimize_coding = FALSE;
|
|
1242
|
+
cinfo->dct_method = IP_FAST_DCT;
|
|
1243
|
+
cinfo->smoothing_factor = 0;
|
|
1244
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, FALSE);
|
|
1245
|
+
ip_disable_mozjpeg_trellis(cinfo);
|
|
1246
|
+
|
|
1247
|
+
if (progressive_requested) {
|
|
1248
|
+
jpeg_simple_progression(cinfo);
|
|
1249
|
+
cinfo->optimize_coding = TRUE;
|
|
1250
|
+
}
|
|
855
1251
|
}
|
|
856
1252
|
|
|
857
1253
|
static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
|
|
858
1254
|
struct jpeg_compress_struct cinfo;
|
|
859
1255
|
ip_jpeg_error_mgr jerr;
|
|
1256
|
+
unsigned long jpeg_size = 0;
|
|
860
1257
|
memset(&cinfo, 0, sizeof(cinfo));
|
|
861
1258
|
memset(&jerr, 0, sizeof(jerr));
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
jerr.pub.error_exit = ip_jpeg_encode_error_exit;
|
|
865
|
-
jerr.ctx = ctx;
|
|
1259
|
+
cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_encode_error_exit);
|
|
1260
|
+
ctx->transient_jpeg_buf = NULL;
|
|
866
1261
|
|
|
867
1262
|
ctx->jmp_armed = 1;
|
|
868
1263
|
if (setjmp(ctx->jmpbuf)) {
|
|
869
|
-
ctx->jmp_armed = 0;
|
|
870
|
-
jpeg_destroy_compress(&cinfo);
|
|
871
1264
|
if (ctx->status == IP_OK)
|
|
872
1265
|
ip_context_set_error(ctx, IP_ERR_ENCODE, "JPEG encode failed");
|
|
873
|
-
|
|
1266
|
+
goto fail;
|
|
874
1267
|
}
|
|
875
1268
|
|
|
876
1269
|
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);
|
|
1270
|
+
jpeg_mem_dest(&cinfo, &ctx->transient_jpeg_buf, &jpeg_size);
|
|
881
1271
|
|
|
882
1272
|
cinfo.image_width = (JDIMENSION)ctx->width;
|
|
883
1273
|
cinfo.image_height = (JDIMENSION)ctx->height;
|
|
884
|
-
cinfo.input_components = ctx->channels
|
|
885
|
-
cinfo.in_color_space =
|
|
1274
|
+
cinfo.input_components = ctx->channels;
|
|
1275
|
+
cinfo.in_color_space =
|
|
1276
|
+
ctx->channels == 4 ? JCS_EXT_RGBA : color_space_for_channels(ctx->channels);
|
|
886
1277
|
|
|
887
1278
|
configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
|
|
888
1279
|
jpeg_set_defaults(&cinfo);
|
|
@@ -892,63 +1283,46 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
|
|
|
892
1283
|
|
|
893
1284
|
jpeg_start_compress(&cinfo, TRUE);
|
|
894
1285
|
|
|
895
|
-
if (!ctx->strip_metadata)
|
|
1286
|
+
if (!ctx->strip_metadata)
|
|
896
1287
|
ip_write_preserved_markers(ctx, &cinfo);
|
|
897
|
-
}
|
|
898
1288
|
|
|
899
1289
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
900
|
-
if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
|
|
901
|
-
|
|
902
|
-
jpeg_abort_compress(&cinfo);
|
|
903
|
-
jpeg_destroy_compress(&cinfo);
|
|
904
|
-
free(jpeg_buf);
|
|
905
|
-
ctx->jmp_armed = 0;
|
|
906
|
-
return 0;
|
|
907
|
-
}
|
|
908
|
-
|
|
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
|
-
}
|
|
1290
|
+
if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
|
|
1291
|
+
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
|
|
922
1292
|
|
|
923
1293
|
JSAMPROW rows[16];
|
|
924
|
-
JDIMENSION
|
|
1294
|
+
JDIMENSION start_scanline = cinfo.next_scanline;
|
|
1295
|
+
JDIMENSION batch = cinfo.image_height - start_scanline;
|
|
925
1296
|
if (batch > 16)
|
|
926
1297
|
batch = 16;
|
|
927
1298
|
|
|
928
|
-
for (JDIMENSION i = 0; i < batch; i++)
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
((size_t)y * (size_t)ctx->width * (size_t)ctx->channels));
|
|
932
|
-
}
|
|
1299
|
+
for (JDIMENSION i = 0; i < batch; i++)
|
|
1300
|
+
rows[i] = (JSAMPROW)(ctx->pixel_data + ((size_t)(start_scanline + i) *
|
|
1301
|
+
(size_t)ctx->width * (size_t)ctx->channels));
|
|
933
1302
|
|
|
934
1303
|
jpeg_write_scanlines(&cinfo, rows, batch);
|
|
935
1304
|
}
|
|
936
1305
|
|
|
937
1306
|
jpeg_finish_compress(&cinfo);
|
|
1307
|
+
|
|
1308
|
+
if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
|
|
1309
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
|
|
1310
|
+
|
|
938
1311
|
jpeg_destroy_compress(&cinfo);
|
|
939
1312
|
ctx->jmp_armed = 0;
|
|
940
1313
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
|
|
944
|
-
return 0;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
ctx->output_data = jpeg_buf;
|
|
1314
|
+
ctx->output_data = ctx->transient_jpeg_buf;
|
|
1315
|
+
ctx->transient_jpeg_buf = NULL;
|
|
948
1316
|
ctx->output_size = (size_t)jpeg_size;
|
|
949
|
-
ctx->output_capacity = (size_t)jpeg_size;
|
|
950
1317
|
ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
|
|
951
1318
|
return 1;
|
|
1319
|
+
|
|
1320
|
+
fail:
|
|
1321
|
+
jpeg_destroy_compress(&cinfo);
|
|
1322
|
+
free(ctx->transient_jpeg_buf);
|
|
1323
|
+
ctx->transient_jpeg_buf = NULL;
|
|
1324
|
+
ctx->jmp_armed = 0;
|
|
1325
|
+
return 0;
|
|
952
1326
|
}
|
|
953
1327
|
|
|
954
1328
|
static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
|
|
@@ -957,120 +1331,84 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
|
|
|
957
1331
|
ip_jpeg_error_mgr jerr;
|
|
958
1332
|
memset(&cinfo, 0, sizeof(cinfo));
|
|
959
1333
|
memset(&jerr, 0, sizeof(jerr));
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
jerr.ctx = ctx;
|
|
1334
|
+
cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
|
|
1335
|
+
ctx->transient_decode_buf = NULL;
|
|
1336
|
+
ctx->source_orientation = 1;
|
|
964
1337
|
|
|
965
1338
|
ctx->jmp_armed = 1;
|
|
966
1339
|
if (setjmp(ctx->jmpbuf)) {
|
|
967
|
-
ctx->jmp_armed = 0;
|
|
968
|
-
jpeg_destroy_decompress(&cinfo);
|
|
969
1340
|
if (ctx->status == IP_OK)
|
|
970
1341
|
ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG decode failed");
|
|
971
|
-
|
|
1342
|
+
goto fail;
|
|
972
1343
|
}
|
|
973
1344
|
|
|
974
1345
|
jpeg_create_decompress(&cinfo);
|
|
975
1346
|
jpeg_mem_src(&cinfo, ctx->input_data, (unsigned long)ctx->input_size);
|
|
976
1347
|
|
|
1348
|
+
jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
|
|
977
1349
|
if (!ctx->strip_metadata) {
|
|
978
1350
|
jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
|
|
979
|
-
for (int app =
|
|
1351
|
+
for (int app = 2; app < 16; app++)
|
|
980
1352
|
jpeg_save_markers(&cinfo, JPEG_APP0 + app, 0xFFFF);
|
|
981
|
-
}
|
|
982
1353
|
}
|
|
983
1354
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
jpeg_destroy_decompress(&cinfo);
|
|
987
|
-
ctx->jmp_armed = 0;
|
|
988
|
-
ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
989
|
-
return 0;
|
|
990
|
-
}
|
|
1355
|
+
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
|
|
1356
|
+
IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
991
1357
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
return 0;
|
|
997
|
-
}
|
|
1358
|
+
ctx->source_orientation = ip_read_exif_orientation_from_decompress(&cinfo);
|
|
1359
|
+
|
|
1360
|
+
if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
|
|
1361
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
|
|
998
1362
|
|
|
999
1363
|
if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
|
|
1000
|
-
cinfo.jpeg_color_space == JCS_YCCK)
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
1004
|
-
"CMYK/YCCK JPEG input is not supported in this release");
|
|
1005
|
-
return 0;
|
|
1006
|
-
}
|
|
1364
|
+
cinfo.jpeg_color_space == JCS_YCCK)
|
|
1365
|
+
IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
|
|
1366
|
+
"CMYK/YCCK JPEG input is not supported in this release");
|
|
1007
1367
|
|
|
1008
1368
|
int ch = cinfo.num_components == 1 ? 1 : 3;
|
|
1009
1369
|
|
|
1010
1370
|
ctx->width = (int)cinfo.image_width;
|
|
1011
1371
|
ctx->height = (int)cinfo.image_height;
|
|
1012
1372
|
ctx->channels = ch;
|
|
1013
|
-
if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
|
|
1017
|
-
return 0;
|
|
1018
|
-
}
|
|
1373
|
+
if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &ctx->decoded_bytes))
|
|
1374
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
|
|
1375
|
+
|
|
1019
1376
|
validate_limits_for_pixels(ctx);
|
|
1020
|
-
if (ctx->status != IP_OK)
|
|
1021
|
-
|
|
1022
|
-
ctx->jmp_armed = 0;
|
|
1023
|
-
return 0;
|
|
1024
|
-
}
|
|
1377
|
+
if (ctx->status != IP_OK)
|
|
1378
|
+
goto fail;
|
|
1025
1379
|
|
|
1026
1380
|
cinfo.out_color_space = ch == 1 ? JCS_GRAYSCALE : JCS_RGB;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
#if defined(IMAGE_PACK_HAS_SIMD)
|
|
1030
|
-
cinfo.dct_method = JDCT_ISLOW;
|
|
1031
|
-
#else
|
|
1032
|
-
cinfo.dct_method = JDCT_FASTEST;
|
|
1033
|
-
#endif
|
|
1034
|
-
cinfo.do_fancy_upsampling = FALSE;
|
|
1035
|
-
cinfo.do_block_smoothing = FALSE;
|
|
1036
|
-
cinfo.quantize_colors = FALSE;
|
|
1037
|
-
cinfo.two_pass_quantize = FALSE;
|
|
1038
|
-
cinfo.dither_mode = JDITHER_NONE;
|
|
1039
|
-
}
|
|
1381
|
+
if (fast_decode_mode)
|
|
1382
|
+
ip_apply_fast_decode(&cinfo);
|
|
1040
1383
|
|
|
1041
1384
|
jpeg_start_decompress(&cinfo);
|
|
1042
1385
|
|
|
1043
|
-
if (!ctx->strip_metadata && !
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1386
|
+
if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &cinfo))
|
|
1387
|
+
goto fail;
|
|
1046
1388
|
|
|
1047
1389
|
size_t row_stride = 0;
|
|
1048
1390
|
size_t size = 0;
|
|
1049
1391
|
if (!ip_checked_mul_size((size_t)cinfo.output_width, (size_t)cinfo.output_components,
|
|
1050
1392
|
&row_stride) ||
|
|
1051
|
-
!ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size))
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
unsigned char *buf = (unsigned char *)ip_malloc_hot(size);
|
|
1058
|
-
if (!buf && size > 0) {
|
|
1059
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1060
|
-
ctx->jmp_armed = 0;
|
|
1061
|
-
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
|
|
1062
|
-
return 0;
|
|
1063
|
-
}
|
|
1393
|
+
!ip_checked_mul_size(row_stride, (size_t)cinfo.output_height, &size))
|
|
1394
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded image buffer size overflow");
|
|
1395
|
+
|
|
1396
|
+
ctx->transient_decode_buf = (unsigned char *)malloc(size);
|
|
1397
|
+
if (!ctx->transient_decode_buf && size > 0)
|
|
1398
|
+
IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
|
|
1064
1399
|
|
|
1065
1400
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
1401
|
+
if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
|
|
1402
|
+
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
|
|
1403
|
+
|
|
1066
1404
|
JSAMPROW rows[16];
|
|
1067
1405
|
JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
|
|
1068
1406
|
if (batch > 16)
|
|
1069
1407
|
batch = 16;
|
|
1070
1408
|
|
|
1071
|
-
for (JDIMENSION i = 0; i < batch; i++)
|
|
1072
|
-
rows[i] =
|
|
1073
|
-
|
|
1409
|
+
for (JDIMENSION i = 0; i < batch; i++)
|
|
1410
|
+
rows[i] =
|
|
1411
|
+
ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * row_stride);
|
|
1074
1412
|
|
|
1075
1413
|
jpeg_read_scanlines(&cinfo, rows, batch);
|
|
1076
1414
|
}
|
|
@@ -1082,11 +1420,37 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
|
|
|
1082
1420
|
jpeg_destroy_decompress(&cinfo);
|
|
1083
1421
|
ctx->jmp_armed = 0;
|
|
1084
1422
|
|
|
1423
|
+
unsigned char *buf = ctx->transient_decode_buf;
|
|
1424
|
+
ctx->transient_decode_buf = NULL;
|
|
1425
|
+
|
|
1426
|
+
if (ctx->strip_metadata && ctx->source_orientation > 1) {
|
|
1427
|
+
if (!ip_transform_pixels_for_orientation(ctx, &buf, &out_width, &out_height, ch)) {
|
|
1428
|
+
free(buf);
|
|
1429
|
+
return 0;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
ctx->width = out_width;
|
|
1433
|
+
ctx->height = out_height;
|
|
1434
|
+
ctx->channels = ch;
|
|
1435
|
+
validate_limits_for_pixels(ctx);
|
|
1436
|
+
if (ctx->status != IP_OK) {
|
|
1437
|
+
free(buf);
|
|
1438
|
+
return 0;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1085
1442
|
*pixels = buf;
|
|
1086
1443
|
*width = out_width;
|
|
1087
1444
|
*height = out_height;
|
|
1088
1445
|
*channels = ch;
|
|
1089
1446
|
return 1;
|
|
1447
|
+
|
|
1448
|
+
fail:
|
|
1449
|
+
jpeg_destroy_decompress(&cinfo);
|
|
1450
|
+
free(ctx->transient_decode_buf);
|
|
1451
|
+
ctx->transient_decode_buf = NULL;
|
|
1452
|
+
ctx->jmp_armed = 0;
|
|
1453
|
+
return 0;
|
|
1090
1454
|
}
|
|
1091
1455
|
|
|
1092
1456
|
static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
|
|
@@ -1121,7 +1485,6 @@ static void ip_clear_output_buffer(ip_context_t *ctx) {
|
|
|
1121
1485
|
|
|
1122
1486
|
ctx->output_data = NULL;
|
|
1123
1487
|
ctx->output_size = 0;
|
|
1124
|
-
ctx->output_capacity = 0;
|
|
1125
1488
|
ctx->output_owner = IP_OUTPUT_OWNER_NONE;
|
|
1126
1489
|
}
|
|
1127
1490
|
|
|
@@ -1131,48 +1494,30 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
|
|
|
1131
1494
|
ip_jpeg_error_mgr jerr;
|
|
1132
1495
|
memset(&cinfo, 0, sizeof(cinfo));
|
|
1133
1496
|
memset(&jerr, 0, sizeof(jerr));
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
jerr.pub.error_exit = ip_jpeg_invalid_error_exit;
|
|
1137
|
-
jerr.ctx = ctx;
|
|
1497
|
+
cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
|
|
1498
|
+
ctx->transient_decode_buf = NULL;
|
|
1138
1499
|
|
|
1139
1500
|
ctx->jmp_armed = 1;
|
|
1140
1501
|
if (setjmp(ctx->jmpbuf)) {
|
|
1141
|
-
ctx->jmp_armed = 0;
|
|
1142
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1143
1502
|
if (ctx->status == IP_OK)
|
|
1144
1503
|
ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "JPEG luma decode failed");
|
|
1145
|
-
|
|
1504
|
+
goto fail;
|
|
1146
1505
|
}
|
|
1147
1506
|
|
|
1148
1507
|
jpeg_create_decompress(&cinfo);
|
|
1149
1508
|
jpeg_mem_src(&cinfo, data, (unsigned long)size);
|
|
1150
1509
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1154
|
-
ctx->jmp_armed = 0;
|
|
1155
|
-
ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
1156
|
-
return 0;
|
|
1157
|
-
}
|
|
1510
|
+
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
|
|
1511
|
+
IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
1158
1512
|
|
|
1159
|
-
if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
|
|
1160
|
-
|
|
1161
|
-
ctx->jmp_armed = 0;
|
|
1162
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
|
|
1163
|
-
return 0;
|
|
1164
|
-
}
|
|
1513
|
+
if (cinfo.image_width > (JDIMENSION)INT_MAX || cinfo.image_height > (JDIMENSION)INT_MAX)
|
|
1514
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
|
|
1165
1515
|
|
|
1166
1516
|
if (cinfo.num_components == 4 || cinfo.jpeg_color_space == JCS_CMYK ||
|
|
1167
|
-
cinfo.jpeg_color_space == JCS_YCCK)
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
1171
|
-
"CMYK/YCCK JPEG input is not supported in this release");
|
|
1172
|
-
return 0;
|
|
1173
|
-
}
|
|
1517
|
+
cinfo.jpeg_color_space == JCS_YCCK)
|
|
1518
|
+
IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
|
|
1519
|
+
"CMYK/YCCK JPEG input is not supported in this release");
|
|
1174
1520
|
|
|
1175
|
-
int out_channels = cinfo.num_components == 1 ? 1 : 3;
|
|
1176
1521
|
int old_width = ctx->width;
|
|
1177
1522
|
int old_height = ctx->height;
|
|
1178
1523
|
int old_channels = ctx->channels;
|
|
@@ -1186,10 +1531,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
|
|
|
1186
1531
|
ctx->height = old_height;
|
|
1187
1532
|
ctx->channels = old_channels;
|
|
1188
1533
|
ctx->decoded_bytes = old_decoded_bytes;
|
|
1189
|
-
|
|
1190
|
-
ctx->jmp_armed = 0;
|
|
1191
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
|
|
1192
|
-
return 0;
|
|
1534
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size overflows native size");
|
|
1193
1535
|
}
|
|
1194
1536
|
validate_limits_for_pixels(ctx);
|
|
1195
1537
|
ctx->width = old_width;
|
|
@@ -1198,110 +1540,58 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
|
|
|
1198
1540
|
size_t luma_size = ctx->decoded_bytes;
|
|
1199
1541
|
ctx->decoded_bytes = old_decoded_bytes;
|
|
1200
1542
|
|
|
1201
|
-
if (ctx->status != IP_OK)
|
|
1202
|
-
|
|
1203
|
-
ctx->jmp_armed = 0;
|
|
1204
|
-
return 0;
|
|
1205
|
-
}
|
|
1543
|
+
if (ctx->status != IP_OK)
|
|
1544
|
+
goto fail;
|
|
1206
1545
|
|
|
1207
|
-
cinfo.out_color_space =
|
|
1208
|
-
|
|
1209
|
-
cinfo.dct_method = JDCT_ISLOW;
|
|
1210
|
-
#else
|
|
1211
|
-
cinfo.dct_method = JDCT_FASTEST;
|
|
1212
|
-
#endif
|
|
1213
|
-
cinfo.do_fancy_upsampling = FALSE;
|
|
1214
|
-
cinfo.do_block_smoothing = FALSE;
|
|
1215
|
-
cinfo.quantize_colors = FALSE;
|
|
1216
|
-
cinfo.two_pass_quantize = FALSE;
|
|
1217
|
-
cinfo.dither_mode = JDITHER_NONE;
|
|
1546
|
+
cinfo.out_color_space = JCS_GRAYSCALE;
|
|
1547
|
+
ip_apply_fast_decode(&cinfo);
|
|
1218
1548
|
|
|
1219
1549
|
jpeg_start_decompress(&cinfo);
|
|
1220
1550
|
|
|
1221
1551
|
size_t luma_stride = (size_t)cinfo.output_width;
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
luma_stride == 0 || luma_size != luma_stride * (size_t)cinfo.output_height) {
|
|
1226
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1227
|
-
ctx->jmp_armed = 0;
|
|
1228
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
|
|
1229
|
-
return 0;
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
unsigned char *buf = (unsigned char *)ip_malloc_hot(luma_size);
|
|
1233
|
-
if (!buf && luma_size > 0) {
|
|
1234
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1235
|
-
ctx->jmp_armed = 0;
|
|
1236
|
-
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
|
|
1237
|
-
return 0;
|
|
1238
|
-
}
|
|
1552
|
+
if (cinfo.output_components != 1 || luma_stride == 0 ||
|
|
1553
|
+
luma_size != luma_stride * (size_t)cinfo.output_height)
|
|
1554
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "decoded luma buffer size mismatch");
|
|
1239
1555
|
|
|
1240
|
-
unsigned char *
|
|
1241
|
-
if (
|
|
1242
|
-
|
|
1243
|
-
if (!ip_checked_mul_size(row_stride, 16, &scratch_size)) {
|
|
1244
|
-
free(buf);
|
|
1245
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1246
|
-
ctx->jmp_armed = 0;
|
|
1247
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "luma decode scratch size overflow");
|
|
1248
|
-
return 0;
|
|
1249
|
-
}
|
|
1250
|
-
scratch = (unsigned char *)ip_malloc_hot(scratch_size);
|
|
1251
|
-
if (!scratch) {
|
|
1252
|
-
free(buf);
|
|
1253
|
-
jpeg_destroy_decompress(&cinfo);
|
|
1254
|
-
ctx->jmp_armed = 0;
|
|
1255
|
-
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma decode scratch row");
|
|
1256
|
-
return 0;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1556
|
+
ctx->transient_decode_buf = (unsigned char *)malloc(luma_size);
|
|
1557
|
+
if (!ctx->transient_decode_buf && luma_size > 0)
|
|
1558
|
+
IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
|
|
1259
1559
|
|
|
1260
1560
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
1561
|
+
if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
|
|
1562
|
+
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
|
|
1563
|
+
|
|
1261
1564
|
JSAMPROW rows[16];
|
|
1262
|
-
JDIMENSION
|
|
1263
|
-
JDIMENSION batch = cinfo.output_height - start_scanline;
|
|
1565
|
+
JDIMENSION batch = cinfo.output_height - cinfo.output_scanline;
|
|
1264
1566
|
if (batch > 16)
|
|
1265
1567
|
batch = 16;
|
|
1266
1568
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1271
|
-
} else {
|
|
1272
|
-
for (JDIMENSION i = 0; i < batch; i++) {
|
|
1273
|
-
rows[i] = scratch + ((size_t)i * row_stride);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1569
|
+
for (JDIMENSION i = 0; i < batch; i++)
|
|
1570
|
+
rows[i] =
|
|
1571
|
+
ctx->transient_decode_buf + ((size_t)(cinfo.output_scanline + i) * luma_stride);
|
|
1276
1572
|
|
|
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
|
-
}
|
|
1573
|
+
jpeg_read_scanlines(&cinfo, rows, batch);
|
|
1291
1574
|
}
|
|
1292
1575
|
|
|
1293
1576
|
int out_width = (int)cinfo.output_width;
|
|
1294
1577
|
int out_height = (int)cinfo.output_height;
|
|
1295
1578
|
|
|
1296
|
-
free(scratch);
|
|
1297
1579
|
jpeg_finish_decompress(&cinfo);
|
|
1298
1580
|
jpeg_destroy_decompress(&cinfo);
|
|
1299
1581
|
ctx->jmp_armed = 0;
|
|
1300
1582
|
|
|
1301
|
-
*luma =
|
|
1583
|
+
*luma = ctx->transient_decode_buf;
|
|
1584
|
+
ctx->transient_decode_buf = NULL;
|
|
1302
1585
|
*width = out_width;
|
|
1303
1586
|
*height = out_height;
|
|
1304
1587
|
return 1;
|
|
1588
|
+
|
|
1589
|
+
fail:
|
|
1590
|
+
jpeg_destroy_decompress(&cinfo);
|
|
1591
|
+
free(ctx->transient_decode_buf);
|
|
1592
|
+
ctx->transient_decode_buf = NULL;
|
|
1593
|
+
ctx->jmp_armed = 0;
|
|
1594
|
+
return 0;
|
|
1305
1595
|
}
|
|
1306
1596
|
|
|
1307
1597
|
static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned char *pixels,
|
|
@@ -1312,7 +1602,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
|
|
|
1312
1602
|
return NULL;
|
|
1313
1603
|
}
|
|
1314
1604
|
|
|
1315
|
-
unsigned char *luma = (unsigned char *)
|
|
1605
|
+
unsigned char *luma = (unsigned char *)malloc(count);
|
|
1316
1606
|
if (!luma) {
|
|
1317
1607
|
ip_context_set_error(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
|
|
1318
1608
|
return NULL;
|
|
@@ -1331,7 +1621,7 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
|
|
|
1331
1621
|
unsigned int r = src[i * 3 + 0];
|
|
1332
1622
|
unsigned int g = src[i * 3 + 1];
|
|
1333
1623
|
unsigned int b = src[i * 3 + 2];
|
|
1334
|
-
dst[i] = (unsigned char)((
|
|
1624
|
+
dst[i] = (unsigned char)((19595u * r + 38470u * g + 7471u * b + 32768u) >> 16);
|
|
1335
1625
|
}
|
|
1336
1626
|
return luma;
|
|
1337
1627
|
}
|
|
@@ -1340,15 +1630,15 @@ static unsigned char *ip_build_luma_buffer(ip_context_t *ctx, const unsigned cha
|
|
|
1340
1630
|
unsigned int r = src[i * 4 + 0];
|
|
1341
1631
|
unsigned int g = src[i * 4 + 1];
|
|
1342
1632
|
unsigned int b = src[i * 4 + 2];
|
|
1343
|
-
dst[i] = (unsigned char)((
|
|
1633
|
+
dst[i] = (unsigned char)((19595u * r + 38470u * g + 7471u * b + 32768u) >> 16);
|
|
1344
1634
|
}
|
|
1345
1635
|
return luma;
|
|
1346
1636
|
}
|
|
1347
1637
|
|
|
1348
1638
|
static double ip_ssim_window_score_double(int32_t n, int32_t sum_a, int32_t sum_b, int32_t sum_a2,
|
|
1349
1639
|
int32_t sum_b2, int32_t sum_ab) {
|
|
1350
|
-
const double c1 = 6.5025;
|
|
1351
|
-
const double c2 = 58.5225;
|
|
1640
|
+
const double c1 = 6.5025;
|
|
1641
|
+
const double c2 = 58.5225;
|
|
1352
1642
|
|
|
1353
1643
|
double inv_n = 1.0 / (double)n;
|
|
1354
1644
|
double mean_a = (double)sum_a * inv_n;
|
|
@@ -1480,7 +1770,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1480
1770
|
int reference_channels = 0;
|
|
1481
1771
|
|
|
1482
1772
|
if (ctx->pixel_data) {
|
|
1483
|
-
reference_pixels = ctx->
|
|
1773
|
+
reference_pixels = (unsigned char *)ctx->pixel_data;
|
|
1484
1774
|
reference_width = ctx->width;
|
|
1485
1775
|
reference_height = ctx->height;
|
|
1486
1776
|
reference_channels = ctx->channels;
|
|
@@ -1502,7 +1792,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1502
1792
|
|
|
1503
1793
|
if (reference_channels == 4) {
|
|
1504
1794
|
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
1505
|
-
"min_ssim is not supported for RGBA input in v0.2.
|
|
1795
|
+
"min_ssim is not supported for RGBA input in v0.2.2");
|
|
1506
1796
|
return 0;
|
|
1507
1797
|
}
|
|
1508
1798
|
|
|
@@ -1542,7 +1832,6 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1542
1832
|
size_t candidate_jpeg_size = ctx->output_size;
|
|
1543
1833
|
ctx->output_data = NULL;
|
|
1544
1834
|
ctx->output_size = 0;
|
|
1545
|
-
ctx->output_capacity = 0;
|
|
1546
1835
|
ctx->output_owner = IP_OUTPUT_OWNER_NONE;
|
|
1547
1836
|
|
|
1548
1837
|
unsigned char *candidate_luma = NULL;
|
|
@@ -1608,11 +1897,130 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1608
1897
|
|
|
1609
1898
|
ctx->output_data = best_jpeg;
|
|
1610
1899
|
ctx->output_size = best_jpeg_size;
|
|
1611
|
-
ctx->output_capacity = best_jpeg_size;
|
|
1612
1900
|
ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
|
|
1613
1901
|
return 1;
|
|
1614
1902
|
}
|
|
1615
1903
|
|
|
1904
|
+
static void ip_setup_marker_saving(struct jpeg_decompress_struct *cinfo, int strip_metadata) {
|
|
1905
|
+
jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xFFFF);
|
|
1906
|
+
if (!strip_metadata) {
|
|
1907
|
+
jpeg_save_markers(cinfo, JPEG_COM, 0xFFFF);
|
|
1908
|
+
for (int app = 2; app < 16; app++) {
|
|
1909
|
+
jpeg_save_markers(cinfo, JPEG_APP0 + app, 0xFFFF);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
static int ip_validate_lossless_optimize_header(ip_context_t *ctx,
|
|
1915
|
+
struct jpeg_decompress_struct *srcinfo) {
|
|
1916
|
+
if (srcinfo->image_width > (JDIMENSION)INT_MAX || srcinfo->image_height > (JDIMENSION)INT_MAX) {
|
|
1917
|
+
ip_context_set_error(ctx, IP_ERR_LIMIT, "JPEG dimensions exceed native int range");
|
|
1918
|
+
return 0;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
ctx->width = (int)srcinfo->image_width;
|
|
1922
|
+
ctx->height = (int)srcinfo->image_height;
|
|
1923
|
+
ctx->channels = srcinfo->num_components;
|
|
1924
|
+
ctx->bit_depth = 8;
|
|
1925
|
+
|
|
1926
|
+
if (ctx->max_width < 0 || ctx->max_height < 0) {
|
|
1927
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
|
|
1928
|
+
return 0;
|
|
1929
|
+
}
|
|
1930
|
+
return ip_check_max_dimension_limits(ctx);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
|
|
1934
|
+
struct jpeg_decompress_struct srcinfo;
|
|
1935
|
+
struct jpeg_compress_struct dstinfo;
|
|
1936
|
+
ip_jpeg_error_mgr srcerr;
|
|
1937
|
+
ip_jpeg_error_mgr dsterr;
|
|
1938
|
+
jvirt_barray_ptr *coef_arrays = NULL;
|
|
1939
|
+
unsigned long jpeg_size = 0;
|
|
1940
|
+
|
|
1941
|
+
memset(&srcinfo, 0, sizeof(srcinfo));
|
|
1942
|
+
memset(&dstinfo, 0, sizeof(dstinfo));
|
|
1943
|
+
memset(&srcerr, 0, sizeof(srcerr));
|
|
1944
|
+
memset(&dsterr, 0, sizeof(dsterr));
|
|
1945
|
+
ctx->transient_jpeg_buf = NULL;
|
|
1946
|
+
|
|
1947
|
+
srcinfo.err = ip_use_error(&srcerr, ctx, ip_jpeg_invalid_error_exit);
|
|
1948
|
+
dstinfo.err = ip_use_error(&dsterr, ctx, ip_jpeg_encode_error_exit);
|
|
1949
|
+
|
|
1950
|
+
ctx->jmp_armed = 1;
|
|
1951
|
+
if (setjmp(ctx->jmpbuf)) {
|
|
1952
|
+
if (ctx->status == IP_OK)
|
|
1953
|
+
ip_context_set_error(ctx, IP_ERR_ENCODE, "lossless JPEG optimize failed");
|
|
1954
|
+
goto fail;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
jpeg_create_decompress(&srcinfo);
|
|
1958
|
+
jpeg_create_compress(&dstinfo);
|
|
1959
|
+
jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
|
|
1960
|
+
ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
|
|
1961
|
+
|
|
1962
|
+
if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
|
|
1963
|
+
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
|
|
1964
|
+
|
|
1965
|
+
if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK)
|
|
1966
|
+
IP_FAIL_GOTO(ctx, IP_ERR_INVALID_IMAGE, "invalid JPEG header");
|
|
1967
|
+
|
|
1968
|
+
ctx->source_orientation = ip_read_exif_orientation_from_decompress(&srcinfo);
|
|
1969
|
+
if (ctx->strip_metadata && ctx->source_orientation > 1)
|
|
1970
|
+
IP_FAIL_GOTO(ctx, IP_ERR_UNSUPPORTED,
|
|
1971
|
+
"lossless optimize cannot strip EXIF Orientation without changing "
|
|
1972
|
+
"visual orientation; use strip_metadata: false or ImagePack.compress");
|
|
1973
|
+
|
|
1974
|
+
if (!ip_validate_lossless_optimize_header(ctx, &srcinfo))
|
|
1975
|
+
goto fail;
|
|
1976
|
+
|
|
1977
|
+
if (!ctx->strip_metadata && !ip_save_markers_from_decompress(ctx, &srcinfo))
|
|
1978
|
+
goto fail;
|
|
1979
|
+
|
|
1980
|
+
coef_arrays = jpeg_read_coefficients(&srcinfo);
|
|
1981
|
+
jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
|
|
1982
|
+
dstinfo.optimize_coding = TRUE;
|
|
1983
|
+
dstinfo.num_scans = 0;
|
|
1984
|
+
dstinfo.scan_info = NULL;
|
|
1985
|
+
if (ctx->progressive) {
|
|
1986
|
+
jpeg_simple_progression(&dstinfo);
|
|
1987
|
+
dstinfo.optimize_coding = TRUE;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
|
|
1991
|
+
|
|
1992
|
+
if (ctx->cancellable_requested && atomic_load(&ctx->cancelled))
|
|
1993
|
+
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
|
|
1994
|
+
|
|
1995
|
+
jpeg_write_coefficients(&dstinfo, coef_arrays);
|
|
1996
|
+
if (!ctx->strip_metadata)
|
|
1997
|
+
ip_write_preserved_markers(ctx, &dstinfo);
|
|
1998
|
+
|
|
1999
|
+
jpeg_finish_compress(&dstinfo);
|
|
2000
|
+
jpeg_finish_decompress(&srcinfo);
|
|
2001
|
+
|
|
2002
|
+
if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
|
|
2003
|
+
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
|
|
2004
|
+
|
|
2005
|
+
jpeg_destroy_compress(&dstinfo);
|
|
2006
|
+
jpeg_destroy_decompress(&srcinfo);
|
|
2007
|
+
ctx->jmp_armed = 0;
|
|
2008
|
+
|
|
2009
|
+
ctx->output_data = ctx->transient_jpeg_buf;
|
|
2010
|
+
ctx->transient_jpeg_buf = NULL;
|
|
2011
|
+
ctx->output_size = (size_t)jpeg_size;
|
|
2012
|
+
ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
|
|
2013
|
+
return 1;
|
|
2014
|
+
|
|
2015
|
+
fail:
|
|
2016
|
+
jpeg_destroy_compress(&dstinfo);
|
|
2017
|
+
jpeg_destroy_decompress(&srcinfo);
|
|
2018
|
+
free(ctx->transient_jpeg_buf);
|
|
2019
|
+
ctx->transient_jpeg_buf = NULL;
|
|
2020
|
+
ctx->jmp_armed = 0;
|
|
2021
|
+
return 0;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1616
2024
|
static int ip_jpeg_turbo_compress(ip_context_t *ctx) {
|
|
1617
2025
|
if (ctx->ssim_guard_enabled)
|
|
1618
2026
|
return guarded_compress_jpeg_input_with_mode(ctx, 0);
|
|
@@ -1625,6 +2033,15 @@ static int ip_mozjpeg_compress(ip_context_t *ctx) {
|
|
|
1625
2033
|
return compress_jpeg_input_with_mode(ctx, 1);
|
|
1626
2034
|
}
|
|
1627
2035
|
|
|
2036
|
+
static ip_execution_t ip_async_execution(const ip_context_t *ctx) {
|
|
2037
|
+
#if IMAGE_PACK_HAS_OFFLOAD_SAFE
|
|
2038
|
+
return ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
|
|
2039
|
+
#else
|
|
2040
|
+
(void)ctx;
|
|
2041
|
+
return IP_EXEC_NOGVL;
|
|
2042
|
+
#endif
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1628
2045
|
static void ip_resolve_execution(ip_context_t *ctx) {
|
|
1629
2046
|
if (ctx->requested_execution != IP_EXEC_AUTO) {
|
|
1630
2047
|
ctx->resolved_execution = ctx->requested_execution;
|
|
@@ -1632,12 +2049,12 @@ static void ip_resolve_execution(ip_context_t *ctx) {
|
|
|
1632
2049
|
}
|
|
1633
2050
|
|
|
1634
2051
|
if (ctx->cancellable_requested) {
|
|
1635
|
-
ctx->resolved_execution = ctx
|
|
2052
|
+
ctx->resolved_execution = ip_async_execution(ctx);
|
|
1636
2053
|
return;
|
|
1637
2054
|
}
|
|
1638
2055
|
|
|
1639
2056
|
if (ctx->ssim_guard_enabled) {
|
|
1640
|
-
ctx->resolved_execution = ctx
|
|
2057
|
+
ctx->resolved_execution = ip_async_execution(ctx);
|
|
1641
2058
|
return;
|
|
1642
2059
|
}
|
|
1643
2060
|
|
|
@@ -1647,7 +2064,7 @@ static void ip_resolve_execution(ip_context_t *ctx) {
|
|
|
1647
2064
|
return;
|
|
1648
2065
|
}
|
|
1649
2066
|
|
|
1650
|
-
ctx->resolved_execution = ctx
|
|
2067
|
+
ctx->resolved_execution = ip_async_execution(ctx);
|
|
1651
2068
|
}
|
|
1652
2069
|
|
|
1653
2070
|
static void ip_unblock_function(void *data) {
|
|
@@ -1684,7 +2101,40 @@ static int ip_run_context(ip_context_t *ctx) {
|
|
|
1684
2101
|
} else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
|
|
1685
2102
|
rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, 0);
|
|
1686
2103
|
} else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
|
|
2104
|
+
#if IMAGE_PACK_HAS_OFFLOAD_SAFE
|
|
1687
2105
|
rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
|
|
2106
|
+
#else
|
|
2107
|
+
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
2108
|
+
"offload execution requires Ruby >= 3.4; use :nogvl or :auto");
|
|
2109
|
+
#endif
|
|
2110
|
+
} else {
|
|
2111
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
return ctx->status == IP_OK;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
static void *ip_run_optimize_nogvl(void *data) {
|
|
2118
|
+
ip_context_t *ctx = (ip_context_t *)data;
|
|
2119
|
+
if (ctx->status == IP_OK)
|
|
2120
|
+
ip_lossless_optimize_jpeg(ctx);
|
|
2121
|
+
return NULL;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
static int ip_run_optimize_context(ip_context_t *ctx) {
|
|
2125
|
+
ip_resolve_execution(ctx);
|
|
2126
|
+
|
|
2127
|
+
if (ctx->resolved_execution == IP_EXEC_DIRECT) {
|
|
2128
|
+
ip_run_optimize_nogvl(ctx);
|
|
2129
|
+
} else if (ctx->resolved_execution == IP_EXEC_NOGVL) {
|
|
2130
|
+
rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, 0);
|
|
2131
|
+
} else if (ctx->resolved_execution == IP_EXEC_OFFLOAD) {
|
|
2132
|
+
#if IMAGE_PACK_HAS_OFFLOAD_SAFE
|
|
2133
|
+
rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
|
|
2134
|
+
#else
|
|
2135
|
+
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
2136
|
+
"offload execution requires Ruby >= 3.4; use :nogvl or :auto");
|
|
2137
|
+
#endif
|
|
1688
2138
|
} else {
|
|
1689
2139
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
|
|
1690
2140
|
}
|
|
@@ -1692,31 +2142,40 @@ static int ip_run_context(ip_context_t *ctx) {
|
|
|
1692
2142
|
return ctx->status == IP_OK;
|
|
1693
2143
|
}
|
|
1694
2144
|
|
|
1695
|
-
static size_t config_size_value(VALUE config, ID id, size_t fallback) {
|
|
2145
|
+
static size_t config_size_value(VALUE config, ID id, size_t fallback, const char *name) {
|
|
1696
2146
|
VALUE value = rb_funcall(config, id, 0);
|
|
1697
2147
|
if (NIL_P(value))
|
|
1698
2148
|
return fallback;
|
|
2149
|
+
if (!RB_INTEGER_TYPE_P(value) || ip_value_negative(value)) {
|
|
2150
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "%s must be an Integer >= 0", name);
|
|
2151
|
+
}
|
|
1699
2152
|
return NUM2SIZET(value);
|
|
1700
2153
|
}
|
|
1701
2154
|
|
|
1702
|
-
static int config_int_value(VALUE config, ID id, int fallback) {
|
|
2155
|
+
static int config_int_value(VALUE config, ID id, int fallback, const char *name) {
|
|
1703
2156
|
VALUE value = rb_funcall(config, id, 0);
|
|
1704
2157
|
if (NIL_P(value))
|
|
1705
2158
|
return fallback;
|
|
2159
|
+
if (!RB_INTEGER_TYPE_P(value) || ip_value_negative(value)) {
|
|
2160
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "%s must be an Integer >= 0", name);
|
|
2161
|
+
}
|
|
1706
2162
|
return NUM2INT(value);
|
|
1707
2163
|
}
|
|
1708
2164
|
|
|
1709
2165
|
static void apply_configuration(VALUE self, ip_context_t *ctx) {
|
|
1710
2166
|
VALUE config = rb_funcall(self, id_configuration, 0);
|
|
1711
|
-
ctx->direct_input_threshold =
|
|
1712
|
-
|
|
1713
|
-
ctx->direct_pixel_threshold =
|
|
1714
|
-
|
|
1715
|
-
ctx->max_pixels =
|
|
1716
|
-
|
|
1717
|
-
ctx->
|
|
1718
|
-
ctx->
|
|
1719
|
-
ctx->
|
|
2167
|
+
ctx->direct_input_threshold = config_size_value(
|
|
2168
|
+
config, id_direct_input_threshold, ctx->direct_input_threshold, "direct_input_threshold");
|
|
2169
|
+
ctx->direct_pixel_threshold = config_size_value(
|
|
2170
|
+
config, id_direct_pixel_threshold, ctx->direct_pixel_threshold, "direct_pixel_threshold");
|
|
2171
|
+
ctx->max_pixels =
|
|
2172
|
+
(uint64_t)config_size_value(config, id_max_pixels, (size_t)ctx->max_pixels, "max_pixels");
|
|
2173
|
+
ctx->max_width = config_int_value(config, id_max_width, ctx->max_width, "max_width");
|
|
2174
|
+
ctx->max_height = config_int_value(config, id_max_height, ctx->max_height, "max_height");
|
|
2175
|
+
ctx->max_output_size =
|
|
2176
|
+
config_size_value(config, id_max_output_size, ctx->max_output_size, "max_output_size");
|
|
2177
|
+
ctx->max_input_size =
|
|
2178
|
+
config_size_value(config, id_max_input_size, ctx->max_input_size, "max_input_size");
|
|
1720
2179
|
}
|
|
1721
2180
|
|
|
1722
2181
|
static void validate_limits_for_pixels(ip_context_t *ctx) {
|
|
@@ -1726,18 +2185,12 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
|
|
|
1726
2185
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "image dimensions must be positive");
|
|
1727
2186
|
return;
|
|
1728
2187
|
}
|
|
1729
|
-
if (ctx->
|
|
1730
|
-
ip_context_set_error(ctx,
|
|
2188
|
+
if (ctx->max_width < 0 || ctx->max_height < 0) {
|
|
2189
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "max_width/max_height must be >= 0");
|
|
1731
2190
|
return;
|
|
1732
2191
|
}
|
|
1733
|
-
if (ctx
|
|
1734
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "image height exceeds max_height");
|
|
2192
|
+
if (!ip_check_max_dimension_limits(ctx))
|
|
1735
2193
|
return;
|
|
1736
|
-
}
|
|
1737
|
-
if ((uint64_t)ctx->width * (uint64_t)ctx->height > ctx->max_pixels) {
|
|
1738
|
-
ip_context_set_error(ctx, IP_ERR_LIMIT, "image pixels exceed max_pixels");
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
2194
|
if (!ip_checked_image_size(ctx->width, ctx->height, ctx->channels, &decoded_bytes)) {
|
|
1742
2195
|
ip_context_set_error(ctx, IP_ERR_LIMIT, "decoded image size overflows native size");
|
|
1743
2196
|
return;
|
|
@@ -1746,131 +2199,233 @@ static void validate_limits_for_pixels(ip_context_t *ctx) {
|
|
|
1746
2199
|
ctx->decoded_bytes = decoded_bytes;
|
|
1747
2200
|
}
|
|
1748
2201
|
|
|
2202
|
+
static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
|
|
2203
|
+
ip_compress_jpeg_call_t *call = (ip_compress_jpeg_call_t *)ptr;
|
|
2204
|
+
ip_context_t *ctx = ip_context_new();
|
|
2205
|
+
if (!ctx)
|
|
2206
|
+
rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
|
|
2207
|
+
call->ctx = ctx;
|
|
2208
|
+
|
|
2209
|
+
ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
|
|
2210
|
+
ctx->algo = ip_parse_algo(call->algo);
|
|
2211
|
+
ctx->quality = NUM2INT(call->quality);
|
|
2212
|
+
ctx->selected_quality = ctx->quality;
|
|
2213
|
+
ip_validate_quality_or_raise(ctx);
|
|
2214
|
+
ctx->min_ssim = NUM2DBL(call->min_ssim);
|
|
2215
|
+
ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
|
|
2216
|
+
ip_validate_min_ssim_or_raise(ctx);
|
|
2217
|
+
ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
|
|
2218
|
+
ctx->progressive = ip_bool_value(call->progressive);
|
|
2219
|
+
ctx->strip_metadata = ip_bool_value(call->strip_metadata);
|
|
2220
|
+
ctx->requested_execution = ip_parse_execution(call->execution);
|
|
2221
|
+
ctx->cancellable_requested = ip_bool_value(call->cancellable);
|
|
2222
|
+
ctx->has_scheduler = ip_bool_value(call->has_scheduler);
|
|
2223
|
+
apply_configuration(call->self, ctx);
|
|
2224
|
+
|
|
2225
|
+
ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
|
|
2226
|
+
if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
|
|
2227
|
+
!ip_prepare_input_bytes(ctx, call->input, in_kind)) {
|
|
2228
|
+
ip_raise_for_status(ctx);
|
|
2229
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
|
|
2233
|
+
!ip_inspect_jpeg_header(ctx, 0)) {
|
|
2234
|
+
ip_raise_for_status(ctx);
|
|
2235
|
+
rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
ip_resolve_execution(ctx);
|
|
2239
|
+
if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
|
|
2240
|
+
ip_raise_for_status(ctx);
|
|
2241
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
ip_run_context(ctx);
|
|
2245
|
+
return ip_finish_output(ctx, out_kind);
|
|
2246
|
+
}
|
|
2247
|
+
|
|
1749
2248
|
static VALUE ip_compress_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
|
|
1750
2249
|
VALUE output_kind, VALUE algo, VALUE quality, VALUE min_ssim,
|
|
1751
2250
|
VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
|
|
1752
2251
|
VALUE execution, VALUE cancellable, VALUE has_scheduler) {
|
|
2252
|
+
ip_compress_jpeg_call_t call = {
|
|
2253
|
+
self, input, input_kind, output, output_kind,
|
|
2254
|
+
algo, quality, min_ssim, mozjpeg_trellis, progressive,
|
|
2255
|
+
strip_metadata, execution, cancellable, has_scheduler, NULL};
|
|
2256
|
+
return rb_ensure(ip_compress_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
|
|
2260
|
+
ip_compress_pixels_call_t *call = (ip_compress_pixels_call_t *)ptr;
|
|
1753
2261
|
ip_context_t *ctx = ip_context_new();
|
|
1754
2262
|
if (!ctx)
|
|
1755
2263
|
rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
|
|
2264
|
+
call->ctx = ctx;
|
|
1756
2265
|
|
|
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);
|
|
2266
|
+
ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
|
|
2267
|
+
ctx->algo = ip_parse_algo(call->algo);
|
|
2268
|
+
ctx->quality = NUM2INT(call->quality);
|
|
1760
2269
|
ctx->selected_quality = ctx->quality;
|
|
1761
2270
|
ip_validate_quality_or_raise(ctx);
|
|
1762
|
-
ctx->min_ssim = NUM2DBL(min_ssim);
|
|
2271
|
+
ctx->min_ssim = NUM2DBL(call->min_ssim);
|
|
1763
2272
|
ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
|
|
1764
2273
|
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 =
|
|
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 (!
|
|
1774
|
-
!
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
ip_context_free(ctx);
|
|
1779
|
-
rb_raise(exception, "%s", message[0] ? message : "invalid JPEG input");
|
|
2274
|
+
ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
|
|
2275
|
+
ctx->progressive = ip_bool_value(call->progressive);
|
|
2276
|
+
ctx->strip_metadata = 1;
|
|
2277
|
+
ctx->requested_execution = ip_parse_execution(call->execution);
|
|
2278
|
+
ctx->cancellable_requested = ip_bool_value(call->cancellable);
|
|
2279
|
+
ctx->has_scheduler = ip_bool_value(call->has_scheduler);
|
|
2280
|
+
apply_configuration(call->self, ctx);
|
|
2281
|
+
|
|
2282
|
+
if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
|
|
2283
|
+
!ip_prepare_pixels(ctx, call->buffer, NUM2INT(call->width), NUM2INT(call->height),
|
|
2284
|
+
NUM2INT(call->channels), ip_bool_value(call->exact_size))) {
|
|
2285
|
+
ip_raise_for_status(ctx);
|
|
2286
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
|
|
1780
2287
|
}
|
|
1781
2288
|
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2289
|
+
validate_limits_for_pixels(ctx);
|
|
2290
|
+
if (ctx->status != IP_OK)
|
|
2291
|
+
ip_raise_for_status(ctx);
|
|
2292
|
+
|
|
2293
|
+
ip_resolve_execution(ctx);
|
|
2294
|
+
if (!ip_ensure_owned_pixels_for_async(ctx, call->buffer)) {
|
|
2295
|
+
ip_raise_for_status(ctx);
|
|
2296
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "invalid pixel input");
|
|
1789
2297
|
}
|
|
1790
2298
|
|
|
1791
2299
|
ip_run_context(ctx);
|
|
1792
|
-
|
|
1793
|
-
ip_context_free(ctx);
|
|
1794
|
-
return result;
|
|
2300
|
+
return ip_finish_output(ctx, out_kind);
|
|
1795
2301
|
}
|
|
1796
2302
|
|
|
1797
2303
|
static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
|
|
1798
2304
|
VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
|
|
1799
|
-
VALUE quality, VALUE
|
|
2305
|
+
VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
|
|
2306
|
+
VALUE progressive, VALUE exact_size, VALUE execution,
|
|
1800
2307
|
VALUE cancellable, VALUE has_scheduler) {
|
|
2308
|
+
ip_compress_pixels_call_t call = {
|
|
2309
|
+
self, buffer, width, height, channels, output, output_kind,
|
|
2310
|
+
algo, quality, min_ssim, mozjpeg_trellis, progressive, exact_size, execution,
|
|
2311
|
+
cancellable, has_scheduler, NULL};
|
|
2312
|
+
return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
|
|
2313
|
+
(VALUE)&call.ctx);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
|
|
2317
|
+
ip_optimize_jpeg_call_t *call = (ip_optimize_jpeg_call_t *)ptr;
|
|
1801
2318
|
ip_context_t *ctx = ip_context_new();
|
|
1802
2319
|
if (!ctx)
|
|
1803
2320
|
rb_raise(rb_eImagePackOutOfMemoryError, "failed to allocate native context");
|
|
2321
|
+
call->ctx = ctx;
|
|
1804
2322
|
|
|
1805
|
-
ip_output_kind_t out_kind = ip_parse_output_kind(output_kind);
|
|
1806
|
-
ctx->
|
|
1807
|
-
ctx->
|
|
1808
|
-
|
|
1809
|
-
ctx->
|
|
1810
|
-
ctx->
|
|
1811
|
-
ctx->
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
char message[512];
|
|
1820
|
-
snprintf(message, sizeof(message), "%s", ctx->error_message);
|
|
1821
|
-
ip_context_free(ctx);
|
|
1822
|
-
rb_raise(exception, "%s", message[0] ? message : "invalid pixel input");
|
|
2323
|
+
ip_output_kind_t out_kind = ip_parse_output_kind(call->output_kind);
|
|
2324
|
+
ctx->progressive = ip_bool_value(call->progressive);
|
|
2325
|
+
ctx->strip_metadata = ip_bool_value(call->strip_metadata);
|
|
2326
|
+
ctx->requested_execution = ip_parse_execution(call->execution);
|
|
2327
|
+
ctx->cancellable_requested = ip_bool_value(call->cancellable);
|
|
2328
|
+
ctx->has_scheduler = ip_bool_value(call->has_scheduler);
|
|
2329
|
+
ctx->ssim_guard_enabled = 0;
|
|
2330
|
+
apply_configuration(call->self, ctx);
|
|
2331
|
+
|
|
2332
|
+
ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
|
|
2333
|
+
if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
|
|
2334
|
+
!ip_prepare_input_bytes(ctx, call->input, in_kind)) {
|
|
2335
|
+
ip_raise_for_status(ctx);
|
|
2336
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
|
|
1823
2337
|
}
|
|
1824
2338
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
snprintf(message, sizeof(message), "%s", ctx->error_message);
|
|
1830
|
-
ip_context_free(ctx);
|
|
1831
|
-
rb_raise(exception, "%s", message);
|
|
2339
|
+
if (ctx->requested_execution == IP_EXEC_AUTO && ctx->input_size < ctx->direct_input_threshold &&
|
|
2340
|
+
!ip_inspect_jpeg_header(ctx, 1)) {
|
|
2341
|
+
ip_raise_for_status(ctx);
|
|
2342
|
+
rb_raise(rb_eImagePackInvalidImageError, "invalid JPEG input");
|
|
1832
2343
|
}
|
|
1833
2344
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2345
|
+
ip_resolve_execution(ctx);
|
|
2346
|
+
if (!ip_ensure_owned_input_for_async(ctx, call->input, in_kind)) {
|
|
2347
|
+
ip_raise_for_status(ctx);
|
|
2348
|
+
rb_raise(rb_eImagePackInvalidArgumentError, "invalid JPEG input");
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
ip_run_optimize_context(ctx);
|
|
2352
|
+
return ip_finish_output(ctx, out_kind);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
|
|
2356
|
+
VALUE output_kind, VALUE progressive, VALUE strip_metadata,
|
|
2357
|
+
VALUE execution, VALUE cancellable, VALUE has_scheduler) {
|
|
2358
|
+
ip_optimize_jpeg_call_t call = {
|
|
2359
|
+
self, input, input_kind, output, output_kind, progressive,
|
|
2360
|
+
strip_metadata, execution, cancellable, has_scheduler, NULL};
|
|
2361
|
+
return rb_ensure(ip_optimize_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
|
|
1838
2362
|
}
|
|
1839
2363
|
|
|
1840
2364
|
IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2365
|
+
static const struct {
|
|
2366
|
+
ID *slot;
|
|
2367
|
+
const char *name;
|
|
2368
|
+
} ids[] = {{&id_jpeg_turbo, "jpeg_turbo"},
|
|
2369
|
+
{&id_mozjpeg, "mozjpeg"},
|
|
2370
|
+
{&id_direct, "direct"},
|
|
2371
|
+
{&id_nogvl, "nogvl"},
|
|
2372
|
+
{&id_offload, "offload"},
|
|
2373
|
+
{&id_auto, "auto"},
|
|
2374
|
+
{&id_bytes, "bytes"},
|
|
2375
|
+
{&id_path, "path"},
|
|
2376
|
+
{&id_io_buffer, "io_buffer"},
|
|
2377
|
+
{&id_return_string, "return_string"},
|
|
2378
|
+
{&id_configuration, "configuration"},
|
|
2379
|
+
{&id_direct_input_threshold, "direct_input_threshold"},
|
|
2380
|
+
{&id_direct_pixel_threshold, "direct_pixel_threshold"},
|
|
2381
|
+
{&id_max_pixels, "max_pixels"},
|
|
2382
|
+
{&id_max_width, "max_width"},
|
|
2383
|
+
{&id_max_height, "max_height"},
|
|
2384
|
+
{&id_max_output_size, "max_output_size"},
|
|
2385
|
+
{&id_max_input_size, "max_input_size"}};
|
|
2386
|
+
for (size_t i = 0; i < IP_ARRAY_LEN(ids); i++)
|
|
2387
|
+
*ids[i].slot = rb_intern(ids[i].name);
|
|
1859
2388
|
|
|
1860
2389
|
rb_mImagePack = rb_define_module("ImagePack");
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2390
|
+
|
|
2391
|
+
static const struct {
|
|
2392
|
+
VALUE *slot;
|
|
2393
|
+
const char *name;
|
|
2394
|
+
} exceptions[] = {{&rb_eImagePackError, "Error"},
|
|
2395
|
+
{&rb_eImagePackInvalidArgumentError, "InvalidArgumentError"},
|
|
2396
|
+
{&rb_eImagePackInvalidImageError, "InvalidImageError"},
|
|
2397
|
+
{&rb_eImagePackUnsupportedError, "UnsupportedError"},
|
|
2398
|
+
{&rb_eImagePackLimitExceededError, "LimitExceededError"},
|
|
2399
|
+
{&rb_eImagePackEncodeError, "EncodeError"},
|
|
2400
|
+
{&rb_eImagePackQualityConstraintError, "QualityConstraintError"},
|
|
2401
|
+
{&rb_eImagePackOutOfMemoryError, "OutOfMemoryError"},
|
|
2402
|
+
{&rb_eImagePackCancelledError, "CancelledError"}};
|
|
2403
|
+
for (size_t i = 0; i < IP_ARRAY_LEN(exceptions); i++)
|
|
2404
|
+
*exceptions[i].slot = rb_const_get(rb_mImagePack, rb_intern(exceptions[i].name));
|
|
2405
|
+
|
|
2406
|
+
rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION", rb_str_new_cstr(VERSION));
|
|
2407
|
+
#if defined(IMAGE_PACK_HAS_SIMD)
|
|
2408
|
+
rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qtrue);
|
|
2409
|
+
#else
|
|
2410
|
+
rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qfalse);
|
|
2411
|
+
#endif
|
|
2412
|
+
#if IMAGE_PACK_HAS_OFFLOAD_SAFE
|
|
2413
|
+
rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE", Qtrue);
|
|
2414
|
+
#else
|
|
2415
|
+
rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE", Qfalse);
|
|
2416
|
+
#endif
|
|
2417
|
+
|
|
2418
|
+
static const struct {
|
|
2419
|
+
const char *name;
|
|
2420
|
+
VALUE (*fn)(ANYARGS);
|
|
2421
|
+
int arity;
|
|
2422
|
+
} methods[] = {{"__compress_jpeg", (VALUE (*)(ANYARGS))ip_compress_jpeg_entry, 13},
|
|
2423
|
+
{"__compress_pixels", (VALUE (*)(ANYARGS))ip_compress_pixels_entry, 15},
|
|
2424
|
+
{"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry, 9},
|
|
2425
|
+
{"__inspect_image", (VALUE (*)(ANYARGS))ip_inspect_image_entry, 2}};
|
|
2426
|
+
for (size_t i = 0; i < IP_ARRAY_LEN(methods); i++) {
|
|
2427
|
+
rb_define_singleton_method(rb_mImagePack, methods[i].name, methods[i].fn, methods[i].arity);
|
|
2428
|
+
rb_funcall(rb_mImagePack, rb_intern("private_class_method"), 1,
|
|
2429
|
+
ID2SYM(rb_intern(methods[i].name)));
|
|
2430
|
+
}
|
|
1876
2431
|
}
|