webp-ffi 0.4.0 → 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.
@@ -0,0 +1,138 @@
1
+ #include <stdlib.h>
2
+ #include <string.h>
3
+
4
+ #include "./metadata.h"
5
+
6
+ #if defined(__cplusplus) || defined(c_plusplus)
7
+ extern "C" {
8
+ #endif
9
+
10
+ // Based on WriteWebPWithMetadata in libwebp's examples/cwebp.c.
11
+
12
+ #define TAG_SIZE 4
13
+ #define CHUNK_HEADER_SIZE 8
14
+ #define RIFF_HEADER_SIZE 12
15
+ #define VP8X_CHUNK_SIZE 18 // chunk header and 10 bytes of payload
16
+ #define MAX_RIFF_SIZE 0xfffffff6ULL // the RIFF size is a 32-bit even number
17
+
18
+ // VP8X flags
19
+ #define FLAG_ICCP 0x20
20
+ #define FLAG_ALPHA 0x10
21
+ #define FLAG_EXIF 0x08
22
+ #define FLAG_XMP 0x04
23
+
24
+ void MetadataInit(Metadata* const metadata) {
25
+ memset(metadata, 0, sizeof(*metadata));
26
+ }
27
+
28
+ static void PayloadFree(MetadataPayload* const payload) {
29
+ free(payload->bytes);
30
+ payload->bytes = NULL;
31
+ payload->size = 0;
32
+ }
33
+
34
+ void MetadataFree(Metadata* const metadata) {
35
+ PayloadFree(&metadata->exif);
36
+ PayloadFree(&metadata->iccp);
37
+ PayloadFree(&metadata->xmp);
38
+ }
39
+
40
+ int MetadataCopy(const void* const data, size_t size,
41
+ MetadataPayload* const payload) {
42
+ if (payload->bytes != NULL || data == NULL || size == 0) return 1;
43
+ payload->bytes = (uint8_t*)malloc(size);
44
+ if (payload->bytes == NULL) return 0;
45
+ memcpy(payload->bytes, data, size);
46
+ payload->size = size;
47
+ return 1;
48
+ }
49
+
50
+ static int WriteLE(FILE* const out, uint32_t value, int bytes) {
51
+ uint8_t buffer[4];
52
+ int i;
53
+ for (i = 0; i < bytes; ++i) {
54
+ buffer[i] = (uint8_t)((value >> (8 * i)) & 0xff);
55
+ }
56
+ return fwrite(buffer, (size_t)bytes, 1, out) == 1;
57
+ }
58
+
59
+ // Chunk payloads have an even size; odd ones get one zero byte of padding.
60
+ static uint64_t ChunkSize(const MetadataPayload* const payload) {
61
+ return CHUNK_HEADER_SIZE + (uint64_t)payload->size + (payload->size & 1);
62
+ }
63
+
64
+ static int WriteChunk(FILE* const out, const char* const tag,
65
+ const MetadataPayload* const payload) {
66
+ const uint8_t zero = 0;
67
+ int ok = (fwrite(tag, TAG_SIZE, 1, out) == 1);
68
+ ok = ok && WriteLE(out, (uint32_t)payload->size, 4);
69
+ ok = ok && (fwrite(payload->bytes, payload->size, 1, out) == 1);
70
+ if (payload->size & 1) ok = ok && (fwrite(&zero, 1, 1, out) == 1);
71
+ return ok;
72
+ }
73
+
74
+ int MetadataWriteWebP(FILE* const out, const uint8_t* webp, size_t webp_size,
75
+ int width, int height, const Metadata* const metadata,
76
+ int keep) {
77
+ const int write_exif = (keep & METADATA_EXIF) && metadata->exif.size > 0;
78
+ const int write_iccp = (keep & METADATA_ICC) && metadata->iccp.size > 0;
79
+ const int write_xmp = (keep & METADATA_XMP) && metadata->xmp.size > 0;
80
+ uint32_t flags = 0;
81
+ uint64_t metadata_size = 0;
82
+ uint64_t riff_size;
83
+ int has_vp8x;
84
+ int ok;
85
+
86
+ if (webp_size < RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE) return 0;
87
+ if (!write_exif && !write_iccp && !write_xmp) {
88
+ return fwrite(webp, webp_size, 1, out) == 1;
89
+ }
90
+
91
+ if (write_exif) { flags |= FLAG_EXIF; metadata_size += ChunkSize(&metadata->exif); }
92
+ if (write_iccp) { flags |= FLAG_ICCP; metadata_size += ChunkSize(&metadata->iccp); }
93
+ if (write_xmp) { flags |= FLAG_XMP; metadata_size += ChunkSize(&metadata->xmp); }
94
+
95
+ has_vp8x = !memcmp(webp + RIFF_HEADER_SIZE, "VP8X", TAG_SIZE);
96
+ riff_size = (uint64_t)webp_size - CHUNK_HEADER_SIZE +
97
+ (has_vp8x ? 0 : VP8X_CHUNK_SIZE) + metadata_size;
98
+ if (riff_size > MAX_RIFF_SIZE) return 0;
99
+
100
+ // "RIFF", the new size, then "WEBP"
101
+ ok = (fwrite(webp, TAG_SIZE, 1, out) == 1);
102
+ ok = ok && WriteLE(out, (uint32_t)riff_size, 4);
103
+ ok = ok && (fwrite(webp + CHUNK_HEADER_SIZE, TAG_SIZE, 1, out) == 1);
104
+ webp += RIFF_HEADER_SIZE;
105
+ webp_size -= RIFF_HEADER_SIZE;
106
+
107
+ if (has_vp8x) {
108
+ // lossy with alpha: the encoder already wrote a VP8X chunk, add our flags
109
+ uint8_t vp8x[VP8X_CHUNK_SIZE];
110
+ memcpy(vp8x, webp, VP8X_CHUNK_SIZE);
111
+ vp8x[CHUNK_HEADER_SIZE] |= (uint8_t)(flags & 0xff);
112
+ ok = ok && (fwrite(vp8x, VP8X_CHUNK_SIZE, 1, out) == 1);
113
+ webp += VP8X_CHUNK_SIZE;
114
+ webp_size -= VP8X_CHUNK_SIZE;
115
+ } else {
116
+ // lossless images keep their alpha bit in the VP8L header
117
+ if (!memcmp(webp, "VP8L", TAG_SIZE) && webp_size > CHUNK_HEADER_SIZE + 4 &&
118
+ (webp[CHUNK_HEADER_SIZE + 4] & (1 << 4))) {
119
+ flags |= FLAG_ALPHA;
120
+ }
121
+ ok = ok && (fwrite("VP8X", TAG_SIZE, 1, out) == 1);
122
+ ok = ok && WriteLE(out, VP8X_CHUNK_SIZE - CHUNK_HEADER_SIZE, 4);
123
+ ok = ok && WriteLE(out, flags, 4);
124
+ ok = ok && WriteLE(out, (uint32_t)(width - 1), 3);
125
+ ok = ok && WriteLE(out, (uint32_t)(height - 1), 3);
126
+ }
127
+
128
+ // the chunk order is fixed: ICCP, image data, EXIF, XMP
129
+ if (write_iccp) ok = ok && WriteChunk(out, "ICCP", &metadata->iccp);
130
+ ok = ok && (fwrite(webp, webp_size, 1, out) == 1);
131
+ if (write_exif) ok = ok && WriteChunk(out, "EXIF", &metadata->exif);
132
+ if (write_xmp) ok = ok && WriteChunk(out, "XMP ", &metadata->xmp);
133
+ return ok;
134
+ }
135
+
136
+ #if defined(__cplusplus) || defined(c_plusplus)
137
+ } // extern "C"
138
+ #endif
@@ -0,0 +1,49 @@
1
+ #ifndef _WEBP_FFI_METADATA_H_
2
+ #define _WEBP_FFI_METADATA_H_
3
+
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+ #include <stdio.h>
7
+
8
+ #if defined(__cplusplus) || defined(c_plusplus)
9
+ extern "C" {
10
+ #endif
11
+
12
+ // Which metadata to copy from the input picture into the WebP file.
13
+ #define METADATA_EXIF 1
14
+ #define METADATA_ICC 2
15
+ #define METADATA_XMP 4
16
+ #define METADATA_ALL (METADATA_EXIF | METADATA_ICC | METADATA_XMP)
17
+
18
+ typedef struct {
19
+ uint8_t* bytes;
20
+ size_t size;
21
+ } MetadataPayload;
22
+
23
+ typedef struct {
24
+ MetadataPayload exif;
25
+ MetadataPayload iccp;
26
+ MetadataPayload xmp;
27
+ } Metadata;
28
+
29
+ void MetadataInit(Metadata* const metadata);
30
+ void MetadataFree(Metadata* const metadata);
31
+
32
+ // Copies 'size' bytes into an empty 'payload'. Returns 1 on success, or when
33
+ // the payload already holds data (the first copy wins). Returns 0 when out of
34
+ // memory.
35
+ int MetadataCopy(const void* const data, size_t size, MetadataPayload* const payload);
36
+
37
+ // Writes a complete WebP file to 'out': the encoder output 'webp' plus the
38
+ // metadata selected by 'keep' (METADATA_* flags), using the extended format
39
+ // (VP8X) when any metadata is written. 'width' and 'height' are the size of the
40
+ // encoded picture. Returns 1 on success.
41
+ int MetadataWriteWebP(FILE* const out, const uint8_t* webp, size_t webp_size,
42
+ int width, int height, const Metadata* const metadata,
43
+ int keep);
44
+
45
+ #if defined(__cplusplus) || defined(c_plusplus)
46
+ } // extern "C"
47
+ #endif
48
+
49
+ #endif /* _WEBP_FFI_METADATA_H_ */
@@ -1,4 +1,5 @@
1
1
  #include "./pngdec.h"
