Refactoring
[libopusenc.git] / src / picture.c
1 /* Copyright (C)2007-2013 Xiph.Org Foundation
2    File: picture.c
3
4    Redistribution and use in source and binary forms, with or without
5    modification, are permitted provided that the following conditions
6    are met:
7
8    - Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10
11    - Redistributions in binary form must reproduce the above copyright
12    notice, this list of conditions and the following disclaimer in the
13    documentation and/or other materials provided with the distribution.
14
15    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18    A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
19    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "picture.h"
36 #include "unicode_support.h"
37
38 static const char BASE64_TABLE[64]={
39   'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
40   'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
41   'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
42   'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
43 };
44
45 /*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
46   Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
47 static void base64_encode(char *dst, const char *src, int len){
48   unsigned s0;
49   unsigned s1;
50   unsigned s2;
51   int      ngroups;
52   int      i;
53   ngroups=len/3;
54   for(i=0;i<ngroups;i++){
55     s0=(unsigned char)src[3*i+0];
56     s1=(unsigned char)src[3*i+1];
57     s2=(unsigned char)src[3*i+2];
58     dst[4*i+0]=BASE64_TABLE[s0>>2];
59     dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
60     dst[4*i+2]=BASE64_TABLE[(s1&15)<<2|s2>>6];
61     dst[4*i+3]=BASE64_TABLE[s2&63];
62   }
63   len-=3*i;
64   if(len==1){
65     s0=(unsigned char)src[3*i+0];
66     dst[4*i+0]=BASE64_TABLE[s0>>2];
67     dst[4*i+1]=BASE64_TABLE[(s0&3)<<4];
68     dst[4*i+2]='=';
69     dst[4*i+3]='=';
70     i++;
71   }
72   else if(len==2){
73     s0=(unsigned char)src[3*i+0];
74     s1=(unsigned char)src[3*i+1];
75     dst[4*i+0]=BASE64_TABLE[s0>>2];
76     dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
77     dst[4*i+2]=BASE64_TABLE[(s1&15)<<2];
78     dst[4*i+3]='=';
79     i++;
80   }
81   dst[4*i]='\0';
82 }
83
84 /*A version of strncasecmp() that is guaranteed to only ignore the case of
85    ASCII characters.*/
86 static int oi_strncasecmp(const char *a, const char *b, int n){
87   int i;
88   for(i=0;i<n;i++){
89     int aval;
90     int bval;
91     int diff;
92     aval=a[i];
93     bval=b[i];
94     if(aval>='a'&&aval<='z') {
95       aval-='a'-'A';
96     }
97     if(bval>='a'&&bval<='z'){
98       bval-='a'-'A';
99     }
100     diff=aval-bval;
101     if(diff){
102       return diff;
103     }
104   }
105   return 0;
106 }
107
108 static int is_jpeg(const unsigned char *buf, size_t length){
109   return length>=3&&memcmp(buf,"\xFF\xD8\xFF",3)==0;
110 }
111
112 static int is_png(const unsigned char *buf, size_t length){
113   return length>=8&&memcmp(buf,"\x89PNG\x0D\x0A\x1A\x0A",8)==0;
114 }
115
116 static int is_gif(const unsigned char *buf, size_t length){
117   return length>=6
118    &&(memcmp(buf,"GIF87a",6)==0||memcmp(buf,"GIF89a",6)==0);
119 }
120
121 #define READ_U32_BE(buf) \
122     (((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff))
123
124 /*Tries to extract the width, height, bits per pixel, and palette size of a
125    PNG.
126   On failure, simply leaves its outputs unmodified.*/
127 static void extract_png_params(const unsigned char *data, size_t data_length,
128                         opus_uint32 *width, opus_uint32 *height,
129                         opus_uint32 *depth, opus_uint32 *colors,
130                         int *has_palette){
131   if(is_png(data,data_length)){
132     size_t offs;
133     offs=8;
134     while(data_length-offs>=12){
135       opus_uint32 chunk_len;
136       chunk_len=READ_U32_BE(data+offs);
137       if(chunk_len>data_length-(offs+12))break;
138       else if(chunk_len==13&&memcmp(data+offs+4,"IHDR",4)==0){
139         int color_type;
140         *width=READ_U32_BE(data+offs+8);
141         *height=READ_U32_BE(data+offs+12);
142         color_type=data[offs+17];
143         if(color_type==3){
144           *depth=24;
145           *has_palette=1;
146         }
147         else{
148           int sample_depth;
149           sample_depth=data[offs+16];
150           if(color_type==0)*depth=sample_depth;
151           else if(color_type==2)*depth=sample_depth*3;
152           else if(color_type==4)*depth=sample_depth*2;
153           else if(color_type==6)*depth=sample_depth*4;
154           *colors=0;
155           *has_palette=0;
156           break;
157         }
158       }
159       else if(*has_palette>0&&memcmp(data+offs+4,"PLTE",4)==0){
160         *colors=chunk_len/3;
161         break;
162       }
163       offs+=12+chunk_len;
164     }
165   }
166 }
167
168 /*Tries to extract the width, height, bits per pixel, and palette size of a
169    GIF.
170   On failure, simply leaves its outputs unmodified.*/
171 static void extract_gif_params(const unsigned char *data, size_t data_length,
172                         opus_uint32 *width, opus_uint32 *height,
173                         opus_uint32 *depth, opus_uint32 *colors,
174                         int *has_palette){
175   if(is_gif(data,data_length)&&data_length>=14){
176     *width=data[6]|data[7]<<8;
177     *height=data[8]|data[9]<<8;
178     /*libFLAC hard-codes the depth to 24.*/
179     *depth=24;
180     *colors=1<<((data[10]&7)+1);
181     *has_palette=1;
182   }
183 }
184
185
186 /*Tries to extract the width, height, bits per pixel, and palette size of a
187    JPEG.
188   On failure, simply leaves its outputs unmodified.*/
189 static void extract_jpeg_params(const unsigned char *data, size_t data_length,
190                          opus_uint32 *width, opus_uint32 *height,
191                          opus_uint32 *depth, opus_uint32 *colors,
192                          int *has_palette){
193   if(is_jpeg(data,data_length)){
194     size_t offs;
195     offs=2;
196     for(;;){
197       size_t segment_len;
198       int    marker;
199       while(offs<data_length&&data[offs]!=0xFF)offs++;
200       while(offs<data_length&&data[offs]==0xFF)offs++;
201       marker=data[offs];
202       offs++;
203       /*If we hit EOI* (end of image), or another SOI* (start of image),
204          or SOS (start of scan), then stop now.*/
205       if(offs>=data_length||(marker>=0xD8&&marker<=0xDA))break;
206       /*RST* (restart markers): skip (no segment length).*/
207       else if(marker>=0xD0&&marker<=0xD7)continue;
208       /*Read the length of the marker segment.*/
209       if(data_length-offs<2)break;
210       segment_len=data[offs]<<8|data[offs+1];
211       if(segment_len<2||data_length-offs<segment_len)break;
212       if(marker==0xC0||(marker>0xC0&&marker<0xD0&&(marker&3)!=0)){
213         /*Found a SOFn (start of frame) marker segment:*/
214         if(segment_len>=8){
215           *height=data[offs+3]<<8|data[offs+4];
216           *width=data[offs+5]<<8|data[offs+6];
217           *depth=data[offs+2]*data[offs+7];
218           *colors=0;
219           *has_palette=0;
220         }
221         break;
222       }
223       /*Other markers: skip the whole marker segment.*/
224       offs+=segment_len;
225     }
226   }
227 }
228
229 #define IMAX(a,b) ((a) > (b) ? (a) : (b))
230
231 static unsigned char *_ope_read_picture_file(const char *filename, const char *description, int *error, size_t *size, size_t *offset) {
232   FILE          *picture_file;
233   size_t         cbuf;
234   size_t         nbuf;
235   size_t         data_offset;
236   unsigned char *buf;
237   if (description == NULL) description = "";
238   picture_file=_ope_fopen(filename,"rb");
239   /*Buffer size: 8 static 4-byte fields plus 2 dynamic fields, plus the
240      file/URL data.
241     We reserve at least 10 bytes for the media type, in case we still need to
242      extract it from the file.*/
243   data_offset=32+strlen(description)+10;
244   buf=NULL;
245   /*Complicated case: we have a real file.
246     Read it in, attempt to parse the media type and image dimensions if
247      necessary, and validate what the user passed in.*/
248   if(picture_file==NULL){
249     *error = OPE_CANNOT_OPEN;
250     return NULL;
251   }
252   nbuf=data_offset;
253   /*Add a reasonable starting image file size.*/
254   cbuf=data_offset+65536;
255   for(;;){
256     unsigned char *new_buf;
257     size_t         nread;
258     new_buf=realloc(buf,cbuf);
259     if(new_buf==NULL){
260       fclose(picture_file);
261       free(buf);
262       *error = OPE_ALLOC_FAIL;
263       return NULL;
264     }
265     buf=new_buf;
266     nread=fread(buf+nbuf,1,cbuf-nbuf,picture_file);
267     nbuf+=nread;
268     if(nbuf<cbuf){
269       int file_error;
270       file_error=ferror(picture_file);
271       fclose(picture_file);
272       if(file_error){
273         free(buf);
274         *error = OPE_INVALID_PICTURE;
275         return NULL;
276       }
277       break;
278     }
279     if(cbuf==0xFFFFFFFF){
280       fclose(picture_file);
281       free(buf);
282       *error = OPE_INVALID_PICTURE;
283       return NULL;
284     }
285     else if(cbuf>0x7FFFFFFFU)cbuf=0xFFFFFFFFU;
286     else cbuf=cbuf<<1|1;
287   }
288   *size = nbuf;
289   *offset = data_offset;
290   return buf;
291 }
292
293 /*Parse a picture SPECIFICATION as given on the command-line.
294   spec: The specification.
295   error_message: Returns an error message on error.
296   seen_file_icons: Bit flags used to track if any pictures of type 1 or type 2
297    have already been added, to ensure only one is allowed.
298   Return: A Base64-encoded string suitable for use in a METADATA_BLOCK_PICTURE
299    tag.*/
300 char *_ope_parse_picture_specification(const char *filename, int picture_type, const char *description,
301                                   int *error, int *seen_file_icons){
302   opus_uint32  width;
303   opus_uint32  height;
304   opus_uint32  depth;
305   opus_uint32  colors;
306   unsigned char *buf;
307   const char    *mime_type;
308   char          *out;
309   size_t         nbuf;
310   size_t         data_offset;
311   size_t         data_length;
312   size_t         b64_length;
313   int          has_palette;
314   *error = OPE_OK;
315   if (picture_type < 0) picture_type=3;
316   if (picture_type > 20) {
317     *error=OPE_INVALID_PICTURE;
318     return NULL;
319   }
320   if(picture_type>=1&&picture_type<=2&&(*seen_file_icons&picture_type)){
321     *error=OPE_INVALID_PICTURE;
322     return NULL;
323   }
324   buf = _ope_read_picture_file(filename, description, error, &nbuf, &data_offset);
325   if (buf == NULL) return NULL;
326   data_length=nbuf-data_offset;
327   /*Try to extract the image dimensions/color information from the file.*/
328   width=height=depth=colors=0;
329   has_palette=-1;
330   {
331     if(is_jpeg(buf+data_offset,data_length)){
332       mime_type="image/jpeg";
333       extract_jpeg_params(buf+data_offset,data_length,
334        &width,&height,&depth,&colors,&has_palette);
335     }
336     else if(is_png(buf+data_offset,data_length)){
337       mime_type="image/png";
338       extract_png_params(buf+data_offset,data_length,
339        &width,&height,&depth,&colors,&has_palette);
340     }
341     else if(is_gif(buf+data_offset,data_length)){
342       mime_type="image/gif";
343       extract_gif_params(buf+data_offset,data_length,
344        &width,&height,&depth,&colors,&has_palette);
345     }
346     else{
347       free(buf);
348       *error = OPE_INVALID_PICTURE;
349       return NULL;
350     }
351   }
352   /*These fields MUST be set correctly OR all set to zero.
353     So if any of them (except colors, for which 0 is a valid value) are still
354      zero, clear the rest to zero.*/
355   if(width==0||height==0||depth==0)width=height=depth=colors=0;
356   if(picture_type==1&&(width!=32||height!=32
357    ||strlen(mime_type)!=9
358    ||oi_strncasecmp("image/png",mime_type,9)!=0)){
359     free(buf);
360     *error = OPE_INVALID_ICON;
361     return NULL;
362   }
363   /*Build the METADATA_BLOCK_PICTURE buffer.
364     We do this backwards from data_offset, because we didn't necessarily know
365      how big the media type string was before we read the data in.*/
366   data_offset-=4;
367   WRITE_U32_BE(buf+data_offset,(unsigned long)data_length);
368   data_offset-=4;
369   WRITE_U32_BE(buf+data_offset,colors);
370   data_offset-=4;
371   WRITE_U32_BE(buf+data_offset,depth);
372   data_offset-=4;
373   WRITE_U32_BE(buf+data_offset,height);
374   data_offset-=4;
375   WRITE_U32_BE(buf+data_offset,width);
376   data_offset-=strlen(description);
377   memcpy(buf+data_offset,description,strlen(description));
378   data_offset-=4;
379   WRITE_U32_BE(buf+data_offset,strlen(description));
380   data_offset-=strlen(mime_type);
381   memcpy(buf+data_offset,mime_type,strlen(mime_type));
382   data_offset-=4;
383   WRITE_U32_BE(buf+data_offset,strlen(mime_type));
384   data_offset-=4;
385   WRITE_U32_BE(buf+data_offset,picture_type);
386   data_length=nbuf-data_offset;
387   b64_length=BASE64_LENGTH(data_length);
388   out=(char *)malloc(b64_length+1);
389   if(out!=NULL){
390     base64_encode(out,(char *)buf+data_offset,data_length);
391     if(picture_type>=1&&picture_type<=2)*seen_file_icons|=picture_type;
392   } else {
393     *error = OPE_ALLOC_FAIL;
394   }
395   free(buf);
396   return out;
397 }