webp-ffi 0.3.1 → 1.0.0
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 +65 -0
- data/LICENSE.txt +1 -1
- data/README.md +138 -27
- data/ext/webp_ffi/Rakefile +2 -18
- data/ext/webp_ffi/build_config.rb +119 -0
- data/ext/webp_ffi/jpegdec.c +104 -10
- data/ext/webp_ffi/jpegdec.h +5 -2
- data/ext/webp_ffi/metadata.c +138 -0
- data/ext/webp_ffi/metadata.h +49 -0
- data/ext/webp_ffi/pngdec.c +197 -20
- data/ext/webp_ffi/pngdec.h +7 -2
- data/ext/webp_ffi/tiffdec.c +187 -23
- data/ext/webp_ffi/tiffdec.h +12 -4
- data/ext/webp_ffi/util.c +82 -35
- data/ext/webp_ffi/util.h +28 -3
- data/ext/webp_ffi/webp_ffi.c +484 -165
- data/ext/webp_ffi/webp_ffi.h +47 -5
- data/lib/webp/c.rb +41 -7
- data/lib/webp/error.rb +65 -15
- data/lib/webp/info.rb +13 -0
- data/lib/webp/options.rb +129 -22
- data/lib/webp/version.rb +1 -1
- data/lib/webp/webp.rb +150 -32
- data/lib/webp_ffi.rb +1 -1
- data/sig/webp.rbs +74 -0
- metadata +31 -48
- data/.gitignore +0 -22
- data/.ruby-version +0 -1
- data/.travis.yml +0 -38
- data/Gemfile +0 -4
- data/Rakefile +0 -38
- data/lib/webp/libc.rb +0 -20
- data/spec/factories/1.png +0 -0
- data/spec/factories/1.webp +0 -0
- data/spec/factories/2.png +0 -0
- data/spec/factories/2.webp +0 -0
- data/spec/factories/3.jpg +0 -0
- data/spec/factories/3.webp +0 -0
- data/spec/factories/4.jpg +0 -0
- data/spec/factories/4.webp +0 -0
- data/spec/factories/5.tif +0 -0
- data/spec/factories/5.webp +0 -0
- data/spec/spec_helper.rb +0 -9
- data/spec/travis_build.sh +0 -6
- data/spec/webp_ffi_spec.rb +0 -191
- data/webp-ffi.gemspec +0 -28
data/ext/webp_ffi/webp_ffi.c
CHANGED
|
@@ -1,40 +1,57 @@
|
|
|
1
|
-
#include <assert.h>
|
|
2
1
|
#include <stdio.h>
|
|
3
2
|
#include <stdlib.h>
|
|
4
3
|
#include <string.h>
|
|
5
4
|
|
|
6
5
|
// utils
|
|
6
|
+
#include "./metadata.h"
|
|
7
7
|
#include "./util.h"
|
|
8
8
|
#include "./webp_ffi.h"
|
|
9
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
|
+
|
|
10
15
|
#if defined(__cplusplus) || defined(c_plusplus)
|
|
11
16
|
extern "C" {
|
|
12
17
|
#endif
|
|
13
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
|
+
|
|
14
41
|
static int EncodeWriter(const uint8_t* data, size_t data_size,
|
|
15
42
|
const WebPPicture* const pic) {
|
|
16
43
|
FILE* const out = (FILE*)pic->custom_ptr;
|
|
17
44
|
return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
|
|
18
45
|
}
|
|
19
46
|
|
|
20
|
-
static void AllocExtraInfo(WebPPicture* const pic) {
|
|
21
|
-
const int mb_w = (pic->width + 15) / 16;
|
|
22
|
-
const int mb_h = (pic->height + 15) / 16;
|
|
23
|
-
pic->extra_info = (uint8_t*)malloc(mb_w * mb_h * sizeof(*pic->extra_info));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
47
|
// main functions
|
|
27
48
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
sprintf(version, "%d.%d.%d",
|
|
31
|
-
(v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff);
|
|
49
|
+
int decoder_version(void) {
|
|
50
|
+
return WebPGetDecoderVersion();
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
sprintf(version, "%d.%d.%d",
|
|
37
|
-
(v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff);
|
|
53
|
+
int encoder_version(void) {
|
|
54
|
+
return WebPGetEncoderVersion();
|
|
38
55
|
}
|
|
39
56
|
|
|
40
57
|
int webp_get_info(const uint8_t* data, size_t data_size, int* width, int* height) {
|
|
@@ -45,254 +62,556 @@ int webp_get_info(const uint8_t* data, size_t data_size, int* width, int* height
|
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
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
|
+
}
|
|
48
77
|
|
|
78
|
+
// Frees the output of the in-memory functions.
|
|
79
|
+
void webp_free_data(uint8_t* data) {
|
|
80
|
+
free(data);
|
|
81
|
+
}
|
|
49
82
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
89
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
}
|
|
61
102
|
}
|
|
62
103
|
|
|
63
|
-
// OPTIONS BEGIN
|
|
64
104
|
if (encode_config->lossless == 0 || encode_config->lossless == 1){
|
|
65
|
-
config
|
|
105
|
+
config->lossless = encode_config->lossless;
|
|
66
106
|
}
|
|
67
|
-
|
|
68
|
-
#if (ENC_MAJ_VERSION == 0 && ENC_MIN_VERSION > 4) || ENC_MAJ_VERSION > 0
|
|
69
107
|
if (encode_config->near_lossless >= 0 && encode_config->near_lossless <= 100){
|
|
70
|
-
config
|
|
71
|
-
config
|
|
108
|
+
config->near_lossless = encode_config->near_lossless;
|
|
109
|
+
config->lossless = 1; // use near-lossless only with lossless
|
|
72
110
|
}
|
|
73
|
-
#endif
|
|
74
|
-
|
|
75
111
|
if (encode_config->quality >= 0 && encode_config->quality <= 100){
|
|
76
|
-
config
|
|
112
|
+
config->quality = encode_config->quality;
|
|
77
113
|
}
|
|
78
114
|
if (encode_config->method >= 0 && encode_config->method <= 6){
|
|
79
|
-
config
|
|
115
|
+
config->method = encode_config->method;
|
|
80
116
|
}
|
|
81
117
|
if (encode_config->target_size > 0){
|
|
82
|
-
config
|
|
118
|
+
config->target_size = encode_config->target_size;
|
|
83
119
|
}
|
|
84
120
|
if (encode_config->target_PSNR > 0){
|
|
85
|
-
config
|
|
121
|
+
config->target_PSNR = encode_config->target_PSNR;
|
|
86
122
|
}
|
|
87
123
|
if (encode_config->segments >= 0 && encode_config->segments <= 4){
|
|
88
|
-
config
|
|
124
|
+
config->segments = encode_config->segments;
|
|
89
125
|
}
|
|
90
126
|
if (encode_config->sns_strength >= 0 && encode_config->sns_strength <= 100){
|
|
91
|
-
config
|
|
127
|
+
config->sns_strength = encode_config->sns_strength;
|
|
92
128
|
}
|
|
93
129
|
if (encode_config->filter_strength >= 0 && encode_config->filter_strength <= 100){
|
|
94
|
-
config
|
|
130
|
+
config->filter_strength = encode_config->filter_strength;
|
|
95
131
|
}
|
|
96
132
|
if (encode_config->filter_sharpness >= 0 && encode_config->filter_sharpness <= 7){
|
|
97
|
-
config
|
|
133
|
+
config->filter_sharpness = encode_config->filter_sharpness;
|
|
98
134
|
}
|
|
99
135
|
if (encode_config->filter_type == 0 || encode_config->filter_type == 1){
|
|
100
|
-
config
|
|
136
|
+
config->filter_type = encode_config->filter_type;
|
|
101
137
|
}
|
|
102
138
|
if (encode_config->autofilter == 0 || encode_config->autofilter == 1){
|
|
103
|
-
config
|
|
139
|
+
config->autofilter = encode_config->autofilter;
|
|
104
140
|
}
|
|
105
141
|
if (encode_config->alpha_compression == 0 || encode_config->alpha_compression == 1){
|
|
106
|
-
config
|
|
142
|
+
config->alpha_compression = encode_config->alpha_compression;
|
|
107
143
|
}
|
|
108
144
|
if (encode_config->alpha_filtering >= 0 && encode_config->alpha_filtering <= 2){
|
|
109
|
-
config
|
|
145
|
+
config->alpha_filtering = encode_config->alpha_filtering;
|
|
110
146
|
}
|
|
111
147
|
if (encode_config->alpha_quality >= 0 && encode_config->alpha_quality <= 100){
|
|
112
|
-
config
|
|
148
|
+
config->alpha_quality = encode_config->alpha_quality;
|
|
113
149
|
}
|
|
114
150
|
if (encode_config->pass >= 0 && encode_config->pass <= 10){
|
|
115
|
-
config
|
|
151
|
+
config->pass = encode_config->pass;
|
|
116
152
|
}
|
|
117
153
|
if (encode_config->show_compressed >= 0){
|
|
118
|
-
config
|
|
154
|
+
config->show_compressed = encode_config->show_compressed;
|
|
119
155
|
}
|
|
120
156
|
if (encode_config->preprocessing == 0 || encode_config->preprocessing == 1){
|
|
121
|
-
config
|
|
157
|
+
config->preprocessing = encode_config->preprocessing;
|
|
122
158
|
}
|
|
123
159
|
if (encode_config->partitions >= 0 && encode_config->partitions <= 3){
|
|
124
|
-
config
|
|
160
|
+
config->partitions = encode_config->partitions;
|
|
125
161
|
}
|
|
126
162
|
if (encode_config->partition_limit >= 0 && encode_config->partition_limit <= 100){
|
|
127
|
-
config
|
|
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;
|
|
128
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
|
|
129
184
|
if ((encode_config->width | encode_config->height) > 0){
|
|
130
|
-
picture
|
|
131
|
-
picture
|
|
185
|
+
picture->width = encode_config->width;
|
|
186
|
+
picture->height = encode_config->height;
|
|
132
187
|
}
|
|
133
|
-
// OPTIONS END
|
|
134
188
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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;
|
|
139
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;
|
|
140
201
|
|
|
141
|
-
if (!
|
|
142
|
-
|
|
143
|
-
return_value = 3;
|
|
144
|
-
goto Error;
|
|
202
|
+
if (!WebPValidateConfig(config)) {
|
|
203
|
+
return ENCODE_INVALID_CONFIG;
|
|
145
204
|
}
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
146
207
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
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
|
+
}
|
|
163
239
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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;
|
|
170
248
|
}
|
|
249
|
+
return EncodeToMemory(encode_config, config, picture, webp, webp_size, detail);
|
|
250
|
+
}
|
|
171
251
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
}
|
|
175
273
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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;
|
|
180
354
|
}
|
|
181
|
-
|
|
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;
|
|
182
361
|
|
|
183
|
-
|
|
184
|
-
free(
|
|
362
|
+
End:
|
|
363
|
+
free(webp);
|
|
364
|
+
MetadataFree(&metadata);
|
|
185
365
|
WebPPictureFree(&picture);
|
|
186
|
-
if (
|
|
187
|
-
|
|
366
|
+
if (result == 0) {
|
|
367
|
+
*output = (uint8_t*)buffer;
|
|
368
|
+
*output_size = buffer_size;
|
|
369
|
+
} else {
|
|
370
|
+
free(buffer);
|
|
188
371
|
}
|
|
189
|
-
|
|
190
|
-
return return_value;
|
|
372
|
+
return result;
|
|
191
373
|
}
|
|
192
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
|
+
}
|
|
193
416
|
|
|
194
|
-
|
|
195
|
-
int
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
OutputFileFormat format = oPNG;
|
|
201
|
-
|
|
202
|
-
if (!WebPInitDecoderConfig(&config)) {
|
|
203
|
-
//fprintf(stderr, "Library version mismatch!\n");
|
|
204
|
-
return 1;
|
|
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;
|
|
205
423
|
}
|
|
206
424
|
|
|
207
|
-
|
|
208
|
-
format = decode_config->output_format;
|
|
209
|
-
}
|
|
425
|
+
*format = decode_config->output_format;
|
|
210
426
|
if (decode_config->no_fancy_upsampling > 0){
|
|
211
|
-
config
|
|
427
|
+
config->options.no_fancy_upsampling = 1;
|
|
212
428
|
}
|
|
213
429
|
if (decode_config->bypass_filtering > 0){
|
|
214
|
-
config
|
|
430
|
+
config->options.bypass_filtering = 1;
|
|
215
431
|
}
|
|
216
432
|
if (decode_config->use_threads > 0){
|
|
217
|
-
config
|
|
433
|
+
config->options.use_threads = 1;
|
|
218
434
|
}
|
|
219
435
|
if ((decode_config->crop_w | decode_config->crop_h) > 0){
|
|
220
|
-
config
|
|
221
|
-
config
|
|
222
|
-
config
|
|
223
|
-
config
|
|
224
|
-
config
|
|
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;
|
|
225
441
|
}
|
|
226
442
|
if ((decode_config->resize_w | decode_config->resize_h) > 0){
|
|
227
|
-
config
|
|
228
|
-
config
|
|
229
|
-
config
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
VP8StatusCode status = VP8_STATUS_OK;
|
|
233
|
-
size_t data_size = 0;
|
|
234
|
-
const uint8_t* data = NULL;
|
|
235
|
-
|
|
236
|
-
if (!UtilReadFile(in_file, &data, &data_size)) return -1;
|
|
237
|
-
|
|
238
|
-
status = WebPGetFeatures(data, data_size, bitstream);
|
|
239
|
-
if (status != VP8_STATUS_OK) {
|
|
240
|
-
//fprintf(stderr, "This is invalid webp image!\n");
|
|
241
|
-
return_value = 2;
|
|
242
|
-
goto Error;
|
|
443
|
+
config->options.use_scaling = 1;
|
|
444
|
+
config->options.scaled_width = decode_config->resize_w;
|
|
445
|
+
config->options.scaled_height = decode_config->resize_h;
|
|
243
446
|
}
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
244
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) {
|
|
245
453
|
switch (format) {
|
|
246
454
|
case oPNG:
|
|
247
|
-
|
|
248
|
-
|
|
455
|
+
*colorspace = has_alpha ? MODE_RGBA : MODE_RGB;
|
|
456
|
+
return 1;
|
|
249
457
|
case oPAM:
|
|
250
|
-
|
|
251
|
-
|
|
458
|
+
*colorspace = MODE_RGBA;
|
|
459
|
+
return 1;
|
|
252
460
|
case oPPM:
|
|
253
|
-
|
|
254
|
-
|
|
461
|
+
*colorspace = MODE_RGB; // drops alpha for PPM
|
|
462
|
+
return 1;
|
|
255
463
|
case oBMP:
|
|
256
|
-
|
|
257
|
-
|
|
464
|
+
*colorspace = has_alpha ? MODE_BGRA : MODE_BGR;
|
|
465
|
+
return 1;
|
|
258
466
|
case oTIFF_: // note: force pre-multiplied alpha
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
break;
|
|
467
|
+
*colorspace = has_alpha ? MODE_rgbA : MODE_RGB;
|
|
468
|
+
return 1;
|
|
262
469
|
case oPGM:
|
|
263
470
|
case oYUV:
|
|
264
|
-
|
|
265
|
-
|
|
471
|
+
*colorspace = has_alpha ? MODE_YUVA : MODE_YUV;
|
|
472
|
+
return 1;
|
|
266
473
|
case ALPHA_PLANE_ONLY:
|
|
267
|
-
|
|
268
|
-
|
|
474
|
+
*colorspace = MODE_YUVA;
|
|
475
|
+
return 1;
|
|
269
476
|
default:
|
|
270
|
-
|
|
271
|
-
return 3;
|
|
477
|
+
return 0;
|
|
272
478
|
}
|
|
479
|
+
}
|
|
273
480
|
|
|
274
|
-
|
|
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;
|
|
275
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);
|
|
276
493
|
if (status != VP8_STATUS_OK) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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;
|
|
280
529
|
}
|
|
281
|
-
UtilSaveOutput(output_buffer, format, out_file);
|
|
282
|
-
return_value = 0;
|
|
283
530
|
|
|
284
|
-
Error:
|
|
285
531
|
free((void*)data);
|
|
286
|
-
WebPFreeDecBuffer(
|
|
287
|
-
return
|
|
532
|
+
WebPFreeDecBuffer(&config.output);
|
|
533
|
+
return result;
|
|
288
534
|
}
|
|
289
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);
|
|
290
567
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
568
|
+
if (result == 0) {
|
|
569
|
+
*output = (uint8_t*)buffer;
|
|
570
|
+
*output_size = buffer_size;
|
|
571
|
+
} else {
|
|
572
|
+
free(buffer);
|
|
573
|
+
}
|
|
574
|
+
return result;
|
|
294
575
|
}
|
|
295
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
|
+
}
|
|
296
615
|
|
|
297
616
|
#if defined(__cplusplus) || defined(c_plusplus)
|
|
298
617
|
} // extern "C"
|