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