OSDN Git Service

updated with TeX Live 2014.
[putex/putex.git] / src / dvipdfmx-pu / src / pngimage.c
1 /*  
2
3     This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.
4
5     Copyright (C) 2007-2012 by Jin-Hwan Cho and Shunsaku Hirata,
6     the dvipdfmx project team.
7     
8     Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>
9
10     This program is free software; you can redistribute it and/or modify
11     it under the terms of the GNU General Public License as published by
12     the Free Software Foundation; either version 2 of the License, or
13     (at your option) any later version.
14     
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
19     
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
23 */
24
25 #if HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 /*
30  * PNG SUPPORT
31  *
32  *  All bitdepth less than 16 is supported.
33  *  Supported color types are: PALETTE, RGB, GRAY, RGB_ALPHA, GRAY_ALPHA.
34  *  Supported ancillary chunks: tRNS, cHRM + gAMA, (sRGB), (iCCP)
35  * 
36  *  gAMA support is available only when cHRM exists. cHRM support is not
37  *  tested well. CalRGB/CalGray colorspace is used for PNG images that
38  *  have cHRM chunk (but not sRGB).
39  *
40  * LIMITATIONS
41  *
42  *   Recent version of PDF (>= 1.5) support 16 bpc, but 16 bit bitdepth PNG
43  *   images are automatically converted to 8 bit bitpedth image.
44  *
45  * TODO
46  *
47  *  sBIT ? iTXT, tEXT and tIME as MetaData ?, pHYS (see below)
48  *  16 bpc support for PDF-1.5. JBIG compression for monochrome image.
49  *  Predictor for deflate ?
50  */
51
52 #include "system.h"
53 #include "error.h"
54 #include "mem.h"
55
56 #include "dvipdfmx.h"
57
58 #include "pdfcolor.h"
59 #include "pdfobj.h"
60
61 #define PNG_DEBUG_STR "PNG"
62 #define PNG_DEBUG     3
63
64 #ifdef HAVE_LIBPNG
65
66 /*
67  * Write, MNG, Progressive not required.
68  */
69 #define PNG_NO_WRITE_SUPPORTED
70 #define PNG_NO_MNG_FEATURES
71 #define PNG_NO_PROGRESSIVE_READ
72 #if 0
73 /* 16_TO_8 required. */
74 #define PNG_NO_READ_TRANSFORMS
75 #endif
76
77 #include <png.h>
78 #include "pngimage.h"
79
80 #include "pdfximage.h"
81
82 #define PDF_TRANS_TYPE_NONE   0
83 #define PDF_TRANS_TYPE_BINARY 1
84 #define PDF_TRANS_TYPE_ALPHA  2
85
86 /* ColorSpace */
87 static pdf_obj *create_cspace_Indexed  (png_structp png_ptr, png_infop info_ptr);
88
89 /* CIE-Based: CalRGB/CalGray
90  *
91  * We ignore gAMA if cHRM is not found.
92  */
93 static pdf_obj *create_cspace_CalRGB   (png_structp png_ptr, png_infop info_ptr);
94 static pdf_obj *create_cspace_CalGray  (png_structp png_ptr, png_infop info_ptr);
95 static pdf_obj *make_param_Cal         (png_byte color_type,
96                                         double G,
97                                         double xw, double yw,
98                                         double xr, double yr,
99                                         double xg, double yg,
100                                         double xb, double yb);
101
102 /* sRGB:
103  *
104  * We (and PDF) do not have direct sRGB support. The sRGB color space can be
105  * precisely represented by ICC profile, but we use approximate CalRGB color
106  * space.
107  */
108 static pdf_obj *create_cspace_sRGB    (png_structp png_ptr, png_infop info_ptr);
109 static pdf_obj *get_rendering_intent  (png_structp png_ptr, png_infop info_ptr);
110
111 /* ICCBased:
112  *
113  * Not supported yet.
114  * Must check if ICC profile is valid and can be imported to PDF.
115  * There are few restrictions (should be applied to PNG too?) in ICC profile
116  * support in PDF. Some information should be obtained from profile.
117  */
118 static pdf_obj *create_cspace_ICCBased (png_structp png_ptr, png_infop info_ptr);
119
120 /* Transparency */
121 static int      check_transparency (png_structp png_ptr, png_infop info_ptr);
122 /* Color-Key Mask */
123 static pdf_obj *create_ckey_mask   (png_structp png_ptr, png_infop info_ptr);
124 /* Soft Mask:
125  *
126  * create_soft_mask() is for PNG_COLOR_TYPE_PALLETE.
127  * Images with alpha chunnel use strip_soft_mask().
128  * An object representing mask itself is returned.
129  */
130 static pdf_obj *create_soft_mask   (png_structp png_ptr, png_infop info_ptr,
131                                     png_bytep image_data_ptr,
132                                     png_uint_32 width, png_uint_32 height);
133 static pdf_obj *strip_soft_mask    (png_structp png_ptr, png_infop info_ptr,
134                                     png_bytep image_data_ptr,
135                                     png_uint_32p rowbytes_ptr,
136                                     png_uint_32 width, png_uint_32 height);
137
138 /* Read image body */
139 static void read_image_data (png_structp png_ptr, png_infop info_ptr,
140                              png_bytep dest_ptr,
141                              png_uint_32 height, png_uint_32 rowbytes);
142
143 int
144 check_for_png (FILE *png_file) 
145 {
146   unsigned char sigbytes[4];
147
148   rewind (png_file);
149   if (fread (sigbytes, 1, sizeof(sigbytes), png_file) !=
150       sizeof(sigbytes) ||
151       (png_sig_cmp (sigbytes, 0, sizeof(sigbytes))))
152     return 0;
153   else
154     return 1;
155 }
156
157 int
158 png_include_image (pdf_ximage *ximage, FILE *png_file)
159 {
160   pdf_obj  *stream;
161   pdf_obj  *stream_dict;
162   pdf_obj  *colorspace, *mask, *intent;
163   png_bytep stream_data_ptr;
164   int       trans_type;
165   ximage_info info;
166   /* Libpng stuff */
167   png_structp png_ptr;
168   png_infop   png_info_ptr;
169   png_byte    bpc, color_type;
170   png_uint_32 width, height, rowbytes, xppm, yppm;
171
172   pdf_ximage_init_image_info(&info);
173
174   stream      = NULL;
175   stream_dict = NULL;
176   colorspace  = mask = intent = NULL;
177
178   rewind (png_file);
179   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
180   if (png_ptr == NULL || 
181       (png_info_ptr = png_create_info_struct (png_ptr)) == NULL) {
182     WARN("%s: Creating Libpng read/info struct failed.", PNG_DEBUG_STR);
183     if (png_ptr)
184       png_destroy_read_struct(&png_ptr, NULL, NULL);
185     return -1;
186   }
187
188   /* Inititializing file IO. */
189   png_init_io (png_ptr, png_file);
190
191   /* Read PNG info-header and get some info. */
192   png_read_info(png_ptr, png_info_ptr);
193   color_type = png_get_color_type  (png_ptr, png_info_ptr);
194   width      = png_get_image_width (png_ptr, png_info_ptr);
195   height     = png_get_image_height(png_ptr, png_info_ptr);
196   bpc        = png_get_bit_depth   (png_ptr, png_info_ptr);
197   xppm       = png_get_x_pixels_per_meter(png_ptr, png_info_ptr);
198   yppm       = png_get_y_pixels_per_meter(png_ptr, png_info_ptr);
199
200   /* We do not need 16-bpc color. Ask libpng to convert down to 8-bpc. */
201   if (bpc > 8) {
202     png_set_strip_16(png_ptr);
203     bpc = 8;
204   }
205
206   trans_type = check_transparency(png_ptr, png_info_ptr);
207   /* check_transparency() does not do updata_info() */
208   png_read_update_info(png_ptr, png_info_ptr);
209   rowbytes = png_get_rowbytes(png_ptr, png_info_ptr);
210
211   /* Values listed below will not be modified in the remaining process. */
212   info.width  = width;
213   info.height = height;
214   info.bits_per_component = bpc;
215
216   if (compat_mode)
217     info.xdensity = info.ydensity = 72.0 / 100.0;
218   else {
219     if (xppm > 0)
220       info.xdensity = 72.0 / 0.0254 / xppm;
221     if (yppm > 0)
222       info.ydensity = 72.0 / 0.0254 / yppm;
223   }
224
225   stream      = pdf_new_stream (STREAM_COMPRESS);
226   stream_dict = pdf_stream_dict(stream);
227
228   stream_data_ptr = (png_bytep) NEW(rowbytes*height, png_byte);
229   read_image_data(png_ptr, png_info_ptr, stream_data_ptr, height, rowbytes);
230
231   /* Non-NULL intent means there is valid sRGB chunk. */
232   intent = get_rendering_intent(png_ptr, png_info_ptr);
233   if (intent)
234     pdf_add_dict(stream_dict, pdf_new_name("Intent"), intent);
235
236   switch (color_type) {
237   case PNG_COLOR_TYPE_PALETTE:
238
239     colorspace = create_cspace_Indexed(png_ptr, png_info_ptr);
240
241     switch (trans_type) {
242     case PDF_TRANS_TYPE_BINARY:
243       /* Color-key masking */
244       mask = create_ckey_mask(png_ptr, png_info_ptr);
245       break;
246     case PDF_TRANS_TYPE_ALPHA:
247       /* Soft mask */
248       mask = create_soft_mask(png_ptr, png_info_ptr, stream_data_ptr, width, height);
249       break;
250     default:
251       /* Nothing to be done here.
252        * No tRNS chunk or image already composited with background color.
253        */
254       break;
255     }
256     break;
257   case PNG_COLOR_TYPE_RGB:
258   case PNG_COLOR_TYPE_RGB_ALPHA:
259
260     if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_iCCP))
261       colorspace = create_cspace_ICCBased(png_ptr, png_info_ptr);
262     else if (intent) {
263       colorspace = create_cspace_sRGB(png_ptr, png_info_ptr);
264     } else {
265       colorspace = create_cspace_CalRGB(png_ptr, png_info_ptr);
266     }
267     if (!colorspace)
268       colorspace = pdf_new_name("DeviceRGB");
269
270     switch (trans_type) {
271     case PDF_TRANS_TYPE_BINARY:
272       if (color_type != PNG_COLOR_TYPE_RGB)
273         ERROR("Unexpected error in png_include_image().");
274       mask = create_ckey_mask(png_ptr, png_info_ptr);
275       break;
276     /* rowbytes changes 4 to 3 at here */
277     case PDF_TRANS_TYPE_ALPHA:
278       if (color_type != PNG_COLOR_TYPE_RGB_ALPHA)
279         ERROR("Unexpected error in png_include_image().");
280       mask = strip_soft_mask(png_ptr, png_info_ptr,
281                              stream_data_ptr, &rowbytes, width, height);
282       break;
283     default:
284       mask = NULL;
285     }
286     info.num_components = 3;
287     break;
288
289   case PNG_COLOR_TYPE_GRAY:
290   case PNG_COLOR_TYPE_GRAY_ALPHA:
291
292     if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_iCCP))
293       colorspace = create_cspace_ICCBased(png_ptr, png_info_ptr);
294     else if (intent) {
295       colorspace = create_cspace_sRGB(png_ptr, png_info_ptr);
296     } else {
297       colorspace = create_cspace_CalGray(png_ptr, png_info_ptr);
298     }
299     if (!colorspace)
300       colorspace = pdf_new_name("DeviceGray");
301
302     switch (trans_type) {
303     case PDF_TRANS_TYPE_BINARY:
304       if (color_type != PNG_COLOR_TYPE_GRAY)
305         ERROR("Unexpected error in png_include_image().");
306       mask = create_ckey_mask(png_ptr, png_info_ptr);
307       break;
308     case PDF_TRANS_TYPE_ALPHA:
309       if (color_type != PNG_COLOR_TYPE_GRAY_ALPHA)
310         ERROR("Unexpected error in png_include_image().");
311       mask = strip_soft_mask(png_ptr, png_info_ptr,
312                              stream_data_ptr, &rowbytes, width, height);
313       break;
314     default:
315       mask = NULL;
316     }
317     info.num_components = 1;
318     break;
319
320   default:
321     WARN("%s: Unknown PNG colortype %d.", PNG_DEBUG_STR, color_type);
322   }
323   pdf_add_dict(stream_dict, pdf_new_name("ColorSpace"), colorspace);
324
325   pdf_add_stream(stream, stream_data_ptr, rowbytes*height);
326   RELEASE(stream_data_ptr);
327
328   if (mask) {
329     if (trans_type == PDF_TRANS_TYPE_BINARY)
330       pdf_add_dict(stream_dict, pdf_new_name("Mask"), mask);
331     else if (trans_type == PDF_TRANS_TYPE_ALPHA) {
332       pdf_add_dict(stream_dict, pdf_new_name("SMask"), pdf_ref_obj(mask));
333       pdf_release_obj(mask);
334     } else {
335       WARN("%s: You found a bug in pngimage.c.", PNG_DEBUG_STR);
336       pdf_release_obj(mask);
337     }
338   }
339
340   png_read_end(png_ptr, NULL);
341
342   /* Cleanup */
343   if (png_info_ptr)
344     png_destroy_info_struct(png_ptr, &png_info_ptr);
345   if (png_ptr)
346     png_destroy_read_struct(&png_ptr, NULL, NULL);
347
348   pdf_ximage_set_image(ximage, &info, stream);
349
350   return 0;
351 }
352
353 /* 
354  * The returned value trans_type is the type of transparency to be used for
355  * this image. Possible values are:
356  *
357  *   PDF_TRANS_TYPE_NONE    No Masking will be used/required.
358  *   PDF_TRANS_TYPE_BINARY  Pixels are either fully opaque/fully transparent.
359  *   PDF_TRANS_TYPE_ALPHA   Uses alpha channel, requies SMask.(PDF-1.4)
360  *
361  * check_transparency() must check the current setting of output PDF version
362  * and must choose appropriate trans_type value according to PDF version of
363  * current output PDF document.
364  *
365  * If the PDF version is less than 1.3, no transparency is supported for this
366  * version of PDF, hence PDF_TRANS_TYPE_NONE must be returned. And when the PDF
367  * version is equal to 1.3, possible retrun values are PDF_TRANS_TYPE_BINARY or
368  * PDF_TRANS_TYPE_NONE. The latter case arises when PNG file uses alpha channel
369  * explicitly (color type PNG_COLOR_TYPE_XXX_ALPHA), or the tRNS chunk for the
370  * PNG_COLOR_TYPE_PALETTE image contains intermediate values of opacity.
371  *
372  * Finally, in the case of PDF version 1.4, all kind of translucent pixels can
373  * be represented with Soft-Mask.
374  */
375
376 static int
377 check_transparency (png_structp png_ptr, png_infop info_ptr)
378 {
379   int           trans_type;
380   unsigned      pdf_version;
381   png_byte      color_type;
382   png_color_16p trans_values;
383   png_bytep     trans;
384   int           num_trans;
385
386   pdf_version = pdf_get_version();
387   color_type  = png_get_color_type(png_ptr, info_ptr);
388
389   /*
390    * First we set trans_type to appropriate value for PNG image.
391    */
392   if (color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
393       color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
394     trans_type = PDF_TRANS_TYPE_ALPHA;
395   } else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) &&
396              png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values)) {
397     /* Have valid tRNS chunk. */
398     switch (color_type) {
399     case PNG_COLOR_TYPE_PALETTE:
400       /* Use color-key mask if possible. */ 
401       trans_type = PDF_TRANS_TYPE_BINARY;
402       while (num_trans-- > 0) {
403         if (trans[num_trans] != 0x00 && trans[num_trans] != 0xff) {
404           /* This seems not binary transparency */
405           trans_type = PDF_TRANS_TYPE_ALPHA;
406           break;
407         }
408       }
409       break;
410     case PNG_COLOR_TYPE_GRAY:
411     case PNG_COLOR_TYPE_RGB:
412       /* RGB or GRAY, single color specified by trans_values is transparent. */
413       trans_type = PDF_TRANS_TYPE_BINARY;
414       break;
415     default:
416       /* Else tRNS silently ignored. */
417       trans_type = PDF_TRANS_TYPE_NONE;
418     }
419   } else { /* no transparency */
420     trans_type = PDF_TRANS_TYPE_NONE;
421   }
422
423   /*
424    * Now we check PDF version.
425    * We can convert alpha cahnnels to explicit mask via user supplied alpha-
426    * threshold value. But I will not do that.
427    */
428   if (( pdf_version < 3 && trans_type != PDF_TRANS_TYPE_NONE   ) ||
429       ( pdf_version < 4 && trans_type == PDF_TRANS_TYPE_ALPHA )) {
430     /*
431      *   No transparency supported but PNG uses transparency, or Soft-Mask
432      * required but no support for it is available in this version of PDF.
433      * We must do pre-composition of image with the background image here. But,
434      * we cannot do that in general since dvipdfmx is not a rasterizer. What we
435      * can do here is to composite image with a rectangle filled with the
436      * background color. However, images are stored as an Image XObject which
437      * can be referenced anywhere in the PDF document content. Hence, we cannot
438      * know the correct background color at this time. So we will choose white
439      * as background color, which is most probable color in our cases.
440      * We ignore bKGD chunk.
441      */
442     png_color_16 bg;
443     bg.red = 255; bg.green = 255; bg.blue  = 255; bg.gray = 255; bg.index = 0;
444     png_set_background(png_ptr, &bg, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
445     WARN("%s: Transparency will be ignored. (no support in PDF ver. < 1.3)", PNG_DEBUG_STR);
446     if (pdf_version < 3)
447       WARN("%s: Please use -V 3 option to enable binary transparency support.", PNG_DEBUG_STR);
448     if (pdf_version < 4)
449       WARN("%s: Please use -V 4 option to enable full alpha channel support.", PNG_DEBUG_STR);
450     trans_type = PDF_TRANS_TYPE_NONE;
451   }
452
453   return trans_type;
454 }
455
456 /*
457  * sRGB:
458  *
459  *   If sRGB chunk is present, cHRM and gAMA chunk must be ignored.
460  *
461  */
462 static pdf_obj *
463 get_rendering_intent (png_structp png_ptr, png_infop info_ptr)
464 {
465   pdf_obj *intent;
466   int      srgb_intent;
467
468   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB) &&
469       png_get_sRGB (png_ptr, info_ptr, &srgb_intent)) {
470     switch (srgb_intent) {
471     case PNG_sRGB_INTENT_SATURATION:
472       intent = pdf_new_name("Saturation");
473       break;
474     case PNG_sRGB_INTENT_PERCEPTUAL:
475       intent = pdf_new_name("Perceptual");
476       break;
477     case PNG_sRGB_INTENT_ABSOLUTE:
478       intent = pdf_new_name("AbsoluteColorimetric");
479       break;
480     case PNG_sRGB_INTENT_RELATIVE:
481       intent = pdf_new_name("RelativeColorimetric");
482       break;
483     default:
484       WARN("%s: Invalid value in PNG sRGB chunk: %d", PNG_DEBUG_STR, srgb_intent);
485       intent = NULL;
486     }
487   } else
488     intent = NULL;
489
490   return intent;
491 }
492
493 /* Approximated sRGB */
494 static pdf_obj *
495 create_cspace_sRGB (png_structp png_ptr, png_infop info_ptr)
496 {
497   pdf_obj  *colorspace;
498   pdf_obj  *cal_param;
499   png_byte  color_type;
500
501   color_type = png_get_color_type(png_ptr, info_ptr);
502
503   /* Parameters taken from PNG spec. section 4.2.2.3. */
504   cal_param = make_param_Cal(color_type,
505                              2.2,
506                              0.3127, 0.329,
507                              0.64, 0.33, 0.3, 0.6, 0.15, 0.06);
508   if (!cal_param)
509     return NULL;
510
511   colorspace = pdf_new_array();
512
513   switch (color_type) {
514   case PNG_COLOR_TYPE_RGB:
515   case PNG_COLOR_TYPE_RGB_ALPHA:
516   case PNG_COLOR_TYPE_PALETTE:
517     pdf_add_array(colorspace, pdf_new_name("CalRGB"));
518     break;
519   case PNG_COLOR_TYPE_GRAY:
520   case PNG_COLOR_TYPE_GRAY_ALPHA:
521     pdf_add_array(colorspace, pdf_new_name("CalGray"));
522     break;
523   }
524   pdf_add_array(colorspace, cal_param);
525
526   return colorspace;
527 }
528
529 static pdf_obj *
530 create_cspace_ICCBased (png_structp png_ptr, png_infop info_ptr)
531 {
532   pdf_obj   *colorspace;
533   int        csp_id, colortype;
534   png_byte   color_type;
535   png_charp  name;
536   int        compression_type;  /* Manual page for libpng does not
537                                  * clarify whether profile data is inflated by libpng.
538                                  */
539 #if PNG_LIBPNG_VER_MINOR < 5
540   png_charp   profile;
541 #else
542   png_bytep   profile;
543 #endif
544   png_uint_32 proflen;
545
546   if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP) ||
547       !png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile, &proflen))
548     return NULL;
549
550   color_type = png_get_color_type(png_ptr, info_ptr);
551
552   if (color_type & PNG_COLOR_MASK_COLOR) {
553     colortype = PDF_COLORSPACE_TYPE_RGB;
554 #if 0
555     alternate = create_cspace_CalRGB(png_ptr, info_ptr);
556 #endif
557   } else {
558     colortype = PDF_COLORSPACE_TYPE_GRAY;
559 #if 0
560     alternate = create_cspace_CalGray(png_ptr, info_ptr);
561 #endif
562   }
563
564 #if 0
565   if (alternate)
566     pdf_add_dict(dict, pdf_new_name("Alternate"), alternate);
567 #endif
568
569   if (iccp_check_colorspace(colortype, profile, proflen) < 0)
570     colorspace = NULL;
571   else {
572     csp_id = iccp_load_profile(name, profile, proflen);
573     if (csp_id < 0) {
574       colorspace = NULL;
575     } else {
576       colorspace = pdf_get_colorspace_reference(csp_id);
577     }
578   }
579
580   /* Rendering intent ... */
581
582   return colorspace;
583 }
584
585 /*
586  * gAMA, cHRM:
587  *
588  *   If cHRM is present, we use CIE-Based color space. gAMA is also used here
589  * if available.
590  */
591
592 #define INVALID_CHRM_VALUE(xw,yw,xr,yr,xg,yg,xb,yb) (\
593   (xw) <= 0.0 || (yw) < 1.0e-10 || \
594   (xr) < 0.0  || (yr) < 0.0 || (xg) < 0.0 || (yg) < 0.0 || \
595   (xb) < 0.0  || (yb) < 0.0)
596
597 static pdf_obj *
598 create_cspace_CalRGB (png_structp png_ptr, png_infop info_ptr)
599 {
600   pdf_obj *colorspace;
601   pdf_obj *cal_param;
602   double   xw, yw, xr, yr, xg, yg, xb, yb;
603   double   G;
604
605   if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM) ||
606       !png_get_cHRM(png_ptr, info_ptr, &xw, &yw, &xr, &yr, &xg, &yg, &xb, &yb))
607     return NULL;
608
609   if (xw <= 0.0 || yw < 1.0e-10 ||
610       xr < 0.0  || yr < 0.0 || xg < 0.0 || yg < 0.0 || xb < 0.0 || yb < 0.0) {
611     WARN("%s: Invalid cHRM chunk parameters found.", PNG_DEBUG_STR);
612     return NULL;
613   }
614
615   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
616       png_get_gAMA (png_ptr, info_ptr, &G)) {
617     if (G < 1.0e-2) {
618       WARN("%s: Unusual Gamma value: %g", PNG_DEBUG_STR, G);
619       return NULL;
620     }
621     G = 1.0 / G; /* Gamma is inverted. */
622   } else {
623     G = 1.0;
624   }
625
626   cal_param = make_param_Cal(PNG_COLOR_TYPE_RGB, G, xw, yw, xr, yr, xg, yg, xb, yb);
627
628   if (!cal_param)
629     return NULL;
630
631   colorspace = pdf_new_array();
632   pdf_add_array(colorspace, pdf_new_name("CalRGB"));
633   pdf_add_array(colorspace, cal_param);
634
635   return colorspace;
636 }
637
638 static pdf_obj *
639 create_cspace_CalGray (png_structp png_ptr, png_infop info_ptr)
640 {
641   pdf_obj *colorspace;
642   pdf_obj *cal_param;
643   double   xw, yw, xr, yr, xg, yg, xb, yb;
644   double   G;
645
646   if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM) ||
647       !png_get_cHRM(png_ptr, info_ptr, &xw, &yw, &xr, &yr, &xg, &yg, &xb, &yb))
648     return NULL;
649
650   if (xw <= 0.0 || yw < 1.0e-10 ||
651       xr < 0.0  || yr < 0.0 || xg < 0.0 || yg < 0.0 || xb < 0.0 || yb < 0.0) {
652     WARN("%s: Invalid cHRM chunk parameters found.", PNG_DEBUG_STR);
653     return NULL;
654   }
655
656   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
657       png_get_gAMA (png_ptr, info_ptr, &G)) {
658     if (G < 1.0e-2) {
659       WARN("%s: Unusual Gamma value: %g", PNG_DEBUG_STR, G);
660       return NULL;
661     }
662     G = 1.0 / G; /* Gamma is inverted. */
663   } else {
664     G = 1.0;
665   }
666
667   cal_param = make_param_Cal(PNG_COLOR_TYPE_GRAY, G, xw, yw, xr, yr, xg, yg, xb, yb);
668
669   if (!cal_param)
670     return NULL;
671
672   colorspace = pdf_new_array();
673   pdf_add_array(colorspace, pdf_new_name("CalGray"));
674   pdf_add_array(colorspace, cal_param);
675
676   return colorspace;
677 }
678
679 static pdf_obj *
680 make_param_Cal (png_byte color_type,
681                 double G, /* Gamma */
682                 double xw, double yw,
683                 double xr, double yr, double xg, double yg, double xb, double yb)
684 {
685   pdf_obj *cal_param;
686   pdf_obj *white_point, *matrix, *dev_gamma;
687   double Xw, Yw, Zw; /* Yw = 1.0 */
688   double Xr, Xg, Xb, Yr, Yb, Yg, Zr, Zg, Zb;
689
690 #ifndef ABS
691 #define ABS(x) ((x) < 0 ? -(x) : (x))
692 #endif
693   /*
694    * TODO: Check validity
695    *
696    * Conversion found in
697    *
698    *  com.sixlegs.image.png - Java package to read and display PNG images
699    *  Copyright (C) 1998, 1999, 2001 Chris Nokleberg
700    *
701    *  http://www.sixlegs.com/software/png/
702    *
703    */
704   {
705     double zw, zr, zg, zb;
706     double fr, fg, fb;
707     double det;
708
709     /* WhitePoint */
710     zw = 1 - (xw + yw);
711     zr = 1 - (xr + yr); zg = 1 - (xg + yg); zb = 1 - (xb + yb);
712     Xw = xw / yw; Yw = 1.0; Zw = zw / yw;
713
714     /* Matrix */
715     det = xr * (yg * zb - zg * yb) - xg * (yr * zb - zr * yb) + xb * (yr * zg - zr * yg);
716     if (ABS(det) < 1.0e-10) {
717       WARN("Non invertible matrix: Maybe invalid value(s) specified in cHRM chunk.");
718       return NULL;
719     }
720     fr  = (Xw * (yg * zb - zg * yb) - xg * (zb - Zw * yb) + xb * (zg - Zw * yg)) / det;
721     fg  = (xr * (zb - Zw * yb) - Xw * (yr * zb - zr * yb) + xb * (yr * Zw - zr)) / det;
722     fb  = (xr * (yg * Zw - zg) - xg * (yr * Zw - zr) + Xw * (yr * zg - zr * yg)) / det;
723     Xr = fr * xr; Yr = fr * yr; Zr = fr * zr;
724     Xg = fg * xg; Yg = fg * yg; Zg = fg * zg;
725     Xb = fb * xb; Yb = fb * yb; Zb = fb * zb;
726   }
727
728   if (G < 1.0e-2) {
729     WARN("Unusual Gamma specified: %g", G);
730     return NULL;
731   }
732
733   cal_param = pdf_new_dict();
734
735   /* White point is always required. */
736   white_point = pdf_new_array();
737   pdf_add_array(white_point, pdf_new_number(ROUND(Xw, 0.00001)));
738   pdf_add_array(white_point, pdf_new_number(ROUND(Yw, 0.00001)));
739   pdf_add_array(white_point, pdf_new_number(ROUND(Zw, 0.00001)));
740   pdf_add_dict(cal_param, pdf_new_name("WhitePoint"), white_point);
741
742   /* Matrix - default: Identity */ 
743   if (color_type & PNG_COLOR_MASK_COLOR) {
744     if (G != 1.0) {
745       dev_gamma = pdf_new_array();
746       pdf_add_array(dev_gamma, pdf_new_number(ROUND(G, 0.00001)));
747       pdf_add_array(dev_gamma, pdf_new_number(ROUND(G, 0.00001)));
748       pdf_add_array(dev_gamma, pdf_new_number(ROUND(G, 0.00001)));
749       pdf_add_dict(cal_param, pdf_new_name("Gamma"), dev_gamma);
750     }
751
752     matrix = pdf_new_array();
753     pdf_add_array(matrix, pdf_new_number(ROUND(Xr, 0.00001)));
754     pdf_add_array(matrix, pdf_new_number(ROUND(Yr, 0.00001)));
755     pdf_add_array(matrix, pdf_new_number(ROUND(Zr, 0.00001)));
756     pdf_add_array(matrix, pdf_new_number(ROUND(Xg, 0.00001)));
757     pdf_add_array(matrix, pdf_new_number(ROUND(Yg, 0.00001)));
758     pdf_add_array(matrix, pdf_new_number(ROUND(Zg, 0.00001)));
759     pdf_add_array(matrix, pdf_new_number(ROUND(Xb, 0.00001)));
760     pdf_add_array(matrix, pdf_new_number(ROUND(Yb, 0.00001)));
761     pdf_add_array(matrix, pdf_new_number(ROUND(Zb, 0.00001)));
762     pdf_add_dict (cal_param, pdf_new_name("Matrix"), matrix);
763   } else { /* Gray */
764     if (G != 1.0)
765       pdf_add_dict(cal_param,
766                    pdf_new_name("Gamma"),
767                    pdf_new_number(ROUND(G, 0.00001)));
768   }
769
770   return cal_param;
771 }
772
773 /*
774  * Set up Indexed ColorSpace for color-type PALETTE:
775  *
776  *  PNG allows only RGB color for base color space. If gAMA and/or cHRM
777  *  chunk is available, we can use CalRGB color space instead of DeviceRGB
778  *  for base color space.
779  *
780  */
781 static pdf_obj *
782 create_cspace_Indexed (png_structp png_ptr, png_infop info_ptr)
783 {
784   pdf_obj   *colorspace;
785   pdf_obj   *base, *lookup;
786   png_byte  *data_ptr;
787   png_colorp plte;
788   int        num_plte, i;
789
790   if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE) ||
791       !png_get_PLTE(png_ptr, info_ptr, &plte, &num_plte)) {
792     WARN("%s: PNG does not have valid PLTE chunk.", PNG_DEBUG_STR);
793     return NULL;
794   }
795
796   /* Order is important. */
797   colorspace = pdf_new_array ();
798   pdf_add_array(colorspace, pdf_new_name("Indexed"));
799
800   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP))
801     base = create_cspace_ICCBased(png_ptr, info_ptr);
802   else {
803     if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB))
804       base = create_cspace_sRGB(png_ptr, info_ptr);
805     else
806       base = create_cspace_CalRGB(png_ptr, info_ptr);
807   }
808
809   if (!base)
810     base = pdf_new_name("DeviceRGB");
811
812   pdf_add_array(colorspace, base);
813   pdf_add_array(colorspace, pdf_new_number(num_plte-1));
814   data_ptr = NEW(num_plte*3, png_byte);
815   for (i = 0; i < num_plte; i++) {
816     data_ptr[3*i]   = plte[i].red;
817     data_ptr[3*i+1] = plte[i].green;
818     data_ptr[3*i+2] = plte[i].blue;
819   }
820   lookup = pdf_new_string(data_ptr, num_plte*3);
821   RELEASE(data_ptr);
822   pdf_add_array(colorspace, lookup);
823
824   return colorspace;
825 }
826
827 /*
828  * pHYs: no support
829  *
830  *  pngimage.c is not responsible for adjusting image size.
831  *  Higher layer must do something for this.
832  */
833
834 /*
835  * Colorkey Mask: array
836  *
837  *  [component_0_min component_0_max ... component_n_min component_n_max]
838  *
839  */
840
841 static pdf_obj *
842 create_ckey_mask (png_structp png_ptr, png_infop info_ptr)
843 {
844   pdf_obj  *colorkeys;
845   png_byte  color_type;
846   png_bytep trans;
847   int       num_trans, i;
848   png_color_16p colors;
849
850   if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ||
851       !png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &colors)) {
852     WARN("%s: PNG does not have valid tRNS chunk!", PNG_DEBUG_STR);
853     return NULL;
854   }
855
856   colorkeys  = pdf_new_array();
857   color_type = png_get_color_type(png_ptr, info_ptr);
858
859   switch (color_type) {
860   case PNG_COLOR_TYPE_PALETTE:
861     for (i = 0; i < num_trans; i++) {
862       if (trans[i] == 0x00) {
863         pdf_add_array(colorkeys, pdf_new_number(i));
864         pdf_add_array(colorkeys, pdf_new_number(i));
865       } else if (trans[i] != 0xff) {
866         WARN("%s: You found a bug in pngimage.c.", PNG_DEBUG_STR);
867       }
868     }
869     break;
870   case PNG_COLOR_TYPE_RGB:
871     pdf_add_array(colorkeys, pdf_new_number(colors->red));
872     pdf_add_array(colorkeys, pdf_new_number(colors->red));
873     pdf_add_array(colorkeys, pdf_new_number(colors->green));
874     pdf_add_array(colorkeys, pdf_new_number(colors->green));
875     pdf_add_array(colorkeys, pdf_new_number(colors->blue));
876     pdf_add_array(colorkeys, pdf_new_number(colors->blue));
877     break;
878   case PNG_COLOR_TYPE_GRAY:
879     pdf_add_array(colorkeys, pdf_new_number(colors->gray));
880     pdf_add_array(colorkeys, pdf_new_number(colors->gray));
881     break;
882   default:
883     WARN("%s: You found a bug in pngimage.c.", PNG_DEBUG_STR);
884     pdf_release_obj(colorkeys);
885     colorkeys = NULL;
886   }
887
888   return colorkeys;
889 }
890
891 /*
892  * Soft-Mask: stream
893  *
894  *   <<
895  *      /Type             /XObject
896  *      /Subtype          /Image
897  *      /Width            -int-
898  *      /Height           -int-
899  *      /BitsPerComponent bpc
900  *   >>
901  *   stream .... endstream
902  *
903  *   ColorSpace, Mask, SMask must be absent. ImageMask must be false or absent.
904  */
905
906 static pdf_obj *
907 create_soft_mask (png_structp png_ptr, png_infop info_ptr,
908                   png_bytep image_data_ptr, png_uint_32 width, png_uint_32 height)
909 {
910   pdf_obj    *smask, *dict;
911   png_bytep   smask_data_ptr;
912   png_bytep   trans;
913   int         num_trans;
914   png_uint_32 i;
915
916   if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ||
917       !png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL)) {
918     WARN("%s: PNG does not have valid tRNS chunk but tRNS is requested.", PNG_DEBUG_STR);
919     return NULL;
920   }
921
922   smask = pdf_new_stream(STREAM_COMPRESS);
923   dict  = pdf_stream_dict(smask);
924   smask_data_ptr = (png_bytep) NEW(width*height, png_byte);
925   pdf_add_dict(dict, pdf_new_name("Type"),    pdf_new_name("XObjcect"));
926   pdf_add_dict(dict, pdf_new_name("Subtype"), pdf_new_name("Image"));
927   pdf_add_dict(dict, pdf_new_name("Width"),      pdf_new_number(width));
928   pdf_add_dict(dict, pdf_new_name("Height"),     pdf_new_number(height));
929   pdf_add_dict(dict, pdf_new_name("ColorSpace"), pdf_new_name("DeviceGray"));
930   pdf_add_dict(dict, pdf_new_name("BitsPerComponent"), pdf_new_number(8));
931   for (i = 0; i < width*height; i++) {
932     png_byte idx = image_data_ptr[i];
933     smask_data_ptr[i] = (idx < num_trans) ? trans[idx] : 0xff;
934   }
935   pdf_add_stream(smask, (char *)smask_data_ptr, width*height);
936   RELEASE(smask_data_ptr);
937
938   return smask;
939 }
940
941 /* bitdepth is always 8 (16 is not supported) */
942 static pdf_obj *
943 strip_soft_mask (png_structp png_ptr, png_infop info_ptr,
944                  /* next two values will be modified. */
945                  png_bytep image_data_ptr, png_uint_32p rowbytes_ptr,
946                  png_uint_32 width, png_uint_32 height)
947 {
948   pdf_obj    *smask, *dict;
949   png_byte    color_type;
950   png_bytep   smask_data_ptr;
951   png_uint_32 i;
952
953   color_type = png_get_color_type(png_ptr, info_ptr);
954
955   if (color_type & PNG_COLOR_MASK_COLOR) {
956     if (*rowbytes_ptr != 4*width*sizeof(png_byte)) { /* Something wrong */
957       WARN("%s: Inconsistent rowbytes value.", PNG_DEBUG_STR);
958       return NULL;
959     }
960   } else {
961     if (*rowbytes_ptr != 2*width*sizeof(png_byte)) { /* Something wrong */
962       WARN("%s: Inconsistent rowbytes value.", PNG_DEBUG_STR);
963       return NULL;
964     }
965   }
966
967   smask = pdf_new_stream(STREAM_COMPRESS);
968   dict  = pdf_stream_dict(smask);
969   pdf_add_dict(dict, pdf_new_name("Type"),    pdf_new_name("XObjcect"));
970   pdf_add_dict(dict, pdf_new_name("Subtype"), pdf_new_name("Image"));
971   pdf_add_dict(dict, pdf_new_name("Width"),      pdf_new_number(width));
972   pdf_add_dict(dict, pdf_new_name("Height"),     pdf_new_number(height));
973   pdf_add_dict(dict, pdf_new_name("ColorSpace"), pdf_new_name("DeviceGray"));
974   pdf_add_dict(dict, pdf_new_name("BitsPerComponent"), pdf_new_number(8));
975
976   smask_data_ptr = (png_bytep) NEW(width*height, png_byte);
977
978   switch (color_type) {
979   case PNG_COLOR_TYPE_RGB_ALPHA:
980     for (i = 0; i < width*height; i++) {
981       memmove(image_data_ptr+(3*i), image_data_ptr+(4*i), 3);
982       smask_data_ptr[i] = image_data_ptr[4*i+3];
983     }
984     *rowbytes_ptr = 3*width*sizeof(png_byte);
985     break;
986   case PNG_COLOR_TYPE_GRAY_ALPHA:
987     for (i = 0; i < width*height; i++) {
988       image_data_ptr[i] = image_data_ptr[2*i];
989       smask_data_ptr[i] = image_data_ptr[2*i+1];
990     }
991     *rowbytes_ptr = width*sizeof(png_byte);
992     break;
993   default:
994     WARN("You found a bug in pngimage.c!");
995     pdf_release_obj(smask);
996     RELEASE(smask_data_ptr);
997     return NULL;
998   }
999
1000   pdf_add_stream(smask, smask_data_ptr, width*height);
1001   RELEASE(smask_data_ptr);
1002
1003   return smask;
1004 }
1005
1006 static void
1007 read_image_data (png_structp png_ptr, png_infop info_ptr, /* info_ptr unused */
1008                  png_bytep dest_ptr, png_uint_32 height, png_uint_32 rowbytes)
1009 {
1010   png_bytepp  rows_p;
1011   png_uint_32 i;
1012
1013   rows_p = (png_bytepp) NEW (height, png_bytep);
1014   for (i=0; i< height; i++)
1015     rows_p[i] = dest_ptr + (rowbytes * i);
1016   png_read_image(png_ptr, rows_p);
1017   RELEASE(rows_p);
1018 }
1019
1020 int
1021 png_get_bbox (FILE *png_file, long *width, long *height,
1022                double *xdensity, double *ydensity)
1023 {
1024   png_structp png_ptr;
1025   png_infop   png_info_ptr;
1026   png_uint_32 xppm, yppm;
1027
1028   rewind (png_file);
1029   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1030   if (png_ptr == NULL || 
1031       (png_info_ptr = png_create_info_struct (png_ptr)) == NULL) {
1032     WARN("%s: Creating Libpng read/info struct failed.", PNG_DEBUG_STR);
1033     if (png_ptr)
1034       png_destroy_read_struct(&png_ptr, NULL, NULL);
1035     return -1;
1036   }
1037
1038   /* Inititializing file IO. */
1039   png_init_io (png_ptr, png_file);
1040
1041   /* Read PNG info-header and get some info. */
1042   png_read_info(png_ptr, png_info_ptr);
1043   *width      = png_get_image_width (png_ptr, png_info_ptr);
1044   *height     = png_get_image_height(png_ptr, png_info_ptr);
1045   xppm       = png_get_x_pixels_per_meter(png_ptr, png_info_ptr);
1046   yppm       = png_get_y_pixels_per_meter(png_ptr, png_info_ptr);
1047
1048   /* Cleanup */
1049   if (png_info_ptr)
1050     png_destroy_info_struct(png_ptr, &png_info_ptr);
1051   if (png_ptr)
1052     png_destroy_read_struct(&png_ptr, NULL, NULL);
1053
1054   if (compat_mode)
1055     *xdensity = *ydensity = 72.0 / 100.0;
1056   else {
1057     *xdensity = xppm ? 72.0 / 0.0254 / xppm : 1.0;
1058     *ydensity = yppm ? 72.0 / 0.0254 / yppm : 1.0;
1059   }
1060
1061   return 0;
1062 }
1063
1064 #endif /* HAVE_LIBPNG */