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