webp-ffi 1.0.0-x86_64-linux-gnu
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 +7 -0
- data/CHANGELOG.md +135 -0
- data/LICENSE.txt +22 -0
- data/README.md +308 -0
- data/ext/webp_ffi/Rakefile +3 -0
- data/ext/webp_ffi/build_config.rb +119 -0
- data/ext/webp_ffi/jpegdec.c +174 -0
- data/ext/webp_ffi/jpegdec.h +20 -0
- data/ext/webp_ffi/metadata.c +138 -0
- data/ext/webp_ffi/metadata.h +49 -0
- data/ext/webp_ffi/pngdec.c +389 -0
- data/ext/webp_ffi/pngdec.h +29 -0
- data/ext/webp_ffi/tiffdec.c +361 -0
- data/ext/webp_ffi/tiffdec.h +31 -0
- data/ext/webp_ffi/util.c +216 -0
- data/ext/webp_ffi/util.h +63 -0
- data/ext/webp_ffi/webp_ffi.c +618 -0
- data/ext/webp_ffi/webp_ffi.h +110 -0
- data/ext/webp_ffi/x86_64-linux/libwebp_ffi.so +0 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libjpeg-turbo-LICENSE.md +147 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libjpeg-turbo-README.ijg +260 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libpng-LICENSE +134 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libtiff-LICENSE.md +49 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libwebp-COPYING +30 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libwebp-PATENTS +23 -0
- data/ext/webp_ffi/x86_64-linux/licenses/zlib-LICENSE +22 -0
- data/ext/webp_ffi/x86_64-linux/versions.txt +5 -0
- data/lib/webp/c.rb +101 -0
- data/lib/webp/error.rb +71 -0
- data/lib/webp/info.rb +13 -0
- data/lib/webp/options.rb +171 -0
- data/lib/webp/version.rb +3 -0
- data/lib/webp/webp.rb +179 -0
- data/lib/webp-ffi.rb +1 -0
- data/lib/webp_ffi.rb +16 -0
- data/sig/webp.rbs +74 -0
- metadata +145 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
#include <stdio.h>
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
// utils
|
|
6
|
+
#include "./metadata.h"
|
|
7
|
+
#include "./util.h"
|
|
8
|
+
#include "./webp_ffi.h"
|
|
9
|
+
|
|
10
|
+
// libwebp 1.0.0 is the oldest supported version (encoder ABI 0x020e)
|
|
11
|
+
#if WEBP_ENCODER_ABI_VERSION < 0x020e || WEBP_DECODER_ABI_VERSION < 0x0208
|
|
12
|
+
#error "webp-ffi needs libwebp 1.0.0 or newer"
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#if defined(__cplusplus) || defined(c_plusplus)
|
|
16
|
+
extern "C" {
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
// Error codes returned to Ruby. lib/webp/error.rb has the messages.
|
|
20
|
+
enum {
|
|
21
|
+
ENCODE_VERSION_MISMATCH = 1,
|
|
22
|
+
ENCODE_INVALID_CONFIG = 2,
|
|
23
|
+
ENCODE_CANNOT_READ_INPUT = 3,
|
|
24
|
+
ENCODE_CANNOT_OPEN_OUTPUT = 4,
|
|
25
|
+
ENCODE_CANNOT_CROP = 5,
|
|
26
|
+
ENCODE_CANNOT_RESIZE = 6,
|
|
27
|
+
ENCODE_FAILED = 7,
|
|
28
|
+
ENCODE_OUT_OF_MEMORY = 8
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
enum {
|
|
32
|
+
DECODE_VERSION_MISMATCH = 1,
|
|
33
|
+
DECODE_INVALID_WEBP = 2,
|
|
34
|
+
DECODE_INVALID_FORMAT = 3,
|
|
35
|
+
DECODE_FAILED = 4,
|
|
36
|
+
DECODE_CANNOT_READ_INPUT = 5,
|
|
37
|
+
DECODE_CANNOT_WRITE_OUTPUT = 6,
|
|
38
|
+
DECODE_OUT_OF_MEMORY = 7
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
static int EncodeWriter(const uint8_t* data, size_t data_size,
|
|
42
|
+
const WebPPicture* const pic) {
|
|
43
|
+
FILE* const out = (FILE*)pic->custom_ptr;
|
|
44
|
+
return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// main functions
|
|
48
|
+
|
|
49
|
+
int decoder_version(void) {
|
|
50
|
+
return WebPGetDecoderVersion();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
int encoder_version(void) {
|
|
54
|
+
return WebPGetEncoderVersion();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
int webp_get_info(const uint8_t* data, size_t data_size, int* width, int* height) {
|
|
58
|
+
if (WebPGetInfo(data, data_size, width, height) == 1) {
|
|
59
|
+
return 0;
|
|
60
|
+
} else {
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int webp_get_features(const uint8_t* data, size_t data_size,
|
|
66
|
+
FfiWebpFeatures* features) {
|
|
67
|
+
WebPBitstreamFeatures bitstream;
|
|
68
|
+
const VP8StatusCode status = WebPGetFeatures(data, data_size, &bitstream);
|
|
69
|
+
if (status != VP8_STATUS_OK) return (int)status;
|
|
70
|
+
features->width = bitstream.width;
|
|
71
|
+
features->height = bitstream.height;
|
|
72
|
+
features->has_alpha = bitstream.has_alpha;
|
|
73
|
+
features->has_animation = bitstream.has_animation;
|
|
74
|
+
features->format = bitstream.format;
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Frees the output of the in-memory functions.
|
|
79
|
+
void webp_free_data(uint8_t* data) {
|
|
80
|
+
free(data);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Initializes 'config' and 'picture' and applies the options from Ruby.
|
|
84
|
+
static int SetupEncoder(const FfiWebpEncodeConfig* const encode_config,
|
|
85
|
+
WebPConfig* const config, WebPPicture* const picture) {
|
|
86
|
+
if (!WebPPictureInit(picture) || !WebPConfigInit(config)) {
|
|
87
|
+
return ENCODE_VERSION_MISMATCH;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// presets first, so the options below can still change their settings
|
|
91
|
+
if (encode_config->preset >= WEBP_PRESET_DEFAULT && encode_config->preset <= WEBP_PRESET_TEXT) {
|
|
92
|
+
const float quality = (encode_config->quality >= 0 && encode_config->quality <= 100)
|
|
93
|
+
? encode_config->quality : 75.f;
|
|
94
|
+
if (!WebPConfigPreset(config, (WebPPreset)encode_config->preset, quality)) {
|
|
95
|
+
return ENCODE_VERSION_MISMATCH;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (encode_config->lossless_level >= 0 && encode_config->lossless_level <= 9) {
|
|
99
|
+
if (!WebPConfigLosslessPreset(config, encode_config->lossless_level)) {
|
|
100
|
+
return ENCODE_INVALID_CONFIG;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (encode_config->lossless == 0 || encode_config->lossless == 1){
|
|
105
|
+
config->lossless = encode_config->lossless;
|
|
106
|
+
}
|
|
107
|
+
if (encode_config->near_lossless >= 0 && encode_config->near_lossless <= 100){
|
|
108
|
+
config->near_lossless = encode_config->near_lossless;
|
|
109
|
+
config->lossless = 1; // use near-lossless only with lossless
|
|
110
|
+
}
|
|
111
|
+
if (encode_config->quality >= 0 && encode_config->quality <= 100){
|
|
112
|
+
config->quality = encode_config->quality;
|
|
113
|
+
}
|
|
114
|
+
if (encode_config->method >= 0 && encode_config->method <= 6){
|
|
115
|
+
config->method = encode_config->method;
|
|
116
|
+
}
|
|
117
|
+
if (encode_config->target_size > 0){
|
|
118
|
+
config->target_size = encode_config->target_size;
|
|
119
|
+
}
|
|
120
|
+
if (encode_config->target_PSNR > 0){
|
|
121
|
+
config->target_PSNR = encode_config->target_PSNR;
|
|
122
|
+
}
|
|
123
|
+
if (encode_config->segments >= 0 && encode_config->segments <= 4){
|
|
124
|
+
config->segments = encode_config->segments;
|
|
125
|
+
}
|
|
126
|
+
if (encode_config->sns_strength >= 0 && encode_config->sns_strength <= 100){
|
|
127
|
+
config->sns_strength = encode_config->sns_strength;
|
|
128
|
+
}
|
|
129
|
+
if (encode_config->filter_strength >= 0 && encode_config->filter_strength <= 100){
|
|
130
|
+
config->filter_strength = encode_config->filter_strength;
|
|
131
|
+
}
|
|
132
|
+
if (encode_config->filter_sharpness >= 0 && encode_config->filter_sharpness <= 7){
|
|
133
|
+
config->filter_sharpness = encode_config->filter_sharpness;
|
|
134
|
+
}
|
|
135
|
+
if (encode_config->filter_type == 0 || encode_config->filter_type == 1){
|
|
136
|
+
config->filter_type = encode_config->filter_type;
|
|
137
|
+
}
|
|
138
|
+
if (encode_config->autofilter == 0 || encode_config->autofilter == 1){
|
|
139
|
+
config->autofilter = encode_config->autofilter;
|
|
140
|
+
}
|
|
141
|
+
if (encode_config->alpha_compression == 0 || encode_config->alpha_compression == 1){
|
|
142
|
+
config->alpha_compression = encode_config->alpha_compression;
|
|
143
|
+
}
|
|
144
|
+
if (encode_config->alpha_filtering >= 0 && encode_config->alpha_filtering <= 2){
|
|
145
|
+
config->alpha_filtering = encode_config->alpha_filtering;
|
|
146
|
+
}
|
|
147
|
+
if (encode_config->alpha_quality >= 0 && encode_config->alpha_quality <= 100){
|
|
148
|
+
config->alpha_quality = encode_config->alpha_quality;
|
|
149
|
+
}
|
|
150
|
+
if (encode_config->pass >= 0 && encode_config->pass <= 10){
|
|
151
|
+
config->pass = encode_config->pass;
|
|
152
|
+
}
|
|
153
|
+
if (encode_config->show_compressed >= 0){
|
|
154
|
+
config->show_compressed = encode_config->show_compressed;
|
|
155
|
+
}
|
|
156
|
+
if (encode_config->preprocessing == 0 || encode_config->preprocessing == 1){
|
|
157
|
+
config->preprocessing = encode_config->preprocessing;
|
|
158
|
+
}
|
|
159
|
+
if (encode_config->partitions >= 0 && encode_config->partitions <= 3){
|
|
160
|
+
config->partitions = encode_config->partitions;
|
|
161
|
+
}
|
|
162
|
+
if (encode_config->partition_limit >= 0 && encode_config->partition_limit <= 100){
|
|
163
|
+
config->partition_limit = encode_config->partition_limit;
|
|
164
|
+
}
|
|
165
|
+
if (encode_config->image_hint >= WEBP_HINT_DEFAULT && encode_config->image_hint < WEBP_HINT_LAST){
|
|
166
|
+
config->image_hint = (WebPImageHint)encode_config->image_hint;
|
|
167
|
+
}
|
|
168
|
+
if (encode_config->exact == 0 || encode_config->exact == 1){
|
|
169
|
+
config->exact = encode_config->exact;
|
|
170
|
+
}
|
|
171
|
+
if (encode_config->use_sharp_yuv == 0 || encode_config->use_sharp_yuv == 1){
|
|
172
|
+
config->use_sharp_yuv = encode_config->use_sharp_yuv;
|
|
173
|
+
}
|
|
174
|
+
if (encode_config->thread_level == 0 || encode_config->thread_level == 1){
|
|
175
|
+
config->thread_level = encode_config->thread_level;
|
|
176
|
+
}
|
|
177
|
+
if (encode_config->low_memory == 0 || encode_config->low_memory == 1){
|
|
178
|
+
config->low_memory = encode_config->low_memory;
|
|
179
|
+
}
|
|
180
|
+
if (encode_config->emulate_jpeg_size == 0 || encode_config->emulate_jpeg_size == 1){
|
|
181
|
+
config->emulate_jpeg_size = encode_config->emulate_jpeg_size;
|
|
182
|
+
}
|
|
183
|
+
// a size means the input is raw YUV
|
|
184
|
+
if ((encode_config->width | encode_config->height) > 0){
|
|
185
|
+
picture->width = encode_config->width;
|
|
186
|
+
picture->height = encode_config->height;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The rules below come from libwebp's examples/cwebp.c.
|
|
190
|
+
// A target size or PSNR is only searched for with more than one pass.
|
|
191
|
+
if ((config->target_size > 0 || config->target_PSNR > 0) && config->pass == 1) {
|
|
192
|
+
config->pass = 6;
|
|
193
|
+
}
|
|
194
|
+
// Keep RGB samples when the encoding needs them; otherwise the readers
|
|
195
|
+
// convert to YUV right away, which loses detail before lossless encoding
|
|
196
|
+
// and makes sharp YUV conversion impossible.
|
|
197
|
+
picture->use_argb = config->lossless || config->use_sharp_yuv ||
|
|
198
|
+
config->preprocessing > 0 ||
|
|
199
|
+
(encode_config->crop_w | encode_config->crop_h) > 0 ||
|
|
200
|
+
(encode_config->resize_w | encode_config->resize_h) > 0;
|
|
201
|
+
|
|
202
|
+
if (!WebPValidateConfig(config)) {
|
|
203
|
+
return ENCODE_INVALID_CONFIG;
|
|
204
|
+
}
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Crops, resizes and encodes 'picture'. On success '*webp' holds the WebP
|
|
209
|
+
// file (free it with free()).
|
|
210
|
+
static int EncodeToMemory(const FfiWebpEncodeConfig* const encode_config,
|
|
211
|
+
const WebPConfig* const config, WebPPicture* const picture,
|
|
212
|
+
char** const webp, size_t* const webp_size, int* const detail) {
|
|
213
|
+
int result = 0;
|
|
214
|
+
FILE* const out = open_memstream(webp, webp_size);
|
|
215
|
+
if (out == NULL) return ENCODE_OUT_OF_MEMORY;
|
|
216
|
+
|
|
217
|
+
picture->writer = EncodeWriter;
|
|
218
|
+
picture->custom_ptr = (void*)out;
|
|
219
|
+
if ((encode_config->crop_w | encode_config->crop_h) > 0 &&
|
|
220
|
+
!WebPPictureView(picture, encode_config->crop_x, encode_config->crop_y,
|
|
221
|
+
encode_config->crop_w, encode_config->crop_h, picture)) {
|
|
222
|
+
result = ENCODE_CANNOT_CROP;
|
|
223
|
+
} else if ((encode_config->resize_w | encode_config->resize_h) > 0 &&
|
|
224
|
+
!WebPPictureRescale(picture, encode_config->resize_w, encode_config->resize_h)) {
|
|
225
|
+
result = ENCODE_CANNOT_RESIZE;
|
|
226
|
+
} else if (!WebPEncode(config, picture)) {
|
|
227
|
+
result = ENCODE_FAILED;
|
|
228
|
+
}
|
|
229
|
+
if (result != 0) *detail = (int)picture->error_code;
|
|
230
|
+
|
|
231
|
+
// '*webp' and '*webp_size' are final only after fclose
|
|
232
|
+
if (fclose(out) != 0 && result == 0) result = ENCODE_OUT_OF_MEMORY;
|
|
233
|
+
if (result != 0) {
|
|
234
|
+
free(*webp);
|
|
235
|
+
*webp = NULL;
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Reads a picture (and its metadata, when asked for) from 'in' and encodes it.
|
|
241
|
+
// On success '*webp' holds the encoder output (free it with free()).
|
|
242
|
+
static int EncodeInput(FILE* const in, const FfiWebpEncodeConfig* const encode_config,
|
|
243
|
+
const WebPConfig* const config, WebPPicture* const picture,
|
|
244
|
+
Metadata* const metadata, char** const webp,
|
|
245
|
+
size_t* const webp_size, int* const detail) {
|
|
246
|
+
if (!UtilReadPictureFile(in, picture, 1, encode_config->metadata > 0 ? metadata : NULL)) {
|
|
247
|
+
return ENCODE_CANNOT_READ_INPUT;
|
|
248
|
+
}
|
|
249
|
+
return EncodeToMemory(encode_config, config, picture, webp, webp_size, detail);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Writes the WebP file with the requested metadata to 'out_file'. Opening it
|
|
253
|
+
// only now means a failed encode leaves no file, and the input file can be the
|
|
254
|
+
// output file. When writing fails, a file this call created is removed again.
|
|
255
|
+
static int WriteWebPFile(const char* const out_file, const char* const webp,
|
|
256
|
+
size_t webp_size, const WebPPicture* const picture,
|
|
257
|
+
const Metadata* const metadata, int keep_metadata,
|
|
258
|
+
int* const detail) {
|
|
259
|
+
const int existed = UtilFileExists(out_file);
|
|
260
|
+
FILE* const out = fopen(out_file, "wb");
|
|
261
|
+
int ok;
|
|
262
|
+
|
|
263
|
+
if (out == NULL) return ENCODE_CANNOT_OPEN_OUTPUT;
|
|
264
|
+
ok = MetadataWriteWebP(out, (const uint8_t*)webp, webp_size, picture->width,
|
|
265
|
+
picture->height, metadata, keep_metadata);
|
|
266
|
+
// fclose flushes buffered data, so it can also fail (for example, disk full)
|
|
267
|
+
if (fclose(out) != 0) ok = 0;
|
|
268
|
+
if (ok) return 0;
|
|
269
|
+
if (!existed) remove(out_file);
|
|
270
|
+
*detail = VP8_ENC_ERROR_BAD_WRITE;
|
|
271
|
+
return ENCODE_FAILED;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
int webp_encode(const char *in_file, const char *out_file,
|
|
275
|
+
const FfiWebpEncodeConfig *encode_config, int* detail) {
|
|
276
|
+
WebPPicture picture;
|
|
277
|
+
WebPConfig config;
|
|
278
|
+
Metadata metadata;
|
|
279
|
+
FILE* in;
|
|
280
|
+
char* webp = NULL;
|
|
281
|
+
size_t webp_size = 0;
|
|
282
|
+
int result;
|
|
283
|
+
|
|
284
|
+
*detail = 0;
|
|
285
|
+
result = SetupEncoder(encode_config, &config, &picture);
|
|
286
|
+
// after a version mismatch 'picture' is not initialized, so it can't be freed
|
|
287
|
+
if (result == ENCODE_VERSION_MISMATCH) return result;
|
|
288
|
+
MetadataInit(&metadata);
|
|
289
|
+
if (result != 0) goto End;
|
|
290
|
+
|
|
291
|
+
in = fopen(in_file, "rb");
|
|
292
|
+
if (in == NULL) {
|
|
293
|
+
result = ENCODE_CANNOT_READ_INPUT;
|
|
294
|
+
goto End;
|
|
295
|
+
}
|
|
296
|
+
// the input is read completely and closed before the output is opened
|
|
297
|
+
result = EncodeInput(in, encode_config, &config, &picture, &metadata,
|
|
298
|
+
&webp, &webp_size, detail);
|
|
299
|
+
fclose(in);
|
|
300
|
+
if (result == 0) {
|
|
301
|
+
result = WriteWebPFile(out_file, webp, webp_size, &picture, &metadata,
|
|
302
|
+
encode_config->metadata, detail);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
End:
|
|
306
|
+
free(webp);
|
|
307
|
+
MetadataFree(&metadata);
|
|
308
|
+
WebPPictureFree(&picture);
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
int webp_encode_data(const uint8_t* data, size_t data_size,
|
|
313
|
+
uint8_t** output, size_t* output_size,
|
|
314
|
+
const FfiWebpEncodeConfig* encode_config, int* detail) {
|
|
315
|
+
WebPPicture picture;
|
|
316
|
+
WebPConfig config;
|
|
317
|
+
Metadata metadata;
|
|
318
|
+
FILE* in;
|
|
319
|
+
FILE* out;
|
|
320
|
+
char* webp = NULL;
|
|
321
|
+
size_t webp_size = 0;
|
|
322
|
+
char* buffer = NULL;
|
|
323
|
+
size_t buffer_size = 0;
|
|
324
|
+
int result;
|
|
325
|
+
|
|
326
|
+
*output = NULL;
|
|
327
|
+
*output_size = 0;
|
|
328
|
+
*detail = 0;
|
|
329
|
+
result = SetupEncoder(encode_config, &config, &picture);
|
|
330
|
+
if (result == ENCODE_VERSION_MISMATCH) return result;
|
|
331
|
+
MetadataInit(&metadata);
|
|
332
|
+
if (result != 0) goto End;
|
|
333
|
+
|
|
334
|
+
if (data == NULL || data_size == 0) {
|
|
335
|
+
result = ENCODE_CANNOT_READ_INPUT;
|
|
336
|
+
goto End;
|
|
337
|
+
}
|
|
338
|
+
// the readers take a FILE*, so wrap the data in one; "rb" never writes to it
|
|
339
|
+
in = fmemopen((void*)data, data_size, "rb");
|
|
340
|
+
if (in == NULL) {
|
|
341
|
+
result = ENCODE_OUT_OF_MEMORY;
|
|
342
|
+
goto End;
|
|
343
|
+
}
|
|
344
|
+
result = EncodeInput(in, encode_config, &config, &picture, &metadata,
|
|
345
|
+
&webp, &webp_size, detail);
|
|
346
|
+
fclose(in);
|
|
347
|
+
if (result != 0) goto End;
|
|
348
|
+
|
|
349
|
+
// a FILE* that collects the WebP file in a growing malloc'ed buffer
|
|
350
|
+
out = open_memstream(&buffer, &buffer_size);
|
|
351
|
+
if (out == NULL) {
|
|
352
|
+
result = ENCODE_OUT_OF_MEMORY;
|
|
353
|
+
goto End;
|
|
354
|
+
}
|
|
355
|
+
if (!MetadataWriteWebP(out, (const uint8_t*)webp, webp_size, picture.width,
|
|
356
|
+
picture.height, &metadata, encode_config->metadata)) {
|
|
357
|
+
result = ENCODE_OUT_OF_MEMORY;
|
|
358
|
+
}
|
|
359
|
+
// 'buffer' and 'buffer_size' are final only after fclose
|
|
360
|
+
if (fclose(out) != 0 && result == 0) result = ENCODE_OUT_OF_MEMORY;
|
|
361
|
+
|
|
362
|
+
End:
|
|
363
|
+
free(webp);
|
|
364
|
+
MetadataFree(&metadata);
|
|
365
|
+
WebPPictureFree(&picture);
|
|
366
|
+
if (result == 0) {
|
|
367
|
+
*output = (uint8_t*)buffer;
|
|
368
|
+
*output_size = buffer_size;
|
|
369
|
+
} else {
|
|
370
|
+
free(buffer);
|
|
371
|
+
}
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
int webp_encode_rgba(const uint8_t* rgba, int width, int height,
|
|
376
|
+
uint8_t** output, size_t* output_size,
|
|
377
|
+
const FfiWebpEncodeConfig* encode_config, int* detail) {
|
|
378
|
+
WebPPicture picture;
|
|
379
|
+
WebPConfig config;
|
|
380
|
+
char* webp = NULL;
|
|
381
|
+
size_t webp_size = 0;
|
|
382
|
+
int stride;
|
|
383
|
+
int result;
|
|
384
|
+
|
|
385
|
+
*output = NULL;
|
|
386
|
+
*output_size = 0;
|
|
387
|
+
*detail = 0;
|
|
388
|
+
result = SetupEncoder(encode_config, &config, &picture);
|
|
389
|
+
if (result == ENCODE_VERSION_MISMATCH) return result;
|
|
390
|
+
if (result != 0) goto End;
|
|
391
|
+
|
|
392
|
+
if (rgba == NULL || width <= 0 || height <= 0 ||
|
|
393
|
+
!UtilCheckImageSize((uint64_t)width, (uint64_t)height, 4, &stride, NULL)) {
|
|
394
|
+
result = ENCODE_CANNOT_READ_INPUT;
|
|
395
|
+
goto End;
|
|
396
|
+
}
|
|
397
|
+
picture.width = width;
|
|
398
|
+
picture.height = height;
|
|
399
|
+
picture.use_argb = 1;
|
|
400
|
+
if (!WebPPictureImportRGBA(&picture, rgba, stride)) {
|
|
401
|
+
result = ENCODE_OUT_OF_MEMORY;
|
|
402
|
+
*detail = (int)picture.error_code;
|
|
403
|
+
goto End;
|
|
404
|
+
}
|
|
405
|
+
// raw pixels have no metadata, so the encoder output is the whole file
|
|
406
|
+
result = EncodeToMemory(encode_config, &config, &picture, &webp, &webp_size, detail);
|
|
407
|
+
if (result == 0) {
|
|
408
|
+
*output = (uint8_t*)webp;
|
|
409
|
+
*output_size = webp_size;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
End:
|
|
413
|
+
WebPPictureFree(&picture);
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Initializes 'config' and applies the options from Ruby.
|
|
418
|
+
static int SetupDecoder(const FfiWebpDecodeConfig* const decode_config,
|
|
419
|
+
WebPDecoderConfig* const config,
|
|
420
|
+
OutputFileFormat* const format) {
|
|
421
|
+
if (!WebPInitDecoderConfig(config)) {
|
|
422
|
+
return DECODE_VERSION_MISMATCH;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
*format = decode_config->output_format;
|
|
426
|
+
if (decode_config->no_fancy_upsampling > 0){
|
|
427
|
+
config->options.no_fancy_upsampling = 1;
|
|
428
|
+
}
|
|
429
|
+
if (decode_config->bypass_filtering > 0){
|
|
430
|
+
config->options.bypass_filtering = 1;
|
|
431
|
+
}
|
|
432
|
+
if (decode_config->use_threads > 0){
|
|
433
|
+
config->options.use_threads = 1;
|
|
434
|
+
}
|
|
435
|
+
if ((decode_config->crop_w | decode_config->crop_h) > 0){
|
|
436
|
+
config->options.use_cropping = 1;
|
|
437
|
+
config->options.crop_left = decode_config->crop_x;
|
|
438
|
+
config->options.crop_top = decode_config->crop_y;
|
|
439
|
+
config->options.crop_width = decode_config->crop_w;
|
|
440
|
+
config->options.crop_height = decode_config->crop_h;
|
|
441
|
+
}
|
|
442
|
+
if ((decode_config->resize_w | decode_config->resize_h) > 0){
|
|
443
|
+
config->options.use_scaling = 1;
|
|
444
|
+
config->options.scaled_width = decode_config->resize_w;
|
|
445
|
+
config->options.scaled_height = decode_config->resize_h;
|
|
446
|
+
}
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// The colorspace each output format is written from.
|
|
451
|
+
static int OutputColorspace(OutputFileFormat format, int has_alpha,
|
|
452
|
+
WEBP_CSP_MODE* const colorspace) {
|
|
453
|
+
switch (format) {
|
|
454
|
+
case oPNG:
|
|
455
|
+
*colorspace = has_alpha ? MODE_RGBA : MODE_RGB;
|
|
456
|
+
return 1;
|
|
457
|
+
case oPAM:
|
|
458
|
+
*colorspace = MODE_RGBA;
|
|
459
|
+
return 1;
|
|
460
|
+
case oPPM:
|
|
461
|
+
*colorspace = MODE_RGB; // drops alpha for PPM
|
|
462
|
+
return 1;
|
|
463
|
+
case oBMP:
|
|
464
|
+
*colorspace = has_alpha ? MODE_BGRA : MODE_BGR;
|
|
465
|
+
return 1;
|
|
466
|
+
case oTIFF_: // note: force pre-multiplied alpha
|
|
467
|
+
*colorspace = has_alpha ? MODE_rgbA : MODE_RGB;
|
|
468
|
+
return 1;
|
|
469
|
+
case oPGM:
|
|
470
|
+
case oYUV:
|
|
471
|
+
*colorspace = has_alpha ? MODE_YUVA : MODE_YUV;
|
|
472
|
+
return 1;
|
|
473
|
+
case ALPHA_PLANE_ONLY:
|
|
474
|
+
*colorspace = MODE_YUVA;
|
|
475
|
+
return 1;
|
|
476
|
+
default:
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Decodes 'data' into config->output. 'format' picks the colorspace; a
|
|
482
|
+
// negative 'format' decodes to RGBA.
|
|
483
|
+
static int DecodeData(const uint8_t* const data, size_t data_size, int format,
|
|
484
|
+
WebPDecoderConfig* const config, int* const detail) {
|
|
485
|
+
WebPDecBuffer* const output_buffer = &config->output;
|
|
486
|
+
VP8StatusCode status;
|
|
487
|
+
|
|
488
|
+
if (data == NULL || data_size == 0) {
|
|
489
|
+
*detail = VP8_STATUS_NOT_ENOUGH_DATA;
|
|
490
|
+
return DECODE_INVALID_WEBP;
|
|
491
|
+
}
|
|
492
|
+
status = WebPGetFeatures(data, data_size, &config->input);
|
|
493
|
+
if (status != VP8_STATUS_OK) {
|
|
494
|
+
*detail = (int)status;
|
|
495
|
+
return DECODE_INVALID_WEBP;
|
|
496
|
+
}
|
|
497
|
+
if (format < 0) {
|
|
498
|
+
output_buffer->colorspace = MODE_RGBA;
|
|
499
|
+
} else if (!OutputColorspace((OutputFileFormat)format, config->input.has_alpha,
|
|
500
|
+
&output_buffer->colorspace)) {
|
|
501
|
+
return DECODE_INVALID_FORMAT;
|
|
502
|
+
}
|
|
503
|
+
status = WebPDecode(data, data_size, config);
|
|
504
|
+
if (status != VP8_STATUS_OK) {
|
|
505
|
+
*detail = (int)status;
|
|
506
|
+
return DECODE_FAILED;
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
int webp_decode(const char *in_file, const char *out_file,
|
|
512
|
+
const FfiWebpDecodeConfig *decode_config, int* detail) {
|
|
513
|
+
WebPDecoderConfig config;
|
|
514
|
+
OutputFileFormat format;
|
|
515
|
+
const uint8_t* data = NULL;
|
|
516
|
+
size_t data_size = 0;
|
|
517
|
+
int result;
|
|
518
|
+
|
|
519
|
+
*detail = 0;
|
|
520
|
+
result = SetupDecoder(decode_config, &config, &format);
|
|
521
|
+
if (result != 0) return result;
|
|
522
|
+
if (!UtilReadFile(in_file, &data, &data_size)) {
|
|
523
|
+
return DECODE_CANNOT_READ_INPUT;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
result = DecodeData(data, data_size, (int)format, &config, detail);
|
|
527
|
+
if (result == 0 && !UtilSaveOutput(&config.output, format, out_file)) {
|
|
528
|
+
result = DECODE_CANNOT_WRITE_OUTPUT;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
free((void*)data);
|
|
532
|
+
WebPFreeDecBuffer(&config.output);
|
|
533
|
+
return result;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
int webp_decode_data(const uint8_t* data, size_t data_size,
|
|
537
|
+
uint8_t** output, size_t* output_size,
|
|
538
|
+
const FfiWebpDecodeConfig* decode_config, int* detail) {
|
|
539
|
+
WebPDecoderConfig config;
|
|
540
|
+
OutputFileFormat format;
|
|
541
|
+
FILE* out = NULL;
|
|
542
|
+
char* buffer = NULL;
|
|
543
|
+
size_t buffer_size = 0;
|
|
544
|
+
int result;
|
|
545
|
+
|
|
546
|
+
*output = NULL;
|
|
547
|
+
*output_size = 0;
|
|
548
|
+
*detail = 0;
|
|
549
|
+
result = SetupDecoder(decode_config, &config, &format);
|
|
550
|
+
if (result != 0) return result;
|
|
551
|
+
|
|
552
|
+
result = DecodeData(data, data_size, (int)format, &config, detail);
|
|
553
|
+
if (result == 0) {
|
|
554
|
+
// a FILE* that collects the output in a growing malloc'ed buffer
|
|
555
|
+
out = open_memstream(&buffer, &buffer_size);
|
|
556
|
+
if (out == NULL) {
|
|
557
|
+
result = DECODE_OUT_OF_MEMORY;
|
|
558
|
+
} else {
|
|
559
|
+
const int written = UtilWriteOutput(&config.output, format, out);
|
|
560
|
+
// writing to memory only fails when memory runs out
|
|
561
|
+
if (fclose(out) != 0 || !written) {
|
|
562
|
+
result = DECODE_OUT_OF_MEMORY;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
WebPFreeDecBuffer(&config.output);
|
|
567
|
+
|
|
568
|
+
if (result == 0) {
|
|
569
|
+
*output = (uint8_t*)buffer;
|
|
570
|
+
*output_size = buffer_size;
|
|
571
|
+
} else {
|
|
572
|
+
free(buffer);
|
|
573
|
+
}
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
int webp_decode_rgba(const uint8_t* data, size_t data_size,
|
|
578
|
+
uint8_t** output, size_t* output_size, int* width, int* height,
|
|
579
|
+
const FfiWebpDecodeConfig* decode_config, int* detail) {
|
|
580
|
+
WebPDecoderConfig config;
|
|
581
|
+
OutputFileFormat format;
|
|
582
|
+
int result;
|
|
583
|
+
|
|
584
|
+
*output = NULL;
|
|
585
|
+
*output_size = 0;
|
|
586
|
+
*width = 0;
|
|
587
|
+
*height = 0;
|
|
588
|
+
*detail = 0;
|
|
589
|
+
result = SetupDecoder(decode_config, &config, &format);
|
|
590
|
+
if (result != 0) return result;
|
|
591
|
+
|
|
592
|
+
result = DecodeData(data, data_size, -1, &config, detail);
|
|
593
|
+
if (result == 0) {
|
|
594
|
+
const WebPDecBuffer* const buffer = &config.output;
|
|
595
|
+
const WebPRGBABuffer* const rgba = &buffer->u.RGBA;
|
|
596
|
+
const size_t row_size = (size_t)buffer->width * 4;
|
|
597
|
+
uint8_t* pixels = (uint8_t*)malloc(row_size * (size_t)buffer->height);
|
|
598
|
+
if (pixels == NULL) {
|
|
599
|
+
result = DECODE_OUT_OF_MEMORY;
|
|
600
|
+
} else {
|
|
601
|
+
int y;
|
|
602
|
+
// copy row by row: libwebp's stride may be wider than the image
|
|
603
|
+
for (y = 0; y < buffer->height; ++y) {
|
|
604
|
+
memcpy(pixels + (size_t)y * row_size, rgba->rgba + (size_t)y * rgba->stride, row_size);
|
|
605
|
+
}
|
|
606
|
+
*output = pixels;
|
|
607
|
+
*output_size = row_size * (size_t)buffer->height;
|
|
608
|
+
*width = buffer->width;
|
|
609
|
+
*height = buffer->height;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
WebPFreeDecBuffer(&config.output);
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
#if defined(__cplusplus) || defined(c_plusplus)
|
|
617
|
+
} // extern "C"
|
|
618
|
+
#endif
|