2
+ #include "./util.h"
2
3
 
3
4
  #include <assert.h>
4
5
  #include <stdio.h>
@@ -12,10 +13,146 @@ static void PNGAPI error_function(png_structp png, png_const_charp dummy) {
12
13
  longjmp(png_jmpbuf(png), 1);
13
14
  }
14
15
 
15
- int UtilReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha) {
16
- png_structp png;
17
- png_infop info = NULL;
18
- png_infop end_info = NULL;
16
+ // libpng prints warnings to stderr by default. A library should stay quiet.
17
+ static void PNGAPI warning_function(png_structp png, png_const_charp dummy) {
18
+ (void)png;
19
+ (void)dummy;
20
+ }
21
+
22
+ // The metadata code below is based on libwebp's imageio/pngdec.c.
23
+
24
+ // Converts pairs of hex digits, possibly split by newlines, to 'length' bytes.
25
+ // Returns NULL for other characters or when there are fewer bytes.
26
+ static uint8_t* HexStringToBytes(const char* hexstring, size_t length) {
27
+ const char* src = hexstring;
28
+ size_t actual_length = 0;
29
+ uint8_t* const raw_data = (uint8_t*)malloc(length > 0 ? length : 1);
30
+ uint8_t* dst;
31
+
32
+ if (raw_data == NULL) return NULL;
33
+ for (dst = raw_data; actual_length < length && *src != '\0'; ++src) {
34
+ char* end;
35
+ char value[3];
36
+ if (*src == '\n') continue;
37
+ value[0] = *src++;
38
+ value[1] = *src;
39
+ value[2] = '\0';
40
+ *dst++ = (uint8_t)strtol(value, &end, 16);
41
+ if (end != value + 2) break;
42
+ ++actual_length;
43
+ }
44
+ if (actual_length != length) {
45
+ free(raw_data);
46
+ return NULL;
47
+ }
48
+ return raw_data;
49
+ }
50
+
51
+ // ImageMagick and ExifTool store metadata in text chunks as "raw profiles":
52
+ // "\n<name>\n<length>\n<hex data>\n".
53
+ static int ProcessRawProfile(const char* profile, size_t profile_length,
54
+ MetadataPayload* const payload) {
55
+ const char* src = profile;
56
+ char* end;
57
+ long length;
58
+
59
+ if (profile == NULL || profile_length == 0 || *src != '\n') return 0;
60
+ ++src;
61
+ while (*src != '\0' && *src++ != '\n') {} // skip the profile name
62
+ length = strtol(src, &end, 10);
63
+ if (*end != '\n') return 0;
64
+ ++end;
65
+ // two hex digits per byte, so a longer length cannot be right
66
+ if (length < 0 || (size_t)length > profile_length / 2) return 0;
67
+ payload->bytes = HexStringToBytes(end, (size_t)length);
68
+ if (payload->bytes == NULL) return 0;
69
+ payload->size = (size_t)length;
70
+ return 1;
71
+ }
72
+
73
+ static int CopyText(const char* text, size_t length, MetadataPayload* const payload) {
74
+ return MetadataCopy(text, length, payload);
75
+ }
76
+
77
+ static const struct {
78
+ const char* key;
79
+ int (*process)(const char* text, size_t length, MetadataPayload* const payload);
80
+ int kind; // METADATA_* flag
81
+ } kPNGTextMetadata[] = {
82
+ // https://exiftool.org/TagNames/PNG.html#TextualData
83
+ { "Raw profile type exif", ProcessRawProfile, METADATA_EXIF },
84
+ { "Raw profile type xmp", ProcessRawProfile, METADATA_XMP },
85
+ // ExifTool puts EXIF data in an APP1 profile too; ImageMagick in "app1".
86
+ { "Raw profile type APP1", ProcessRawProfile, METADATA_EXIF },
87
+ { "Raw profile type app1", ProcessRawProfile, METADATA_EXIF },
88
+ // XMP Specification Part 3, Section 3 #PNG
89
+ { "XML:com.adobe.xmp", CopyText, METADATA_XMP },
90
+ { NULL, NULL, 0 }
91
+ };
92
+
93
+ // Looks for metadata before and after the image data, preferring what comes
94
+ // first. Like libwebp, an eXIf chunk wins over EXIF in text chunks.
95
+ static int ExtractMetadataFromPNG(png_structp png, png_infop info,
96
+ png_infop end_info, Metadata* const metadata) {
97
+ int p;
98
+ for (p = 0; p < 2; ++p) {
99
+ png_infop const head = (p == 0) ? info : end_info;
100
+ png_textp text = NULL;
101
+ const png_uint_32 count = png_get_text(png, head, &text, NULL);
102
+ png_uint_32 i;
103
+
104
+ #ifdef PNG_eXIf_SUPPORTED
105
+ {
106
+ png_bytep exif = NULL;
107
+ png_uint_32 length = 0;
108
+ if (png_get_eXIf_1(png, head, &length, &exif) == PNG_INFO_eXIf &&
109
+ !MetadataCopy(exif, length, &metadata->exif)) {
110
+ return 0;
111
+ }
112
+ }
113
+ #endif
114
+
115
+ for (i = 0; i < count; ++i, ++text) {
116
+ int j;
117
+ for (j = 0; kPNGTextMetadata[j].key != NULL; ++j) {
118
+ if (text->key != NULL && !strcmp(text->key, kPNGTextMetadata[j].key)) {
119
+ MetadataPayload* const payload =
120
+ (kPNGTextMetadata[j].kind == METADATA_EXIF) ? &metadata->exif : &metadata->xmp;
121
+ // iTXt chunks keep their length in a separate field
122
+ const size_t length = (text->compression == PNG_ITXT_COMPRESSION_NONE ||
123
+ text->compression == PNG_ITXT_COMPRESSION_zTXt)
124
+ ? text->itxt_length : text->text_length;
125
+ // the first one wins; like cwebp, a broken one fails the read
126
+ if (payload->bytes == NULL &&
127
+ !kPNGTextMetadata[j].process(text->text, length, payload)) {
128
+ return 0;
129
+ }
130
+ break;
131
+ }
132
+ }
133
+ }
134
+
135
+ {
136
+ png_charp name;
137
+ int compression_type;
138
+ png_bytep profile;
139
+ png_uint_32 length;
140
+ if (png_get_iCCP(png, head, &name, &compression_type, &profile, &length) == PNG_INFO_iCCP &&
141
+ !MetadataCopy(profile, length, &metadata->iccp)) {
142
+ return 0;
143
+ }
144
+ }
145
+ }
146
+ return 1;
147
+ }
148
+
149
+ int UtilReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha,
150
+ Metadata* const metadata) {
151
+ // Locals that change after setjmp() must be volatile, otherwise their values
152
+ // are undefined when libpng jumps back to the error handler.
153
+ volatile png_structp png = NULL;
154
+ volatile png_infop info = NULL;
155
+ volatile png_infop end_info = NULL;
19
156
  int color_type, bit_depth, interlaced;
20
157
  int has_alpha;
21
158
  int num_passes;
@@ -23,17 +160,19 @@ int UtilReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha) {
23
160
  int ok = 0;
24
161
  png_uint_32 width, height, y;
25
162
  int stride;
26
- uint8_t* rgb = NULL;
163
+ size_t rgb_size;
164
+ uint8_t* volatile rgb = NULL;
27
165
 
28
- png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
166
+ png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
29
167
  if (png == NULL) {
30
168
  goto End;
31
169
  }
32
170
 
33
- png_set_error_fn(png, 0, error_function, NULL);
171
+ png_set_error_fn(png, NULL, error_function, warning_function);
34
172
  if (setjmp(png_jmpbuf(png))) {
35
173
  Error:
36
- png_destroy_read_struct(&png, &info, &end_info);
174
+ png_destroy_read_struct((png_structpp)&png, (png_infopp)&info,
175
+ (png_infopp)&end_info);
37
176
  goto End;
38
177
  }
39
178
 
@@ -72,28 +211,31 @@ int UtilReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha) {
72
211
 
73
212
  num_passes = png_set_interlace_handling(png);
74
213
  png_read_update_info(png, info);
75
- stride = (has_alpha ? 4 : 3) * width * sizeof(*rgb);
76
- rgb = (uint8_t*)malloc(stride * height);
214
+ if (!UtilCheckImageSize(width, height, has_alpha ? 4 : 3,
215
+ &stride, &rgb_size)) {
216
+ goto Error;
217
+ }
218
+ rgb = (uint8_t*)malloc(rgb_size);
77
219
  if (rgb == NULL) goto Error;
78
220
  for (p = 0; p < num_passes; ++p) {
79
221
  for (y = 0; y < height; ++y) {
80
- png_bytep row = rgb + y * stride;
222
+ png_bytep row = rgb + (size_t)y * stride;
81
223
  png_read_rows(png, &row, NULL, 1);
82
224
  }
83
225
  }
84
226
  png_read_end(png, end_info);
85
- png_destroy_read_struct(&png, &info, &end_info);
227
+ if (metadata != NULL && !ExtractMetadataFromPNG(png, info, end_info, metadata)) {
228
+ goto Error;
229
+ }
230
+ png_destroy_read_struct((png_structpp)&png, (png_infopp)&info,
231
+ (png_infopp)&end_info);
86
232
 
87
- pic->width = width;
88
- pic->height = height;
233
+ pic->width = (int)width;
234
+ pic->height = (int)height;
89
235
  pic->use_argb = 1;
90
236
  ok = has_alpha ? WebPPictureImportRGBA(pic, rgb, stride)
91
237
  : WebPPictureImportRGB(pic, rgb, stride);
92
238
 
93
- if (!ok) {
94
- goto Error;
95
- }
96
-
97
239
  End:
98
240
  free(rgb);
99
241
  return ok;
@@ -114,7 +256,7 @@ int UtilWritePNG(FILE* out_file, const WebPDecBuffer* const buffer) {
114
256
  png_uint_32 y;
115
257
 
116
258
  png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
117
- NULL, error_function, NULL);
259
+ NULL, error_function, warning_function);
118
260
  if (png == NULL) {
119
261
  return 0;
120
262
  }
