// pngdib.c // // PNGDIB - a mini imaging library for Win32 // By Jason Summers // This software may be used without restriction. // // read_png_to_dib() // Read a PNG image file and convert it to a Windows "Device Independent // Bitmap" (DIB) // // write_dib_to_png() // Write a DIB to a PNG image file // // pngdib_get_version_string(void) // // pngdib_get_version(void) // #define PNGDIB_SRC_VERSION 20200 #define PNGDIB_SRC_VERSION_STRING "2.2.0" #include #include #include #include #include #include "png.h" #include "pngdib.h" #if PNGDIB_SRC_VERSION != PNGDIB_HEADER_VERSION #error Wrong PNGDIB header file version #endif #if (PNG_LIBPNG_VER<10202) || \ (PNG_LIBPNG_VER==10202 && PNG_LIBPNG_BUILD_TYPE<2) || \ (PNG_LIBPNG_VER==10202 && PNG_LIBPNG_BUILD_TYPE==2 && PNG_LIBPNG_VER_BUILD<5) #error libpng 1.2.2b5 or higher is recommended /* You can comment out the previous line if you aren't using gamma * correction, or don't care about a few obscure gamma correction * problems that exist in earlier versions of libpng. */ #endif // This is basically a Windows-only utility with a simple-as-possible // interface, so I'm not too concerned about allowing a // user-configurable screen gamma. static const double screen_gamma = 2.2; #define MAX_ERRMSGLEN 100 struct errstruct { jmp_buf *jbufp; char *errmsg; }; static void pngd_get_error_message(int rv,char *e) { switch(rv) { case PNGD_E_ERROR: strcpy(e,"Unknown error"); break; case PNGD_E_VERSION: strcpy(e,"Incompatible library version"); break; case PNGD_E_NOMEM: strcpy(e,"Unable to allocate memory"); break; case PNGD_E_UNSUPP: strcpy(e,"Invalid or unsupported image"); break; case PNGD_E_LIBPNG: strcpy(e,"libpng reported an error"); break; case PNGD_E_BADBMP: strcpy(e,"Invalid BMP image"); break; case PNGD_E_BADPNG: strcpy(e,"Invalid PNG image"); break; case PNGD_E_READ: strcpy(e,"Unable to read file"); break; case PNGD_E_WRITE: strcpy(e,"Unable to write file"); break; } } static unsigned char* uncompress_dib(LPBITMAPINFO lpbmi1, int infosize, void *lpbits1) { LPBITMAPINFOHEADER lpdib2; unsigned char *lpbits2; void *whatever; int linesize, bitssize; HBITMAP hb; HDC hdc; HGDIOBJ rvgdi; int rvi; int width,height; LPBITMAPINFOHEADER lpdib1; lpdib1=(LPBITMAPINFOHEADER)lpbmi1; width=lpdib1->biWidth; height=lpdib1->biHeight; linesize= (((width * lpdib1->biBitCount)+31)/32)*4; bitssize= linesize*height; lpdib2= (LPBITMAPINFOHEADER)malloc(infosize); if(!lpdib2) return NULL; // create a header for the new uncompressed DIB CopyMemory((void*)lpdib2,(void*)lpdib1,infosize); lpdib2->biCompression=BI_RGB; lpdib2->biSizeImage=0; lpbits2= (unsigned char*)malloc(bitssize); if(!lpbits2) { free((void*)lpdib2); return NULL; } // Windows bitmap handling functions are not exactly convenient, // especially when trying to deal with DIBs. Every function wants // to convert them into DDBs. We have to play stupid games and // convert back and forth. This probably uses too much memory, // and I'm not 100% sure it is exactly correct, but it seems to // work for me. hb=CreateDIBSection(NULL,(LPBITMAPINFO)lpdib2,DIB_RGB_COLORS,&whatever,NULL,0); hdc=CreateCompatibleDC(NULL); rvgdi=SelectObject(hdc,hb); //SetStretchBltMode(hdc,COLORONCOLOR); rvi=StretchDIBits(hdc, 0,0,width,height, 0,0,width,height, lpbits1, (LPBITMAPINFO)lpdib1, DIB_RGB_COLORS,SRCCOPY); rvi=GetDIBits(hdc,hb,0,height, (LPVOID)lpbits2, (LPBITMAPINFO)lpdib2,DIB_RGB_COLORS); DeleteDC(hdc); DeleteObject(hb); free((void*)lpdib2); return lpbits2; } static void my_error_fn(png_structp png_ptr, const char *err_msg) { struct errstruct *errinfop; jmp_buf *j; errinfop = (struct errstruct *)png_get_error_ptr(png_ptr); j = errinfop->jbufp; _snprintf(errinfop->errmsg,MAX_ERRMSGLEN,"[libpng] %s",err_msg); errinfop->errmsg[MAX_ERRMSGLEN-1]='\0'; longjmp(*j, -1); } static void my_warning_fn(png_structp png_ptr, const char *warn_msg) { return; } // This function should perform identically to libpng's gamma correction. // I'd prefer to have libpng do all gamma correction itself, // but I can't figure out how to do that efficiently. static void gamma_correct(double screen_gamma,double file_gamma, unsigned char *red, unsigned char *green, unsigned char *blue) { double g; #ifndef PNG_GAMMA_THRESHOLD # define PNG_GAMMA_THRESHOLD 0.05 #endif if(fabs(screen_gamma*file_gamma-1.0)<=PNG_GAMMA_THRESHOLD) return; if (screen_gamma>0.000001) g=1.0/(file_gamma*screen_gamma); else g=1.0; (*red) = (unsigned char)(pow((double)(*red )/255.0,g)*255.0+0.5); (*green) = (unsigned char)(pow((double)(*green)/255.0,g)*255.0+0.5); (*blue) = (unsigned char)(pow((double)(*blue )/255.0,g)*255.0+0.5); } int read_png_to_dib(PNGD_P2DINFO *p2dp) { png_structp png_ptr; png_infop info_ptr; jmp_buf jbuf; struct errstruct errinfo; png_uint_32 width, height; int png_bit_depth, color_type, interlace_type; png_colorp png_palette; png_uint_32 res_x, res_y; int has_phys, has_gama; int res_unit_type; FILE *fp; int palette_entries; unsigned char **row_pointers; unsigned char *lpdib; unsigned char *dib_palette; unsigned char *dib_bits; unsigned char *tmprow; int dib_bpp, dib_bytesperrow; int i,j; int rv; png_color_16 bkgd; // used with png_set_background int has_trns, trns_color; int has_bkgd; // ==1 if there a bkgd chunk, and USE_BKGD flag png_color_16p temp_colorp; png_color_16p bg_colorp; // background color (if has_bkgd) png_bytep trns_trans; int manual_trns; int manual_gamma; struct PNGD_COLOR_struct bkgd_color; int is_grayscale,has_alpha_channel; double file_gamma; char *errmsg; char dummy_errmsg[MAX_ERRMSGLEN]; int imginfo; // flag for v2.1 fields int use_heapalloc; HANDLE heap; imginfo=0; use_heapalloc=0; // fields through errmsg must exist if(p2dp->structsize<48) return PNGD_E_VERSION; // try to be somewhat backward-compatible if(p2dp->structsize>=88) { //if(p2dp->reserved1 != 0) return PNGD_E_VERSION; imginfo=1; } if(p2dp->structsize>=96) { //if(p2dp->reserved2 != 0) return PNGD_E_VERSION; if(p2dp->flags & PNGD_USE_HEAPALLOC) { use_heapalloc=1; heap = p2dp->heap; if(!heap) heap = GetProcessHeap(); } } manual_trns=0; has_trns=has_bkgd=0; rv=PNGD_E_ERROR; png_ptr=NULL; info_ptr=NULL; fp=NULL; row_pointers=NULL; lpdib=NULL; // make sure errmsg is not NULL even if user doesn't want it if(p2dp->errmsg) errmsg=p2dp->errmsg; else errmsg=dummy_errmsg; errmsg[0]='\0'; if(p2dp->flags&PNGD_USE_CUSTOM_BG) { bkgd_color.red= p2dp->bgcolor.red; bkgd_color.green= p2dp->bgcolor.green; bkgd_color.blue= p2dp->bgcolor.blue; } else { bkgd_color.red= 255; // Should never get used. If the bkgd_color.green= 128; // background turns orange, it's a bug. bkgd_color.blue= 0; } // Set the user-defined pointer to point to our jmp_buf. This will // hopefully protect against potentially different sized jmp_buf's in // libpng, while still allowing this library to be threadsafe. errinfo.jbufp = &jbuf; errinfo.errmsg = errmsg; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,(void*)(&errinfo), my_error_fn, my_warning_fn); if(!png_ptr) { rv=PNGD_E_NOMEM; goto abort; } info_ptr = png_create_info_struct(png_ptr); if(!info_ptr) { //png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); rv=PNGD_E_NOMEM; goto abort; } if(setjmp(jbuf)) { // we'll get here if an error occurred in any of the following // png_ functions rv=PNGD_E_LIBPNG; goto abort; } if((fp = fopen(p2dp->pngfn, "rb")) == NULL) { rv=PNGD_E_READ; goto abort; } png_init_io(png_ptr, fp); png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &png_bit_depth, &color_type, &interlace_type, NULL, NULL); if(imginfo) { // return some info to the caller p2dp->color_type=color_type; p2dp->bits_per_sample=png_bit_depth; p2dp->interlace=interlace_type; switch(color_type) { case PNG_COLOR_TYPE_RGB: p2dp->bits_per_pixel=png_bit_depth*3; break; case PNG_COLOR_TYPE_RGB_ALPHA: p2dp->bits_per_pixel=png_bit_depth*4; break; case PNG_COLOR_TYPE_GRAY_ALPHA: p2dp->bits_per_pixel=png_bit_depth*2; break; default: p2dp->bits_per_pixel=png_bit_depth; } } is_grayscale = !(color_type&PNG_COLOR_MASK_COLOR); has_alpha_channel = (color_type&PNG_COLOR_MASK_ALPHA)?1:0; has_trns = png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS); // look for bKGD chunk, and process if applicable if(p2dp->flags & PNGD_USE_BKGD) { if(png_get_bKGD(png_ptr, info_ptr, &bg_colorp)) { // process the background, store 8-bit RGB in bkgd_color has_bkgd=1; if(is_grayscale && png_bit_depth<8) { bkgd_color.red = bkgd_color.green= bkgd_color.blue = (unsigned char) ( (bg_colorp->gray*255)/( (1<red); bkgd_color.green=(unsigned char)(bg_colorp->green); bkgd_color.blue =(unsigned char)(bg_colorp->blue); } else { bkgd_color.red=(unsigned char)(bg_colorp->red>>8); bkgd_color.green=(unsigned char)(bg_colorp->green>>8); bkgd_color.blue =(unsigned char)(bg_colorp->blue>>8); } } } if( !(color_type & PNG_COLOR_MASK_ALPHA) && !has_trns) { // If no transparency, we can skip this whole background-color mess. goto notrans; } if(has_bkgd && (png_bit_depth>8 || !is_grayscale || has_alpha_channel)) { png_set_background(png_ptr, bg_colorp, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); } else if(is_grayscale && has_trns && png_bit_depth<=8 && (has_bkgd || (p2dp->flags & PNGD_USE_CUSTOM_BG)) ) { // grayscale binarytrans,<=8bpp: transparency is handle manually // by modifying a palette entry (later) png_get_tRNS(png_ptr,info_ptr,&trns_trans, &i, &temp_colorp); if(i>=1) { trns_color= temp_colorp->gray; // corresponds to a palette entry manual_trns=1; } } else if(!has_bkgd && (has_trns || has_alpha_channel) && (p2dp->flags & PNGD_USE_CUSTOM_BG) ) { // process most CUSTOM background colors bkgd.index = 0; // unused bkgd.red = p2dp->bgcolor.red; bkgd.green = p2dp->bgcolor.green; bkgd.blue = p2dp->bgcolor.blue; // libpng may use bkgd.gray if bkgd.red==bkgd.green==bkgd.blue. // Not sure if that's a libpng bug or not. bkgd.gray = p2dp->bgcolor.red; if(png_bit_depth>8) { bkgd.red = (bkgd.red <<8)|bkgd.red; bkgd.green= (bkgd.green<<8)|bkgd.green; bkgd.blue = (bkgd.blue <<8)|bkgd.blue; bkgd.gray = (bkgd.gray <<8)|bkgd.gray; } if(is_grayscale) { /* assert(png_bit_depth>8); */ /* Need to expand to full RGB if unless background is pure gray */ if(bkgd.red!=bkgd.green || bkgd.red!=bkgd.blue) { png_set_gray_to_rgb(png_ptr); // png_set_tRNS_to_alpha() is called here because otherwise // binary transparency for 16-bps grayscale images doesn't // work. Libpng will think black pixels are transparent. // I don't know exactly why it works. It does *not* add an // alpha channel, as you might think (adding an alpha // channnel makes no sense if you are using // png_set_background). // // Here's an alternate hack that also seems to work, but // uses direct structure access: // // png_ptr->trans_values.red = // png_ptr->trans_values.green = // png_ptr->trans_values.blue = png_ptr->trans_values.gray; if(has_trns) png_set_tRNS_to_alpha(png_ptr); png_set_background(png_ptr, &bkgd, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); } else { // gray custom background png_set_background(png_ptr, &bkgd, PNG_BACKGROUND_GAMMA_SCREEN, 1, 1.0); } } else { png_set_background(png_ptr, &bkgd, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); } } notrans: // If we don't have any background color at all that we can use, // strip the alpha channel. if(has_alpha_channel && !has_bkgd && !(p2dp->flags & PNGD_USE_CUSTOM_BG) ) { png_set_strip_alpha(png_ptr); } if(png_bit_depth>8) png_set_strip_16(png_ptr); if (png_get_sRGB(png_ptr, info_ptr, &i)) { has_gama=1; file_gamma = 0.45455; } else if(png_get_gAMA(png_ptr, info_ptr, &file_gamma)) { has_gama=1; } else { has_gama=0; file_gamma = 0.45455; } if(imginfo && has_gama) { p2dp->file_gamma=file_gamma; p2dp->flags |= PNGD_GAMMA_RETURNED; } manual_gamma=0; if(p2dp->flags&PNGD_GAMMA_CORRECTION) { if(!is_grayscale || png_bit_depth>8 || has_alpha_channel) { png_set_gamma(png_ptr, screen_gamma, file_gamma); //png_ptr->transformations |= 0x2000; // hack for old libpng versions } else manual_gamma=1; if(has_bkgd) { // Gamma correct the background color (if we got it from the file) // before returning it to the app. gamma_correct(screen_gamma,file_gamma,&bkgd_color.red,&bkgd_color.green,&bkgd_color.blue); } } png_read_update_info(png_ptr, info_ptr); // color type may have changed, due to our transformations color_type = png_get_color_type(png_ptr,info_ptr); switch(color_type) { case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: dib_bpp=24; palette_entries=0; png_set_bgr(png_ptr); break; case PNG_COLOR_TYPE_PALETTE: dib_bpp=png_bit_depth; png_get_PLTE(png_ptr,info_ptr,&png_palette,&palette_entries); break; case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: dib_bpp=png_bit_depth; if(png_bit_depth>8) dib_bpp=8; palette_entries= 1<0) && (res_y>0)) { p2dp->res_x=res_x; p2dp->res_y=res_y; p2dp->res_units=res_unit_type; p2dp->flags |= PNGD_RES_RETURNED; } } // DIB scanlines are padded to 4-byte boundaries. dib_bytesperrow= (((width * dib_bpp)+31)/32)*4; p2dp->dibsize=sizeof(BITMAPINFOHEADER)+4*palette_entries+height*dib_bytesperrow; if(use_heapalloc) lpdib = (unsigned char*)HeapAlloc(heap,HEAP_ZERO_MEMORY,p2dp->dibsize); else lpdib = (unsigned char*)GlobalAlloc(GPTR,p2dp->dibsize); if(!lpdib) { rv=PNGD_E_NOMEM; goto abort; } p2dp->lpdib = (LPBITMAPINFOHEADER)lpdib; row_pointers=(unsigned char**)malloc(height*sizeof(unsigned char*)); if(!row_pointers) { rv=PNGD_E_NOMEM; goto abort; } // yes, there is some redundancy here p2dp->palette_offs=sizeof(BITMAPINFOHEADER); p2dp->bits_offs =sizeof(BITMAPINFOHEADER) + 4*palette_entries; dib_palette= &lpdib[p2dp->palette_offs]; p2dp->palette= (RGBQUAD*)dib_palette; dib_bits = &lpdib[p2dp->bits_offs]; p2dp->lpbits = (VOID*)dib_bits; p2dp->palette_colors = palette_entries; // set up the DIB palette, if needed switch(color_type) { case PNG_COLOR_TYPE_PALETTE: for(i=0;ipalette[i].rgbRed = png_palette[i].red; p2dp->palette[i].rgbGreen = png_palette[i].green; p2dp->palette[i].rgbBlue = png_palette[i].blue; } break; case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: for(i=0;ipalette[i].rgbRed = p2dp->palette[i].rgbGreen = p2dp->palette[i].rgbBlue = (i*255)/(palette_entries-1); //if(dib_bpp<=8) { //if(png_bit_depth<16) { if(manual_gamma) { gamma_correct(screen_gamma,file_gamma, &(p2dp->palette[i].rgbRed), &(p2dp->palette[i].rgbGreen), &(p2dp->palette[i].rgbBlue)); } } if(manual_trns) { p2dp->palette[trns_color].rgbRed = bkgd_color.red; p2dp->palette[trns_color].rgbGreen = bkgd_color.green; p2dp->palette[trns_color].rgbBlue = bkgd_color.blue; } break; } for(j=0;j<(int)height;j++) { row_pointers[height-1-j]= &dib_bits[j*dib_bytesperrow]; } png_read_image(png_ptr, row_pointers); // special handling for this bit depth, since it doesn't exist in DIBs // expand 2bpp to 4bpp if(png_bit_depth==2) { tmprow = (unsigned char*)malloc((width+3)/4 ); if(!tmprow) { rv=PNGD_E_NOMEM; goto abort; } for(j=0;j<(int)height;j++) { CopyMemory(tmprow, row_pointers[j], (width+3)/4 ); ZeroMemory(row_pointers[j], (width+1)/2 ); for(i=0;i<(int)width;i++) { row_pointers[j][i/2] |= ( ((tmprow[i/4] >> (2*(3-i%4)) ) & 0x03)<< (4*(1-i%2)) ); } } free((void*)tmprow); } free((void*)row_pointers); row_pointers=NULL; png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); png_ptr=NULL; fclose(fp); fp=NULL; // fill in the DIB header fields p2dp->lpdib->biSize= sizeof(BITMAPINFOHEADER); p2dp->lpdib->biWidth= width; p2dp->lpdib->biHeight= height; p2dp->lpdib->biPlanes= 1; p2dp->lpdib->biBitCount= dib_bpp; p2dp->lpdib->biCompression= BI_RGB; // uncompressed // biSizeImage can also be 0 in uncompressed bitmaps p2dp->lpdib->biSizeImage= height*dib_bytesperrow; if(has_phys) { if(res_unit_type==1) { p2dp->lpdib->biXPelsPerMeter= res_x; p2dp->lpdib->biYPelsPerMeter= res_y; } } p2dp->lpdib->biClrUsed= palette_entries; p2dp->lpdib->biClrImportant= 0; if(has_bkgd || (p2dp->flags&PNGD_USE_CUSTOM_BG)) { // return the background color if one was used p2dp->bgcolor.red = bkgd_color.red; p2dp->bgcolor.green = bkgd_color.green; p2dp->bgcolor.blue = bkgd_color.blue; p2dp->flags |= PNGD_BG_RETURNED; } return PNGD_E_SUCCESS; abort: if(png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); if(fp) fclose(fp); if(row_pointers) free((void*)row_pointers); if(lpdib) { if(use_heapalloc) HeapFree(heap,0,(LPVOID)lpdib); else GlobalFree((HGLOBAL)lpdib); } // If we don't have an error message yet, use a // default one based on the code if(!strlen(errmsg)) { pngd_get_error_message(rv,errmsg); } return rv; } //-------------------------------------------------------------------- int write_dib_to_png(PNGD_D2PINFO *d2pp) { png_structp png_ptr; png_infop info_ptr; jmp_buf jbuf; struct errstruct errinfo; png_text text_ptr[1]; unsigned char *bits; unsigned char *newimage; RGBQUAD* dib_palette; int headersize, dib_bpp; png_uint_32 res_x, res_y; png_color_8 pngc8; int height,width; int palette_entries; int topdown; int dib_bytesperrow; int compression; FILE *fp; png_color png_palette[256]; unsigned char **row_pointers; int i,x,y,size; DWORD *bitfields; unsigned int v; int bf_format; // bitfields format identifier int iscompressed; int bfsize; // bytes in "bitfields" section int palsize; // bytes in palette int palentsize; // bytes in a palette entry: 3 or 4; BITMAPCOREHEADER *lpolddib; int rv; // return code char *errmsg; char dummy_errmsg[MAX_ERRMSGLEN]; if(d2pp->structsize != sizeof(PNGD_D2PINFO)) return PNGD_E_VERSION; rv=PNGD_E_ERROR; // this should always get changed before returning png_ptr=NULL; info_ptr=NULL; fp=NULL; row_pointers=NULL; newimage=NULL; if(d2pp->errmsg) errmsg=d2pp->errmsg; else errmsg=dummy_errmsg; errmsg[0]='\0'; headersize= d2pp->lpdib->biSize; if(headersize<40 && headersize!=12) { sprintf(errmsg,"Unexpected BMP header size (%d)",headersize); rv=PNGD_E_BADBMP; goto abort; } if(headersize==12) { // This is to support an old kind of DIBs (OS/2) that aren't really // used anymore. palentsize= 3; lpolddib= (BITMAPCOREHEADER*) d2pp->lpdib; width= lpolddib->bcWidth; height= lpolddib->bcHeight; dib_bpp= lpolddib->bcBitCount; compression = BI_RGB; res_x = res_y = 0; // This will get adjusted later if there is a palette. // Not sure it's right, though. Do old DIBs always have a // full-sized palette? palette_entries=0; } else { palentsize=4; width= d2pp->lpdib->biWidth; height= d2pp->lpdib->biHeight; dib_bpp= d2pp->lpdib->biBitCount; compression= d2pp->lpdib->biCompression; res_x = d2pp->lpdib->biXPelsPerMeter; res_y = d2pp->lpdib->biYPelsPerMeter; palette_entries = d2pp->lpdib->biClrUsed; } // supposedly, if the height is negative, the top scanline is stored first topdown=0; if(height<0) { height= -height; topdown=1; } // sanity check if(height<1 || height>1000000 || width<1 || width>1000000) { sprintf(errmsg,"Unreasonable image dimensions (%dx%d)",width,height); rv=PNGD_E_BADBMP; goto abort; } // only certain combinations of compression and bpp are allowed switch(compression) { case BI_RGB: if(dib_bpp!=1 && dib_bpp!=4 && dib_bpp!=8 && dib_bpp!=16 && dib_bpp!=24 && dib_bpp!=32) { sprintf(errmsg,"Unsupported bit depth (%d)",dib_bpp); rv=PNGD_E_UNSUPP; goto abort; } break; case BI_RLE4: if(dib_bpp!=4) { rv=PNGD_E_UNSUPP; goto abort; } break; case BI_RLE8: if(dib_bpp!=8) { rv=PNGD_E_UNSUPP; goto abort; } break; case BI_BITFIELDS: if(dib_bpp!=16 && dib_bpp!=32) { rv=PNGD_E_UNSUPP; goto abort; } break; default: sprintf(errmsg,"Unsupported compression scheme"); return PNGD_E_UNSUPP; } iscompressed= (compression==BI_RLE4 || compression==BI_RLE8); // uncompressed dibs are padded to 4-byte bondaries dib_bytesperrow= (((width * dib_bpp)+31)/32)*4; if(dib_bpp<16) { if(palette_entries==0) palette_entries = 1<8) { // palette_entries=0; //} //else { // // 0 means it has a full palette // if(palette_entries==0) palette_entries = 1<dibsize) { if(size>d2pp->dibsize) { rv=PNGD_E_BADBMP; goto abort; } } if(d2pp->lpbits) { if(d2pp->bitssize && !iscompressed) { // bounds check size=dib_bytesperrow*height; if(size>d2pp->bitssize) { rv=PNGD_E_BADBMP; goto abort; } } bits=(unsigned char*)d2pp->lpbits; } else { // If not provided by user, assume the bits immediately // follow the palette. if(d2pp->dibsize && !iscompressed) { // bounds check size= headersize+bfsize+palsize+dib_bytesperrow*height; if(size>d2pp->dibsize) { rv=PNGD_E_BADBMP; goto abort; } } bits= &((unsigned char*)(d2pp->lpdib))[headersize+bfsize+palsize]; } bitfields = (DWORD*) ( &((unsigned char*)(d2pp->lpdib))[headersize] ); dib_palette = (RGBQUAD*) ( &((unsigned char*)(d2pp->lpdib))[headersize+bfsize] ); //bitfields=(DWORD*)dib_palette; bf_format=0; if(compression==BI_BITFIELDS) { if(dib_bpp==16) { if (bitfields[0]==0x00007c00 && bitfields[1]==0x000003e0 && bitfields[2]==0x0000001f) bf_format=11; // 555 else if(bitfields[0]==0x0000f800 && bitfields[1]==0x000007e0 && bitfields[2]==0x0000001f) bf_format=12; // 565 else { rv=PNGD_E_UNSUPP; goto abort; } } if(dib_bpp==32) { if (bitfields[0]==0x00ff0000 && bitfields[1]==0x0000ff00 && bitfields[2]==0x000000ff) bf_format=21; else { rv=PNGD_E_UNSUPP; goto abort; } } } if(bf_format==0 && dib_bpp==16) bf_format=10; if(bf_format==0 && dib_bpp==32) bf_format=20; // Done analyzing the DIB, now time to convert it to PNG // jbuf: see comments in read_png_to_dib() errinfo.jbufp = &jbuf; errinfo.errmsg = errmsg; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (void*)(&errinfo), my_error_fn, my_warning_fn); if (!png_ptr) { rv=PNGD_E_NOMEM; goto abort; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { //png_destroy_write_struct(&png_ptr,(png_infopp)NULL); rv=PNGD_E_NOMEM; goto abort; } if(setjmp(jbuf)) { // we'll get here if an error occurred in any of the following // png_ functions rv=PNGD_E_LIBPNG; goto abort; } fp=fopen(d2pp->pngfn,"wb"); if(!fp) { rv=PNGD_E_WRITE; goto abort; } png_init_io(png_ptr, fp); png_set_IHDR(png_ptr, info_ptr, width, height, (dib_bpp>8)?8:dib_bpp, (dib_bpp>8)?PNG_COLOR_TYPE_RGB:PNG_COLOR_TYPE_PALETTE, (d2pp->flags&PNGD_INTERLACED)?PNG_INTERLACE_ADAM7:PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); // write sRGB and gAMA chunks if(!(d2pp->flags&PNGD_NO_GAMMA_LABEL)) { png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_RELATIVE); png_set_gAMA(png_ptr, info_ptr, 0.45455); } // For 16-bit DIBs, we get to write an sBIT chunk. if(bf_format==10 || bf_format==11) { pngc8.red= 5; pngc8.green= 5; pngc8.blue= 5; png_set_sBIT(png_ptr, info_ptr, &pngc8); } if(bf_format==12) { pngc8.red= 5; pngc8.green= 6; pngc8.blue= 5; png_set_sBIT(png_ptr, info_ptr, &pngc8); } // pHYs if(res_x>0 && res_y>0) png_set_pHYs(png_ptr, info_ptr, res_x, res_y, 1); if(palette_entries>0) { for(i=0;i8) png_set_bgr(png_ptr); png_write_info(png_ptr, info_ptr); row_pointers=(unsigned char**)malloc(height*sizeof(unsigned char*)); if(!row_pointers) { rv=PNGD_E_NOMEM; goto abort; } if(dib_bpp==16 || dib_bpp==32) { // Special handling for these bit depths. // This uses a lot of memory, and could be improved by processing // one line at a time (but that makes it tricky to write interlaced // images). newimage=(unsigned char*)malloc(height*width*3); if(!newimage) { rv=PNGD_E_NOMEM; goto abort; } for(y=0;y>2; // blue newimage[(y*width+x)*3+1]= (v & 0x000003e0)>>2 | (v & 0x000003e0)>>7; // green newimage[(y*width+x)*3+2]= (v & 0x00007c00)>>7 | (v & 0x00007c00)>>12; // red break; case 12: // 16-bit, format 565 (RRRRRGGG GGGBBBBB) v= bits[y*dib_bytesperrow+x*2+0] | (bits[y*dib_bytesperrow+x*2+1]<<8); newimage[(y*width+x)*3+0]= (v & 0x0000001f)<<3 | (v & 0x0000001f)>>2; // blue newimage[(y*width+x)*3+1]= (v & 0x000007e0)>>3 | (v & 0x000007e0)>>9; // green newimage[(y*width+x)*3+2]= (v & 0x0000f800)>>8 | (v & 0x0000f800)>>13; // red break; case 20: case 21: // 32-bit, every 4th byte wasted (b g r x) newimage[(y*width+x)*3+0]= bits[y*dib_bytesperrow+x*4+0]; // blue newimage[(y*width+x)*3+1]= bits[y*dib_bytesperrow+x*4+1]; // green newimage[(y*width+x)*3+2]= bits[y*dib_bytesperrow+x*4+2]; // red break; } } } for(i=0;ilpdib, headersize+bfsize+palsize, bits); if(!newimage) { rv=PNGD_E_NOMEM; goto abort; } for(i=0;isoftware) { text_ptr[0].key = "Software"; text_ptr[0].text = d2pp->software; text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; png_set_text(png_ptr, info_ptr, text_ptr, 1); } png_write_end(png_ptr, info_ptr); rv=PNGD_E_SUCCESS; abort: if(png_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); if(fp) fclose(fp); if(row_pointers) GlobalFree(row_pointers); if(newimage) GlobalFree(newimage); // If we don't have an error message yet, use a // default one based on the code if(!strlen(errmsg)) { pngd_get_error_message(rv,errmsg); } return rv; } char* pngdib_get_version_string(void) { return PNGDIB_SRC_VERSION_STRING; } int pngdib_get_version(void) { return PNGDIB_SRC_VERSION; }