3 * Purpose: Platform Independent PNG Image Class Loader and Writer
\r
4 * 07/Aug/2001 Davide Pizzolato - www.xdp.it
\r
5 * CxImage version 6.0.0 02/Feb/2008
\r
10 #if CXIMAGE_SUPPORT_PNG
\r
12 #include "ximaiter.h"
\r
14 ////////////////////////////////////////////////////////////////////////////////
\r
15 void CxImagePNG::ima_png_error(png_struct *png_ptr, char *message)
\r
17 strcpy(info.szLastError,message);
\r
18 longjmp(png_ptr->jmpbuf, 1);
\r
20 ////////////////////////////////////////////////////////////////////////////////
\r
21 #if CXIMAGE_SUPPORT_DECODE
\r
22 ////////////////////////////////////////////////////////////////////////////////
\r
23 void CxImagePNG::expand2to4bpp(BYTE* prow)
\r
27 for(long x=head.biWidth-1;x>=0;x--){
\r
28 psrc = prow + ((2*x)>>3);
\r
29 pdst = prow + ((4*x)>>3);
\r
30 pos = (BYTE)(2*(3-x%4));
\r
31 idx = (BYTE)((*psrc & (0x03<<pos))>>pos);
\r
32 pos = (BYTE)(4*(1-x%2));
\r
33 *pdst &= ~(0x0F<<pos);
\r
34 *pdst |= (idx & 0x0F)<<pos;
\r
37 ////////////////////////////////////////////////////////////////////////////////
\r
38 bool CxImagePNG::Decode(CxFile *hFile)
\r
40 png_struct *png_ptr;
\r
42 BYTE *row_pointers=NULL;
\r
43 CImageIterator iter(this);
\r
47 /* Create and initialize the png_struct with the desired error handler
\r
48 * functions. If you want to use the default stderr and longjump method,
\r
49 * you can supply NULL for the last three parameters. We also supply the
\r
50 * the compiler header file version, so that we know if the application
\r
51 * was compiled with a compatible version of the library. REQUIRED */
\r
52 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,(void *)NULL,NULL,NULL);
\r
53 if (png_ptr == NULL) cx_throw("Failed to create PNG structure");
\r
55 /* Allocate/initialize the memory for image information. REQUIRED. */
\r
56 info_ptr = png_create_info_struct(png_ptr);
\r
57 if (info_ptr == NULL) {
\r
58 png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
\r
59 cx_throw("Failed to initialize PNG info structure");
\r
62 /* Set error handling if you are using the setjmp/longjmp method (this is
\r
63 * the normal method of doing things with libpng). REQUIRED unless you
\r
64 * set up your own error handlers in the png_create_read_struct() earlier. */
\r
65 if (setjmp(png_ptr->jmpbuf)) {
\r
66 /* Free all of the memory associated with the png_ptr and info_ptr */
\r
67 delete [] row_pointers;
\r
68 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
\r
72 // use custom I/O functions
\r
73 png_set_read_fn(png_ptr, hFile, /*(png_rw_ptr)*/user_read_data);
\r
74 png_set_error_fn(png_ptr,info.szLastError,/*(png_error_ptr)*/user_error_fn,NULL);
\r
76 /* read the file information */
\r
77 png_read_info(png_ptr, info_ptr);
\r
79 if (info.nEscape == -1){
\r
80 head.biWidth = info_ptr->width;
\r
81 head.biHeight= info_ptr->height;
\r
82 info.dwType = CXIMAGE_FORMAT_PNG;
\r
83 longjmp(png_ptr->jmpbuf, 1);
\r
86 /* calculate new number of channels */
\r
88 switch(info_ptr->color_type){
\r
89 case PNG_COLOR_TYPE_GRAY:
\r
90 case PNG_COLOR_TYPE_PALETTE:
\r
93 case PNG_COLOR_TYPE_GRAY_ALPHA:
\r
96 case PNG_COLOR_TYPE_RGB:
\r
99 case PNG_COLOR_TYPE_RGB_ALPHA:
\r
103 strcpy(info.szLastError,"unknown PNG color type");
\r
104 longjmp(png_ptr->jmpbuf, 1);
\r
107 //find the right pixel depth used for cximage
\r
108 int pixel_depth = info_ptr->pixel_depth;
\r
109 if (channels == 1 && pixel_depth>8) pixel_depth=8;
\r
110 if (channels == 2) pixel_depth=8;
\r
111 if (channels >= 3) pixel_depth=24;
\r
113 if (!Create(info_ptr->width, info_ptr->height, pixel_depth, CXIMAGE_FORMAT_PNG)){
\r
114 longjmp(png_ptr->jmpbuf, 1);
\r
118 switch (info_ptr->phys_unit_type)
\r
120 case PNG_RESOLUTION_UNKNOWN:
\r
121 SetXDPI(info_ptr->x_pixels_per_unit);
\r
122 SetYDPI(info_ptr->y_pixels_per_unit);
\r
124 case PNG_RESOLUTION_METER:
\r
125 SetXDPI((long)floor(info_ptr->x_pixels_per_unit * 254.0 / 10000.0 + 0.5));
\r
126 SetYDPI((long)floor(info_ptr->y_pixels_per_unit * 254.0 / 10000.0 + 0.5));
\r
130 if (info_ptr->num_palette>0){
\r
131 SetPalette((rgb_color*)info_ptr->palette,info_ptr->num_palette);
\r
132 SetClrImportant(info_ptr->num_palette);
\r
133 } else if (info_ptr->bit_depth ==2) { //<DP> needed for 2 bpp grayscale PNGs
\r
134 SetPaletteColor(0,0,0,0);
\r
135 SetPaletteColor(1,85,85,85);
\r
136 SetPaletteColor(2,170,170,170);
\r
137 SetPaletteColor(3,255,255,255);
\r
138 } else SetGrayPalette(); //<DP> needed for grayscale PNGs
\r
140 int nshift = max(0,(info_ptr->bit_depth>>3)-1)<<3;
\r
142 if (info_ptr->num_trans!=0){ //palette transparency
\r
143 if (info_ptr->num_trans==1){
\r
144 if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE){
\r
145 info.nBkgndIndex = info_ptr->trans_values.index;
\r
147 info.nBkgndIndex = info_ptr->trans_values.gray>>nshift;
\r
150 if (info_ptr->num_trans>1){
\r
151 RGBQUAD* pal=GetPalette();
\r
154 for (ip=0;ip<min(head.biClrUsed,(unsigned long)info_ptr->num_trans);ip++)
\r
155 pal[ip].rgbReserved=info_ptr->trans[ip];
\r
156 for (ip=info_ptr->num_trans;ip<head.biClrUsed;ip++){
\r
157 pal[ip].rgbReserved=255;
\r
159 info.bAlphaPaletteEnabled=true;
\r
164 if (channels == 3){ //check RGB binary transparency
\r
167 png_color_16 *image_background;
\r
168 if (png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &image_background)){
\r
169 info.nBkgndColor.rgbRed = (BYTE)(info_ptr->trans_values.red>>nshift);
\r
170 info.nBkgndColor.rgbGreen = (BYTE)(info_ptr->trans_values.green>>nshift);
\r
171 info.nBkgndColor.rgbBlue = (BYTE)(info_ptr->trans_values.blue>>nshift);
\r
172 info.nBkgndColor.rgbReserved = 0;
\r
173 info.nBkgndIndex = 0;
\r
177 int alpha_present = (channels - 1) % 2;
\r
178 if (alpha_present){
\r
179 #if CXIMAGE_SUPPORT_ALPHA // <vho>
\r
182 png_set_strip_alpha(png_ptr);
\r
183 #endif //CXIMAGE_SUPPORT_ALPHA
\r
186 // <vho> - flip the RGB pixels to BGR (or RGBA to BGRA)
\r
187 if (info_ptr->color_type & PNG_COLOR_MASK_COLOR){
\r
188 png_set_bgr(png_ptr);
\r
191 // <vho> - handle cancel
\r
192 if (info.nEscape) longjmp(png_ptr->jmpbuf, 1);
\r
194 // row_bytes is the width x number of channels x (bit-depth / 8)
\r
195 row_pointers = new BYTE[info_ptr->rowbytes + 8];
\r
197 // turn on interlace handling
\r
198 int number_passes = png_set_interlace_handling(png_ptr);
\r
200 if (number_passes>1){
\r
206 int chan_offset = info_ptr->bit_depth >> 3;
\r
207 int pixel_offset = info_ptr->pixel_depth >> 3;
\r
209 for (int pass=0; pass < number_passes; pass++) {
\r
214 // <vho> - handle cancel
\r
215 if (info.nEscape) longjmp(png_ptr->jmpbuf, 1);
\r
217 #if CXIMAGE_SUPPORT_ALPHA // <vho>
\r
218 if (AlphaIsValid()) {
\r
220 //compute the correct position of the line
\r
222 ay = head.biHeight-1-y;
\r
223 BYTE* prow= iter.GetRow(ay);
\r
225 //recover data from previous scan
\r
226 if (info_ptr->interlace_type && pass>0 && pass!=7){
\r
227 for(ax=0;ax<head.biWidth;ax++){
\r
228 long px = ax * pixel_offset;
\r
229 if (channels == 2){
\r
230 row_pointers[px] = prow[ax];
\r
231 row_pointers[px+chan_offset]=AlphaGet(ax,ay);
\r
234 row_pointers[px] =prow[qx];
\r
235 row_pointers[px+chan_offset] =prow[qx+1];
\r
236 row_pointers[px+chan_offset*2]=prow[qx+2];
\r
237 row_pointers[px+chan_offset*3]=AlphaGet(ax,ay);
\r
243 png_read_row(png_ptr, row_pointers, NULL);
\r
246 for(ax=0;ax<head.biWidth;ax++){
\r
247 long px = ax * pixel_offset;
\r
248 if (channels == 2){
\r
249 prow[ax] = row_pointers[px];
\r
250 AlphaSet(ax,ay,row_pointers[px+chan_offset]);
\r
253 prow[qx] =row_pointers[px];
\r
254 prow[qx+1]=row_pointers[px+chan_offset];
\r
255 prow[qx+2]=row_pointers[px+chan_offset*2];
\r
256 AlphaSet(ax,ay,row_pointers[px+chan_offset*3]);
\r
260 #endif // CXIMAGE_SUPPORT_ALPHA // vho
\r
262 //recover data from previous scan
\r
263 if (info_ptr->interlace_type && pass>0){
\r
264 iter.GetRow(row_pointers, info_ptr->rowbytes);
\r
265 //re-expand buffer for images with bit depth > 8
\r
266 if (info_ptr->bit_depth > 8){
\r
267 for(long ax=(head.biWidth*channels-1);ax>=0;ax--)
\r
268 row_pointers[ax*chan_offset] = row_pointers[ax];
\r
273 png_read_row(png_ptr, row_pointers, NULL);
\r
275 //shrink 16 bit depth images down to 8 bits
\r
276 if (info_ptr->bit_depth > 8){
\r
277 for(long ax=0;ax<(head.biWidth*channels);ax++)
\r
278 row_pointers[ax] = row_pointers[ax*chan_offset];
\r
282 iter.SetRow(row_pointers, info_ptr->rowbytes);
\r
283 //<DP> expand 2 bpp images only in the last pass
\r
284 if (info_ptr->bit_depth==2 && pass==(number_passes-1))
\r
285 expand2to4bpp(iter.GetRow());
\r
292 } while(y<head.biHeight);
\r
295 delete [] row_pointers;
\r
297 /* read the rest of the file, getting any additional chunks in info_ptr */
\r
298 png_read_end(png_ptr, info_ptr);
\r
300 /* clean up after the read, and free any memory allocated - REQUIRED */
\r
301 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
\r
304 if (strcmp(message,"")) strncpy(info.szLastError,message,255);
\r
305 if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_PNG) return true;
\r
311 ////////////////////////////////////////////////////////////////////////////////
\r
312 #endif //CXIMAGE_SUPPORT_DECODE
\r
313 ////////////////////////////////////////////////////////////////////////////////
\r
314 #if CXIMAGE_SUPPORT_ENCODE
\r
315 ////////////////////////////////////////////////////////////////////////////////
\r
316 bool CxImagePNG::Encode(CxFile *hFile)
\r
318 if (EncodeSafeCheck(hFile)) return false;
\r
320 CImageIterator iter(this);
\r
321 BYTE trans[256]; //for transparency (don't move)
\r
322 png_struct *png_ptr;
\r
323 png_info *info_ptr;
\r
327 /* Create and initialize the png_struct with the desired error handler
\r
328 * functions. If you want to use the default stderr and longjump method,
\r
329 * you can supply NULL for the last three parameters. We also check that
\r
330 * the library version is compatible with the one used at compile time,
\r
331 * in case we are using dynamically linked libraries. REQUIRED.
\r
333 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,(void *)NULL,NULL,NULL);
\r
334 if (png_ptr == NULL) cx_throw("Failed to create PNG structure");
\r
336 /* Allocate/initialize the image information data. REQUIRED */
\r
337 info_ptr = png_create_info_struct(png_ptr);
\r
338 if (info_ptr == NULL){
\r
339 png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
\r
340 cx_throw("Failed to initialize PNG info structure");
\r
343 /* Set error handling. REQUIRED if you aren't supplying your own
\r
344 * error hadnling functions in the png_create_write_struct() call.
\r
346 if (setjmp(png_ptr->jmpbuf)){
\r
347 /* If we get here, we had a problem reading the file */
\r
348 if (info_ptr->palette) free(info_ptr->palette);
\r
349 png_destroy_write_struct(&png_ptr, (png_infopp)&info_ptr);
\r
350 cx_throw("Error saving PNG file");
\r
353 /* set up the output control */
\r
354 //png_init_io(png_ptr, hFile);
\r
356 // use custom I/O functions
\r
357 png_set_write_fn(png_ptr,hFile,/*(png_rw_ptr)*/user_write_data,/*(png_flush_ptr)*/user_flush_data);
\r
359 /* set the file information here */
\r
360 info_ptr->width = GetWidth();
\r
361 info_ptr->height = GetHeight();
\r
362 info_ptr->pixel_depth = (BYTE)GetBpp();
\r
363 info_ptr->channels = (GetBpp()>8) ? (BYTE)3: (BYTE)1;
\r
364 info_ptr->bit_depth = (BYTE)(GetBpp()/info_ptr->channels);
\r
365 info_ptr->compression_type = info_ptr->filter_type = 0;
\r
366 info_ptr->valid = 0;
\r
368 switch(GetCodecOption(CXIMAGE_FORMAT_PNG)){
\r
370 info_ptr->interlace_type = PNG_INTERLACE_ADAM7;
\r
373 info_ptr->interlace_type = PNG_INTERLACE_NONE;
\r
376 /* set compression level */
\r
377 //png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
\r
379 bool bGrayScale = IsGrayScale();
\r
381 if (GetNumColors()){
\r
383 info_ptr->color_type = PNG_COLOR_TYPE_GRAY;
\r
385 info_ptr->color_type = PNG_COLOR_TYPE_PALETTE;
\r
388 info_ptr->color_type = PNG_COLOR_TYPE_RGB;
\r
390 #if CXIMAGE_SUPPORT_ALPHA
\r
391 if (AlphaIsValid()){
\r
392 info_ptr->color_type |= PNG_COLOR_MASK_ALPHA;
\r
393 info_ptr->channels++;
\r
394 info_ptr->bit_depth = 8;
\r
395 info_ptr->pixel_depth += 8;
\r
399 /* set background */
\r
400 png_color_16 image_background={ 0, 255, 255, 255, 0 };
\r
401 RGBQUAD tc = GetTransColor();
\r
402 if (info.nBkgndIndex>=0) {
\r
403 image_background.blue = tc.rgbBlue;
\r
404 image_background.green = tc.rgbGreen;
\r
405 image_background.red = tc.rgbRed;
\r
407 png_set_bKGD(png_ptr, info_ptr, &image_background);
\r
410 png_set_pHYs(png_ptr, info_ptr, head.biXPelsPerMeter, head.biYPelsPerMeter, PNG_RESOLUTION_METER);
\r
412 png_set_IHDR(png_ptr, info_ptr, info_ptr->width, info_ptr->height, info_ptr->bit_depth,
\r
413 info_ptr->color_type, info_ptr->interlace_type,
\r
414 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
\r
416 //<DP> simple transparency
\r
417 if (info.nBkgndIndex >= 0){
\r
418 info_ptr->num_trans = 1;
\r
419 info_ptr->valid |= PNG_INFO_tRNS;
\r
420 info_ptr->trans = trans;
\r
421 info_ptr->trans_values.index = (BYTE)info.nBkgndIndex;
\r
422 info_ptr->trans_values.red = tc.rgbRed;
\r
423 info_ptr->trans_values.green = tc.rgbGreen;
\r
424 info_ptr->trans_values.blue = tc.rgbBlue;
\r
425 info_ptr->trans_values.gray = info_ptr->trans_values.index;
\r
427 // the transparency indexes start from 0 for non grayscale palette
\r
428 if (!bGrayScale && head.biClrUsed && info.nBkgndIndex)
\r
429 SwapIndex(0,(BYTE)info.nBkgndIndex);
\r
432 /* set the palette if there is one */
\r
435 info_ptr->valid |= PNG_INFO_PLTE;
\r
438 int nc = GetClrImportant();
\r
439 if (nc==0) nc = GetNumColors();
\r
441 if (info.bAlphaPaletteEnabled){
\r
442 for(WORD ip=0; ip<nc;ip++)
\r
443 trans[ip]=GetPaletteColor((BYTE)ip).rgbReserved;
\r
444 info_ptr->num_trans = (WORD)nc;
\r
445 info_ptr->valid |= PNG_INFO_tRNS;
\r
446 info_ptr->trans = trans;
\r
449 // copy the palette colors
\r
450 info_ptr->palette = new png_color[nc];
\r
451 info_ptr->num_palette = (png_uint_16) nc;
\r
452 for (int i=0; i<nc; i++)
\r
453 GetPaletteColor(i, &info_ptr->palette[i].red, &info_ptr->palette[i].green, &info_ptr->palette[i].blue);
\r
456 #if CXIMAGE_SUPPORT_ALPHA // <vho>
\r
457 //Merge the transparent color with the alpha channel
\r
458 if (AlphaIsValid() && head.biBitCount==24 && info.nBkgndIndex>=0){
\r
459 for(long y=0; y < head.biHeight; y++){
\r
460 for(long x=0; x < head.biWidth ; x++){
\r
461 RGBQUAD c=GetPixelColor(x,y,false);
\r
462 if (*(long*)&c==*(long*)&tc)
\r
465 #endif // CXIMAGE_SUPPORT_ALPHA // <vho>
\r
467 int row_size = max(info.dwEffWidth, info_ptr->width*info_ptr->channels*(info_ptr->bit_depth/8));
\r
468 info_ptr->rowbytes = row_size;
\r
469 BYTE *row_pointers = new BYTE[row_size];
\r
471 /* write the file information */
\r
472 png_write_info(png_ptr, info_ptr);
\r
474 //interlace handling
\r
475 int num_pass = png_set_interlace_handling(png_ptr);
\r
476 for (int pass = 0; pass < num_pass; pass++){
\r
479 long ay=head.biHeight-1;
\r
482 #if CXIMAGE_SUPPORT_ALPHA // <vho>
\r
483 if (AlphaIsValid()){
\r
484 for (long ax=head.biWidth-1; ax>=0;ax--){
\r
485 c = BlindGetPixelColor(ax,ay);
\r
486 int px = ax * info_ptr->channels;
\r
488 row_pointers[px++]=c.rgbRed;
\r
489 row_pointers[px++]=c.rgbGreen;
\r
491 row_pointers[px++]=c.rgbBlue;
\r
492 row_pointers[px] = AlphaGet(ax,ay);
\r
494 png_write_row(png_ptr, row_pointers);
\r
498 #endif //CXIMAGE_SUPPORT_ALPHA // <vho>
\r
500 iter.GetRow(row_pointers, row_size);
\r
501 if (info_ptr->color_type == PNG_COLOR_TYPE_RGB) //HACK BY OP
\r
502 RGBtoBGR(row_pointers, row_size);
\r
503 png_write_row(png_ptr, row_pointers);
\r
505 } while(iter.PrevRow());
\r
508 delete [] row_pointers;
\r
510 //if necessary, restore the original palette
\r
511 if (!bGrayScale && head.biClrUsed && info.nBkgndIndex>0)
\r
512 SwapIndex((BYTE)info.nBkgndIndex,0);
\r
514 /* It is REQUIRED to call this to finish writing the rest of the file */
\r
515 png_write_end(png_ptr, info_ptr);
\r
517 /* if you malloced the palette, free it here */
\r
518 if (info_ptr->palette){
\r
519 delete [] (info_ptr->palette);
\r
520 info_ptr->palette = NULL;
\r
523 /* clean up after the write, and free any memory allocated */
\r
524 png_destroy_write_struct(&png_ptr, (png_infopp)&info_ptr);
\r
527 if (strcmp(message,"")) strncpy(info.szLastError,message,255);
\r
533 ////////////////////////////////////////////////////////////////////////////////
\r
534 #endif // CXIMAGE_SUPPORT_ENCODE
\r
535 ////////////////////////////////////////////////////////////////////////////////
\r
536 #endif // CXIMAGE_SUPPORT_PNG
\r