1 /*
2  * Author:      William Chia-Wei Cheng (bill.cheng@acm.org)
3  *
4  * Copyright (C) 2001-2009, William Chia-Wei Cheng.
5  *
6  * This file may be distributed under the terms of the Q Public License
7  * as defined by Trolltech AS of Norway and appearing in the file
8  * LICENSE.QPL included in the packaging of this file.
9  *
10  * THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING
11  * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
12  * PURPOSE.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
13  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
14  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
16  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  *
18  * @(#)$Header: /mm2/home/cvs/bc-src/tgif/pngtrans.c,v 1.11 2011/05/16 16:21:59 william Exp $
19  */
20 
21 #define _INCLUDE_FROM_PNGTRANS_C_
22 
23 #include "tgifdefs.h"
24 
25 #if (!defined(_NO_ZLIB) || defined(HAVE_LIBZ))
26 #include <zlib.h>
27 #endif /* (!defined(_NO_ZLIB) || defined(HAVE_LIBZ)) */
28 
29 #include "dialog.e"
30 #include "pngtrans.e"
31 #include "msg.e"
32 #include "strtbl.e"
33 #include "util.e"
34 
35 static int gnDebugPngParse=0;
36 
37 static unsigned char pngHeader[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
38 
39 static
PngShowOrPrintMsg(buf,use_msgbox)40 int PngShowOrPrintMsg(buf, use_msgbox)
41    char *buf;
42    int use_msgbox;
43 {
44    if (gnDebugPngParse > 0) {
45       return ShowOrPrintMsg(buf, use_msgbox);
46    }
47    return FALSE;
48 }
49 
50 static
VerifyPngHeader(fp)51 int VerifyPngHeader(fp)
52    FILE *fp;
53 {
54    unsigned char buf[8];
55 
56    if (fread(buf, sizeof(char), 8, fp) != 8) {
57       strcpy(gszMsgBox, TgLoadString(STID_BADPNG_SHORT_HEADER));
58       return PngShowOrPrintMsg(gszMsgBox, TRUE);
59    }
60    if (memcmp(pngHeader, buf, 8) != 0) {
61       strcpy(gszMsgBox, TgLoadString(STID_BADPNG_BAD_HEADER));
62       return PngShowOrPrintMsg(gszMsgBox, TRUE);
63    }
64    return TRUE;
65 }
66 
67 static
ReadPngDWord(pvalue32,fp)68 int ReadPngDWord(pvalue32, fp)
69    uint32_t *pvalue32;
70    FILE *fp;
71 {
72    unsigned char buf[4];
73    uint32_t value32=(uint32_t)0;
74    int bytes_read=fread(buf, sizeof(char), 4, fp);
75 
76    if (bytes_read != 4) {
77       return bytes_read;
78    }
79    value32 += (uint32_t)(buf[0]);
80    value32 <<= 8;
81    value32 += (uint32_t)(buf[1]);
82    value32 <<= 8;
83    value32 += (uint32_t)(buf[2]);
84    value32 <<= 8;
85    value32 += (uint32_t)(buf[3]);
86 
87    *pvalue32 = value32;
88 
89    return 4;
90 }
91 
92 static
ReadPngShortWord(pvalue16,fp)93 int ReadPngShortWord(pvalue16, fp)
94    uint16_t *pvalue16;
95    FILE *fp;
96 {
97    unsigned char buf[2];
98    uint16_t value16=(uint16_t)0;
99    int bytes_read=fread(buf, sizeof(char), 2, fp);
100 
101    if (bytes_read != 2) {
102       return bytes_read;
103    }
104    value16 += (uint16_t)(buf[0]);
105    value16 <<= 8;
106    value16 += (uint16_t)(buf[1]);
107 
108    *pvalue16 = value16;
109 
110    return 2;
111 }
112 
113 static
PngHexDump(buf,len,pn_index,pn_line_num)114 void PngHexDump(buf, len, pn_index, pn_line_num)
115    unsigned char *buf;
116    unsigned int len;
117    int *pn_index, *pn_line_num;
118 {
119    int index=(*pn_index), line_num=(*pn_line_num);
120 
121    while (len > 0) {
122       if (index == 0) {
123          fprintf(stdout, "%06x:  ", line_num<<4);
124       }
125       fprintf(stdout, "%02x ", (int)(unsigned int)(*buf));
126       index++;
127       if (index == 8) {
128          fprintf(stdout, " ");
129       } else if (index == 16) {
130          index = 0;
131          line_num++;
132          fprintf(stdout, "\n");
133       }
134       len--;
135       buf++;
136    }
137    *pn_index = index;
138    *pn_line_num = line_num;
139 }
140 
141 static
DoInflateForPng(in_buf,v32_length)142 int DoInflateForPng(in_buf, v32_length)
143    unsigned char *in_buf;
144    uint32_t v32_length;
145 {
146 #if (!defined(_NO_ZLIB) || defined(HAVE_LIBZ))
147    int ret=0, has_left_over=FALSE, index=0, line_num=0;
148    int bytes_left=(int)(unsigned int)v32_length;
149    unsigned int block_sz=0x4000;
150    unsigned int have=0;
151    z_stream strm;
152    unsigned char in[0x4000], out[0x4000];
153 
154    /* allocate inflate state */
155    memset(&strm, 0, sizeof(strm));
156    strm.zalloc = Z_NULL;
157    strm.zfree = Z_NULL;
158    strm.opaque = Z_NULL;
159    strm.avail_in = 0;
160    strm.next_in = Z_NULL;
161    ret = inflateInit(&strm);
162    if (ret != Z_OK) {
163       return FALSE;
164    }
165    do {
166       int bytes_to_read=min(block_sz, bytes_left);
167 
168       if (bytes_to_read == 0) break;
169       memcpy(in, in_buf, bytes_to_read);
170 
171       bytes_left -= bytes_to_read;
172       in_buf += bytes_to_read;
173 
174       strm.avail_in = bytes_to_read;
175       strm.next_in = in;
176 
177       /* run inflate() on input until output buffer not full */
178       do {
179          strm.avail_out = block_sz;
180          strm.next_out = out;
181          ret = inflate(&strm, Z_NO_FLUSH);
182          if (ret == Z_STREAM_ERROR) return FALSE;
183 
184          switch (ret) {
185          case Z_NEED_DICT:
186             ret = Z_DATA_ERROR;     /* and fall through */
187          case Z_DATA_ERROR:
188          case Z_MEM_ERROR:
189             (void)inflateEnd(&strm);
190             return FALSE;
191          }
192          have = block_sz - strm.avail_out;
193 
194          if (gnDebugPngParse > 1) PngHexDump(out, have, &index, &line_num);
195       } while (strm.avail_out == 0);
196 
197       /* done when inflate() says it's done */
198    } while (ret != Z_STREAM_END);
199 
200    if (has_left_over) {
201       (void)inflateEnd(&strm);
202       return FALSE;
203    }
204    /* clean up and return */
205    inflateEnd(&strm);
206 
207    if (ret == Z_STREAM_END) {
208       return TRUE;
209    }
210    return FALSE;
211 #else /* ~(!defined(_NO_ZLIB) || defined(HAVE_LIBZ)) */
212    return FALSE;
213 #endif /* (!defined(_NO_ZLIB) || defined(HAVE_LIBZ)) */
214 }
215 
216 static
SetPngFileTransColor(pphi,red,green,blue)217 void SetPngFileTransColor(pphi, red, green, blue)
218    PngHeaderInfo *pphi;
219    unsigned int red, green, blue;
220 {
221    pphi->trans_color_pixel_found = TRUE;
222    pphi->trans_color_pixel_red = (unsigned char)(red & 0x00ff);
223    pphi->trans_color_pixel_green = (unsigned char)(green & 0x00ff);
224    pphi->trans_color_pixel_blue = (unsigned char)(blue & 0x00ff);
225 }
226 
227 static
ReadChunk(pphi,psz_chunk_type,v32_length,fp)228 int ReadChunk(pphi, psz_chunk_type, v32_length, fp)
229    PngHeaderInfo *pphi;
230    char *psz_chunk_type;
231    uint32_t v32_length;
232    FILE *fp;
233 {
234    int i=0;
235    uint32_t bytes_read=0, bytes_left=v32_length;
236    uint16_t v16=(uint16_t)0;
237 
238    if (gnDebugPngParse > 0) {
239       /* debug, do not translate */
240       fprintf(stdout, "%s: length = %1d (0x%08x)\n", psz_chunk_type,
241             (int)(unsigned int)v32_length, (int)(unsigned int)v32_length);
242       fflush(stdout);
243    }
244    if (strncmp(psz_chunk_type, "IHDR", 4) == 0) {
245       if (v32_length == 13) {
246          if ((bytes_read=ReadPngDWord(&pphi->image_w, fp)) != 4) return FALSE;
247          if ((bytes_read=ReadPngDWord(&pphi->image_h, fp)) != 4) return FALSE;
248          if ((bytes_read=fread(&pphi->bit_depth, sizeof(char), 1, fp)) != 1) return FALSE;
249          if ((bytes_read=fread(&pphi->color_type, sizeof(char), 1, fp)) != 1) return FALSE;
250          if ((bytes_read=fread(&pphi->compression_method, sizeof(char), 1, fp)) != 1) return FALSE;
251          if ((bytes_read=fread(&pphi->filter_method, sizeof(char), 1, fp)) != 1) return FALSE;
252          if ((bytes_read=fread(&pphi->interlace_method, sizeof(char), 1, fp)) != 1) return FALSE;
253 
254          pphi->valid = TRUE;
255 
256          if (gnDebugPngParse > 0) {
257             /* debug, do not translate */
258             fprintf(stdout, "Width = %1d (0x%08x), Height = %1d (0x%08x), Color type = %1d, Bit depth = %1d, Compression = %1d, Filter = %1d, Interlace = %1d\n",
259                   (int)(unsigned int)pphi->image_w,
260                   (int)(unsigned int)pphi->image_w,
261                   (int)(unsigned int)pphi->image_h,
262                   (int)(unsigned int)pphi->image_h,
263                   (int)(unsigned int)pphi->color_type,
264                   (int)(unsigned int)pphi->bit_depth,
265                   (int)(unsigned int)pphi->compression_method,
266                   (int)(unsigned int)pphi->filter_method,
267                   (int)(unsigned int)pphi->interlace_method);
268          }
269          switch (pphi->color_type) {
270          case 0: break; /* Greyscale, allowed bit depths are 1, 2, 4, 8, 16 */
271          case 2: break; /* Truecolor, allowed bit depths are 8, 16 */
272          case 3: break; /* Indexed-colour, allowed bit depths are 1, 2, 4, 8 */
273 
274          case 4: /* don't know how to handle Greyscale with alpha */
275          case 6: /* don't know how to handle Truecolour with alpha */
276             if (gnDebugPngParse > 0) {
277                /* debug, do not translate */
278                fprintf(stdout, "Transparent color type unsupported.\n");
279             }
280             return FALSE;
281          }
282          return TRUE;
283       } else {
284          if (gnDebugPngParse > 0) {
285             /* debug, do not translate */
286             fprintf(stdout, "Unexpected IHDR chunk size: %1d (0x%08x).\n",
287                   (int)(unsigned int)v32_length, (int)(unsigned int)v32_length);
288          }
289          strcpy(gszMsgBox, TgLoadString(STID_BADPNG_BAD_HEADER));
290          return PngShowOrPrintMsg(gszMsgBox, TRUE);
291       }
292    } else if (strncmp(psz_chunk_type, "IDAT", 4) == 0) {
293       if (pphi->valid && pphi->compression_method == 0) {
294          if (gnDebugPngParse > 0) {
295             unsigned char *buf=(unsigned char *)malloc(v32_length);
296 
297             if (buf == NULL) FailAllocMessage();
298             if ((bytes_read=fread(buf, sizeof(char), v32_length, fp)) != v32_length) return FALSE;
299             if (!DoInflateForPng(buf, v32_length)) {
300                /* debug, do not translate */
301                fprintf(stdout, "DoInflateForPng() failed.\n");
302                fflush(stdout);
303                return FALSE;
304             }
305             free(buf);
306             return TRUE;
307          }
308       }
309    } else if (strncmp(psz_chunk_type, "PLTE", 4) == 0) {
310       if (pphi->valid && v32_length % 3 == 0) {
311          int num_entries=v32_length/3;
312          unsigned char *psz=NULL;
313 
314          pphi->palette = (unsigned char *)malloc(v32_length);
315          if (pphi->palette == NULL) FailAllocMessage();
316          if ((bytes_read=fread(pphi->palette, sizeof(char), v32_length, fp)) != v32_length) return FALSE;
317          pphi->palette_sz = v32_length;
318 
319          if (gnDebugPngParse > 0) {
320             psz = pphi->palette;
321             for (i=0; i < num_entries; i++) {
322                /* debug, do not translate */
323                fprintf(stdout, "%04d: %02x%02x%02x\n", i, (int)psz[0], (int)psz[1], (int)psz[2]);
324                psz = &psz[3];
325             }
326          }
327          return TRUE;
328       } else {
329          strcpy(gszMsgBox, TgLoadString(STID_BADPNG_BAD_PALETTE_LEN));
330          return PngShowOrPrintMsg(gszMsgBox, TRUE);
331       }
332    } else if (strncmp(psz_chunk_type, "tRNS", 4) == 0) {
333       if (pphi->valid && pphi->color_type == 0 && v32_length == 2) {
334          /* Greyscale, allowed bit depths are 1, 2, 4, 8, 16 */
335          if ((bytes_read=ReadPngShortWord(&v16, fp)) != 2) return FALSE;
336          switch (pphi->bit_depth) {
337          case 1: v16 = ((v16 & 0x0001) == 0x0001) ? 0x00ff : 0; break;
338          case 2:
339             v16 = v16 & 0x0003;
340             v16 = v16 | ((v16<<2)&0x000c) | ((v16<<4)&0x0030) | ((v16<<6)&0x00c0);
341             break;
342          case 4:
343             v16 = v16 & 0x000f;
344             v16 = v16 | ((v16<<4)&0x00f0);
345             break;
346          case 8: v16 = v16 & 0x00ff; break;
347          case 16: v16 = (v16>>8) & 0x00ff; break;
348          default: return FALSE;
349          }
350          SetPngFileTransColor(pphi, (unsigned int)v16, (unsigned int)v16, (unsigned int)v16);
351          return TRUE;
352       } else if (pphi->valid && pphi->color_type == 2 && v32_length == 6) {
353          /* Truecolor, allowed bit depths are 8, 16 */
354          uint16_t v16r=(uint16_t)0, v16g=(uint16_t)0, v16b=(uint16_t)0;
355 
356          if ((bytes_read=ReadPngShortWord(&v16r, fp)) != 2) return FALSE;
357          if ((bytes_read=ReadPngShortWord(&v16b, fp)) != 2) return FALSE;
358          if ((bytes_read=ReadPngShortWord(&v16g, fp)) != 2) return FALSE;
359          switch (pphi->bit_depth) {
360          case 8:
361             v16r = v16r & 0x00ff;
362             v16g = v16g & 0x00ff;
363             v16b = v16b & 0x00ff;
364             break;
365          case 16: v16 = (v16>>8) & 0x00ff; break;
366          default: return FALSE;
367          }
368          SetPngFileTransColor(pphi, (unsigned int)v16r, (unsigned int)v16g, (unsigned int)v16b);
369          return TRUE;
370       } else if (pphi->valid && pphi->color_type == 3 && v32_length == 1) {
371          /* Indexed-colour, allowed bit depths are 1, 2, 4, 8 */
372          unsigned char palette_index='\0', *psz=NULL;
373 
374          if ((bytes_read=fread(&palette_index, sizeof(char), 1, fp)) != 1) return FALSE;
375          if (pphi->palette == NULL || palette_index >= pphi->palette_sz) return FALSE;
376          psz = &pphi->palette[palette_index*3];
377          /*
378           * the bit_depth is used to control the number of entries in the
379           * palette, not the number of bits in RGB
380           */
381          switch (pphi->bit_depth) {
382          case 1: break;
383          case 2: break;
384          case 4: break;
385          case 8: break;
386          default: return FALSE;
387          }
388          SetPngFileTransColor(pphi, (unsigned int)psz[0], (unsigned int)psz[1], (unsigned int)psz[2]);
389          return TRUE;
390       } else {
391          if (gnDebugPngParse > 0) {
392             /* debug, do not translate */
393             fprintf(stdout, "Transparent color type unsupported.\n");
394          }
395          return FALSE;
396       }
397    }
398    if (fseek(fp, (long)bytes_left, SEEK_CUR) != 0) {
399       return FALSE;
400    }
401    return TRUE;
402 }
403 
404 static
ProcessChunk(pphi)405 int ProcessChunk(pphi)
406    PngHeaderInfo *pphi;
407 {
408    uint32_t v32_length=(uint32_t)0, v32_crc=(uint32_t)0;
409    int bytes_read=0;
410    char sz_chunk_type[5];
411    FILE *fp=pphi->fp;
412 
413    if ((bytes_read=ReadPngDWord(&v32_length, fp)) == 0) return FALSE;
414    if (bytes_read != 4) {
415       sprintf(gszMsgBox, TgLoadString(STID_BADPNG_BAD_CHUNK_LEN), bytes_read);
416       return PngShowOrPrintMsg(gszMsgBox, TRUE);
417    }
418    if ((bytes_read=fread(sz_chunk_type, sizeof(unsigned char), 4, fp)) != 4) {
419       strcpy(gszMsgBox, TgLoadString(STID_BADPNG_SHORT_CHUNK_TYPE));
420       return PngShowOrPrintMsg(gszMsgBox, TRUE);
421    }
422    sz_chunk_type[4] = '\0';
423    if (!ReadChunk(pphi, sz_chunk_type, v32_length, fp)) return FALSE;
424 
425    if ((bytes_read=ReadPngDWord(&v32_crc, fp)) != 4) {
426       strcpy(gszMsgBox, TgLoadString(STID_BADPNG_SHORT_CRC));
427       return PngShowOrPrintMsg(gszMsgBox, TRUE);
428    }
429    return TRUE;
430 }
431 
432 /* ----------------------- ParseTransColor() ----------------------- */
433 
ParseTransColor(char * buf,unsigned char * puch_trans_color_pixel_r,unsigned char * puch_trans_color_pixel_g,unsigned char * puch_trans_color_pixel_b)434 int ParseTransColor(char *buf, unsigned char *puch_trans_color_pixel_r,
435       unsigned char *puch_trans_color_pixel_g,
436       unsigned char *puch_trans_color_pixel_b)
437 {
438    char buf_r[5], buf_g[5], buf_b[5];
439    int r=0, g=0, b=0;
440 
441    if (*buf != '#' || strlen(buf) != 7) {
442       return FALSE;
443    }
444    buf_r[0] = '0';
445    buf_r[1] = '0';
446    buf_r[2] = buf[1];
447    buf_r[3] = buf[2];
448    buf_r[4] = '\0';
449 
450    buf_g[0] = '0';
451    buf_g[1] = '0';
452    buf_g[2] = buf[3];
453    buf_g[3] = buf[4];
454    buf_g[4] = '\0';
455 
456    buf_b[0] = '0';
457    buf_b[1] = '0';
458    buf_b[2] = buf[5];
459    buf_b[3] = buf[6];
460    buf_b[4] = '\0';
461 
462    if (sscanf(buf_r, "%x", &r) != 1) return FALSE;
463    if (sscanf(buf_g, "%x", &g) != 1) return FALSE;
464    if (sscanf(buf_b, "%x", &b) != 1) return FALSE;
465 
466    *puch_trans_color_pixel_r = (unsigned char)(r & 0x0ff);
467    *puch_trans_color_pixel_g = (unsigned char)(g & 0x0ff);
468    *puch_trans_color_pixel_b = (unsigned char)(b & 0x0ff);
469 
470    return TRUE;
471 }
472 
473 /* ----------------------- ResetPngHeaderInfo() ----------------------- */
474 
ResetPngHeaderInfo(pphi)475 void ResetPngHeaderInfo(pphi)
476    PngHeaderInfo *pphi;
477 {
478    memset(pphi, 0, sizeof(PngHeaderInfo));
479 }
480 
481 /* ----------------------- SetPngHeaderInfoForTransColor() ----------------------- */
482 
SetPngHeaderInfoForTransColor(pphi,trans_color_pixel_r,trans_color_pixel_g,trans_color_pixel_b)483 void SetPngHeaderInfoForTransColor(pphi, trans_color_pixel_r,
484       trans_color_pixel_g, trans_color_pixel_b)
485    PngHeaderInfo *pphi;
486    unsigned char trans_color_pixel_r, trans_color_pixel_g, trans_color_pixel_b;
487 {
488    ResetPngHeaderInfo(pphi);
489 
490    pphi->valid = TRUE;
491    pphi->trans_color_pixel_found = TRUE;
492    pphi->trans_color_pixel_red = trans_color_pixel_r;
493    pphi->trans_color_pixel_green = trans_color_pixel_g;
494    pphi->trans_color_pixel_blue = trans_color_pixel_b;
495 }
496 
497 /* ----------------------- PngFileGetTransColorInit() ----------------------- */
498 
PngFileGetTransColorInit(pphi,fname)499 int PngFileGetTransColorInit(pphi, fname)
500    PngHeaderInfo *pphi;
501    char *fname;
502 {
503    ResetPngHeaderInfo(pphi);
504 
505    pphi->fp = fopen(fname, "r");
506    if (pphi->fp == NULL) return FailToOpenMessage(fname, "r", NULL);
507    UtilStrCpyN(pphi->fname, sizeof(pphi->fname), fname);
508 
509    return TRUE;
510 }
511 
512 /* ----------------------- PngFileGetTransColor() ----------------------- */
513 
PngFileGetTransColor(pphi)514 int PngFileGetTransColor(pphi)
515    PngHeaderInfo *pphi;
516    /*
517     * returns TRUE if transparent pixel found
518     * returns FALSE if there is a problem parsing the PNG file or if it does not contain transparent pixels
519     */
520 {
521    if (!VerifyPngHeader(pphi->fp)) {
522       if (pphi->fp != NULL) fclose(pphi->fp);
523       ResetPngHeaderInfo(pphi);
524       return FALSE;
525    }
526    while (ProcessChunk(pphi)) ;
527 
528    if (pphi->fp != NULL) {
529       fclose(pphi->fp);
530       pphi->fp = NULL;
531    }
532    if (pphi->valid && pphi->palette_sz > 0 && pphi->palette != NULL) {
533       free(pphi->palette);
534    }
535    pphi->palette = NULL;
536 
537    return (pphi->valid && pphi->trans_color_pixel_found);
538 }
539 
540