Fix a couple of Windows 2Gig file size issues.
[flac.git] / src / share / grabbag / picture.c
blob965a7b34984cea42d4eaa2cae4ef49139d5e61ea
1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006,2007,2008,2009 Josh Coalson
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #if HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include "share/alloc.h"
24 #include "share/grabbag.h"
25 #include "FLAC/assert.h"
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include "share/compat.h"
31 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
32 static char *local__strndup_(const char *s, size_t size)
34 char *x = safe_malloc_add_2op_(size, /*+*/1);
35 if(x) {
36 memcpy(x, s, size);
37 x[size] = '\0';
39 return x;
42 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
44 size_t i;
45 FLAC__uint32 val = 0;
47 picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
49 if(len == 0)
50 return true; /* empty string implies default to 'front cover' */
52 for(i = 0; i < len; i++) {
53 if(s[i] >= '0' && s[i] <= '9')
54 val = 10*val + (FLAC__uint32)(s[i] - '0');
55 else
56 return false;
59 if(i == len)
60 picture->type = val;
61 else
62 return false;
64 return true;
67 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
69 int state = 0;
70 size_t i;
71 FLAC__uint32 val = 0;
73 picture->width = picture->height = picture->depth = picture->colors = 0;
75 if(len == 0)
76 return true; /* empty string implies client wants to get info from the file itself */
78 for(i = 0; i < len; i++) {
79 if(s[i] == 'x') {
80 if(state == 0)
81 picture->width = val;
82 else if(state == 1)
83 picture->height = val;
84 else
85 return false;
86 state++;
87 val = 0;
89 else if(s[i] == '/') {
90 if(state == 2)
91 picture->depth = val;
92 else
93 return false;
94 state++;
95 val = 0;
97 else if(s[i] >= '0' && s[i] <= '9')
98 val = 10*val + (FLAC__uint32)(s[i] - '0');
99 else
100 return false;
103 if(state < 2)
104 return false;
105 else if(state == 2)
106 picture->depth = val;
107 else if(state == 3)
108 picture->colors = val;
109 else
110 return false;
111 if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
112 return false;
114 return true;
117 static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
119 if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
120 return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true);
121 else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6)))
122 return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true);
123 else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
124 return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true);
125 return false;
128 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
130 const FLAC__byte *data = picture->data;
131 FLAC__uint32 len = picture->data_length;
133 if(0 == strcmp(picture->mime_type, "image/png")) {
134 /* c.f. http://www.w3.org/TR/PNG/ */
135 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
136 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
137 return false;
138 /* try to find IHDR chunk */
139 data += 8;
140 len -= 8;
141 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
142 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
143 if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
144 unsigned color_type = data[17];
145 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
146 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
147 if(color_type == 3) {
148 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
149 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
150 * sample depth is always 8
152 picture->depth = 8 * 3u;
153 need_palette = true;
154 data += 12 + clen;
155 len -= 12 + clen;
157 else {
158 if(color_type == 0) /* greyscale, 1 sample per pixel */
159 picture->depth = (FLAC__uint32)data[16];
160 if(color_type == 2) /* truecolor, 3 samples per pixel */
161 picture->depth = (FLAC__uint32)data[16] * 3u;
162 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
163 picture->depth = (FLAC__uint32)data[16] * 2u;
164 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
165 picture->depth = (FLAC__uint32)data[16] * 4u;
166 picture->colors = 0;
167 return true;
170 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
171 picture->colors = clen / 3u;
172 return true;
174 else if(clen + 12 > len)
175 return false;
176 else {
177 data += 12 + clen;
178 len -= 12 + clen;
182 else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
183 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
184 if(len < 2 || memcmp(data, "\xff\xd8", 2))
185 return false;
186 data += 2;
187 len -= 2;
188 while(1) {
189 /* look for sync FF byte */
190 for( ; len > 0; data++, len--) {
191 if(*data == 0xff)
192 break;
194 if(len == 0)
195 return false;
196 /* eat any extra pad FF bytes before marker */
197 for( ; len > 0; data++, len--) {
198 if(*data != 0xff)
199 break;
201 if(len == 0)
202 return false;
203 /* if we hit SOS or EOI, bail */
204 if(*data == 0xda || *data == 0xd9)
205 return false;
206 /* looking for some SOFn */
207 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
208 data++; len--; /* skip marker byte */
209 if(len < 2)
210 return false;
211 else {
212 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
213 if(clen < 8 || len < clen)
214 return false;
215 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
216 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
217 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
218 picture->colors = 0;
219 return true;
222 /* else skip it */
223 else {
224 data++; len--; /* skip marker byte */
225 if(len < 2)
226 return false;
227 else {
228 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
229 if(clen < 2 || len < clen)
230 return false;
231 data += clen;
232 len -= clen;
237 else if(0 == strcmp(picture->mime_type, "image/gif")) {
238 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
239 if(len < 14)
240 return false;
241 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
242 return false;
243 #if 0
244 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
245 if(data[10] & 0x80 == 0)
246 return false;
247 #endif
248 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
249 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
250 #if 0
251 /* this value doesn't seem to be reliable... */
252 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
253 #else
254 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
255 picture->depth = 8u * 3u;
256 #endif
257 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
258 return true;
260 return false;
263 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
265 FLAC__StreamMetadata *obj;
266 int state = 0;
267 static const char *error_messages[] = {
268 "memory allocation error",
269 "invalid picture specification",
270 "invalid picture specification: can't parse resolution/color part",
271 "unable to extract resolution and color info from URL, user must set explicitly",
272 "unable to extract resolution and color info from file, user must set explicitly",
273 "error opening picture file",
274 "error reading picture file",
275 "invalid picture type",
276 "unable to guess MIME type from file, user must set explicitly",
277 "type 1 icon must be a 32x32 pixel PNG"
280 FLAC__ASSERT(0 != spec);
281 FLAC__ASSERT(0 != error_message);
283 /* double protection */
284 if(0 == spec)
285 return 0;
286 if(0 == error_message)
287 return 0;
289 *error_message = 0;
291 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
292 *error_message = error_messages[0];
293 return obj;
296 if(strchr(spec, '|')) { /* full format */
297 const char *p;
298 char *q;
299 for(p = spec; *error_message==0 && *p; ) {
300 if(*p == '|') {
301 switch(state) {
302 case 0: /* type */
303 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
304 *error_message = error_messages[7];
305 break;
306 case 1: /* mime type */
307 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
308 if(0 == (q = local__strndup_(spec, p-spec)))
309 *error_message = error_messages[0];
310 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
311 *error_message = error_messages[0];
313 break;
314 case 2: /* description */
315 if(0 == (q = local__strndup_(spec, p-spec)))
316 *error_message = error_messages[0];
317 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
318 *error_message = error_messages[0];
319 break;
320 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
321 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
322 *error_message = error_messages[2];
323 break;
324 default:
325 *error_message = error_messages[1];
326 break;
328 p++;
329 spec = p;
330 state++;
332 else
333 p++;
336 else { /* simple format, filename only, everything else guessed */
337 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
338 *error_message = error_messages[7];
339 /* leave MIME type to be filled in later */
340 /* leave description empty */
341 /* leave the rest to be filled in later: */
342 else if(!local__parse_resolution_("", 0, &obj->data.picture))
343 *error_message = error_messages[2];
344 else
345 state = 4;
348 /* parse filename, read file, try to extract resolution/color info if needed */
349 if(*error_message == 0) {
350 if(state != 4)
351 *error_message = error_messages[1];
352 else { /* 'spec' points to filename/URL */
353 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
354 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
355 *error_message = error_messages[0];
356 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
357 *error_message = error_messages[3];
359 else { /* regular picture file */
360 const FLAC__off_t size = grabbag__file_get_filesize(spec);
361 if(size < 0)
362 *error_message = error_messages[5];
363 else {
364 FLAC__byte *buffer = safe_malloc_(size);
365 if(0 == buffer)
366 *error_message = error_messages[0];
367 else {
368 FILE *f = fopen(spec, "rb");
369 if(0 == f) {
370 *error_message = error_messages[5];
371 free(buffer);
373 else {
374 if(fread(buffer, 1, size, f) != (size_t)size)
375 *error_message = error_messages[6];
376 fclose(f);
377 if(0 == *error_message) {
378 if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
379 *error_message = error_messages[6];
380 /* try to extract MIME type if user left it blank */
381 else if(*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
382 *error_message = error_messages[8];
383 /* try to extract resolution/color info if user left it blank */
384 else if((obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) && !local__extract_resolution_color_info_(&obj->data.picture))
385 *error_message = error_messages[4];
387 else {
388 free(buffer);
397 if(*error_message == 0) {
399 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
401 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
402 obj->data.picture.width != 32 ||
403 obj->data.picture.height != 32
406 *error_message = error_messages[9];
409 if(*error_message && obj) {
410 FLAC__metadata_object_delete(obj);
411 obj = 0;
414 return obj;