@@ -209,4 +351,39 @@ int UtilWritePGM(FILE* fout, const WebPDecBuffer* const buffer) {
209
351
  if (width & 1) fputc(0, fout); // padding byte
210
352
  }
211
353
  return ok;
212
- }
354
+ }
355
+
356
+ // Based on WebPWriteYUV in libwebp's imageio/image_enc.c.
357
+ int UtilWriteYUV(FILE* fout, const WebPDecBuffer* const buffer) {
358
+ const int width = buffer->width;
359
+ const int height = buffer->height;
360
+ const WebPYUVABuffer* const yuv = &buffer->u.YUVA;
361
+ const uint8_t* src_y = yuv->y;
362
+ const uint8_t* src_u = yuv->u;
363
+ const uint8_t* src_v = yuv->v;
364
+ const uint8_t* src_a = yuv->a;
365
+ const int uv_width = (width + 1) / 2;
366
+ const int uv_height = (height + 1) / 2;
367
+ const int a_height = (src_a != NULL) ? height : 0;
368
+ int ok = 1;
369
+ int y;
370
+
371
+ if (src_y == NULL || src_u == NULL || src_v == NULL) return 0;
372
+ for (y = 0; ok && y < height; ++y) {
373
+ ok &= (fwrite(src_y, width, 1, fout) == 1);
374
+ src_y += yuv->y_stride;
375
+ }
376
+ for (y = 0; ok && y < uv_height; ++y) {
377
+ ok &= (fwrite(src_u, uv_width, 1, fout) == 1);
378
+ src_u += yuv->u_stride;
379
+ }
380
+ for (y = 0; ok && y < uv_height; ++y) {
381
+ ok &= (fwrite(src_v, uv_width, 1, fout) == 1);
382
+ src_v += yuv->v_stride;
383
+ }
384
+ for (y = 0; ok && y < a_height; ++y) {
385
+ ok &= (fwrite(src_a, width, 1, fout) == 1);
386
+ src_a += yuv->a_stride;
387
+ }
388
+ return ok;
389
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "webp/encode.h"
5
5
  #include "webp/decode.h"
6
+ #include "./metadata.h"
6
7
 
7
8
  #ifdef __cplusplus
8
9
  extern "C" {
@@ -10,14 +11,18 @@ extern "C" {
10
11
 
11
12
  // Reads a PNG from 'in_file', returning the decoded output in 'pic'.
12
13
  // If 'keep_alpha' is true and the PNG has an alpha channel, the output is RGBA
13
- // otherwise it will be RGB.
14
+ // otherwise it will be RGB. When 'metadata' is not NULL, the ICC profile, EXIF
15
+ // and XMP data are copied into it.
14
16
  // Returns true on success.
15
- int UtilReadPNG(FILE* in_file, struct WebPPicture* const pic, int keep_alpha);
17
+ int UtilReadPNG(FILE* in_file, struct WebPPicture* const pic, int keep_alpha,
18
+ Metadata* const metadata);
16
19
 
17
20
  int UtilWritePNG(FILE* out_file, const WebPDecBuffer* const buffer);
18
21
  int UtilWritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha);
19
22
  int UtilWriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer);
20
23
  int UtilWritePGM(FILE* fout, const WebPDecBuffer* const buffer);
24
+ // Raw planes: Y, then U and V (half size, rounded up), then alpha if any.
25
+ int UtilWriteYUV(FILE* fout, const WebPDecBuffer* const buffer);
21
26
 
22
27
  #ifdef __cplusplus
23
28
  } // extern "C"