$NetBSD: patch-png1,v 1.1 1999/06/13 16:23:53 hubertf Exp $ The source in this patch is available at http://www.mit.edu/afs/athena/contrib/graphics/src/xv/patches/png/xvpng.c --------------------------------------------------------------------------- --- /dev/null Sun Jun 13 01:55:42 1999 +++ xvpng.c Fri Jun 14 01:42:11 1996 @@ -0,0 +1,965 @@ +/* + * xvpng.c - load and write routines for 'PNG' format pictures + * + * callable functions + * + * CreatePNGW() + * PNGDialog(vis) + * PNGCheckEvent(xev) + * PNGSaveParams(fname, col) + * LoadPNG(fname, pinfo) + */ + +/*#include "copyright.h"*/ +/* (c) 1995 by Alexander Lehmann + * this file is a suplement to xv and is supplied under the same copying + * conditions (except the shareware part) + * Modified by Andreas Dilger to fix + * error handling for bad PNGs, add dialogs for interlacing and + * compression selection, and upgrade to libpng-0.89 + * The copyright will be passed on to JB at some future point if he + * so desires. + */ + +#include "xv.h" + +#ifdef HAVE_PNG + +#include "png.h" + +/*** Stuff for PNG Dialog box ***/ +#define PWIDE 318 +#define PHIGH 215 + +#define DISPLAY_GAMMA 2.20 /* Default display gamma */ +/* Default zlib compression level +#define COMPRESSION Z_BEST_COMPRESSION +*/ +#define COMPRESSION 6 + +#define DWIDE 86 +#define DHIGH 104 +#define PFX PWIDE-93 +#define PFY 44 +#define PFH 20 + +#define P_BOK 0 +#define P_BCANC 1 +#define P_NBUTTS 2 + +#define BUTTH 24 + +/*** local functions ***/ +static void drawPD PARM((int, int, int, int)); +static void clickPD PARM((int, int)); +static void doCmd PARM((int)); +static void writePNG PARM((void)); +static int WritePNG PARM((FILE *, byte *, int, int, int, + byte *, byte *, byte *, int)); + +static void png_xv_error PARM((png_struct *png_ptr, char *message)); +static void png_xv_warning PARM((png_struct *png_ptr, char *message)); + +/*** local variables ***/ +static char *filename; +static char *fbasename; +static int colorType; +static int read_anything; +static double Display_Gamma = DISPLAY_GAMMA; + +static DIAL cDial, gDial; +static BUTT pbut[P_NBUTTS]; +static CBUTT interCB; +static CBUTT FdefCB, FnoneCB, FsubCB, FupCB, FavgCB, FPaethCB; + +/**************************************************************************/ +/* PNG SAVE DIALOG ROUTINES ***********************************************/ +/**************************************************************************/ + + +/*******************************************/ +void CreatePNGW() +{ + pngW = CreateWindow("xv png", "XVPNG", NULL, + PWIDE, PHIGH, infofg, infobg, 0); + if (!pngW) FatalError("can't create PNG window!"); + + XSelectInput(theDisp, pngW, ExposureMask | ButtonPressMask | KeyPressMask); + + DCreate(&cDial, pngW, 12, 25, DWIDE, DHIGH, (double)Z_NO_COMPRESSION, + (double)Z_BEST_COMPRESSION, COMPRESSION, 1.0, 2.0, + infofg, infobg, hicol, locol, "Compression", NULL); + + DCreate(&gDial, pngW, DWIDE+27, 25, DWIDE, DHIGH, 1.0, 3.5,DISPLAY_GAMMA,0.01,0.2, + infofg, infobg, hicol, locol, "Disp. Gamma", NULL); + + CBCreate(&interCB, pngW, DWIDE+30, DHIGH+3*LINEHIGH+2, "interlace", + infofg, infobg, hicol, locol); + + CBCreate(&FdefCB, pngW, PFX, PFY, "Default", + infofg, infobg, hicol, locol); + FdefCB.val = 1; + + CBCreate(&FnoneCB, pngW, PFX, FdefCB.y + PFH + 4, "none", + infofg, infobg, hicol, locol); + CBCreate(&FsubCB, pngW, PFX, FnoneCB.y + PFH, "sub", + infofg, infobg, hicol, locol); + CBCreate(&FupCB, pngW, PFX, FsubCB.y + PFH, "up", + infofg, infobg, hicol, locol); + CBCreate(&FavgCB, pngW, PFX, FupCB.y + PFH, "average", + infofg, infobg, hicol, locol); + CBCreate(&FPaethCB, pngW, PFX, FavgCB.y + PFH, "Paeth", + infofg, infobg, hicol, locol); + + FnoneCB.val = FsubCB.val = FupCB.val = FavgCB.val = FPaethCB.val = 1; + CBSetActive(&FnoneCB, !FdefCB.val); + CBSetActive(&FsubCB, !FdefCB.val); + CBSetActive(&FupCB, !FdefCB.val); + CBSetActive(&FavgCB, !FdefCB.val); + CBSetActive(&FPaethCB, !FdefCB.val); + + BTCreate(&pbut[P_BOK], pngW, PWIDE-180-1, PHIGH-10-BUTTH-1, 80, BUTTH, + "Ok", infofg, infobg, hicol, locol); + BTCreate(&pbut[P_BCANC], pngW, PWIDE-90-1, PHIGH-10-BUTTH-1, 80, BUTTH, + "Cancel", infofg, infobg, hicol, locol); + + XMapSubwindows(theDisp, pngW); +} + + +/*******************************************/ +void PNGDialog(vis) + int vis; +{ + if (vis) { + CenterMapWindow(pngW, pbut[P_BOK].x + (int) pbut[P_BOK].w/2, + pbut[P_BOK].y + (int) pbut[P_BOK].h/2, + PWIDE, PHIGH); + } + else XUnmapWindow(theDisp, pngW); + pngUp = vis; +} + + +/*******************************************/ +int PNGCheckEvent(xev) + XEvent *xev; +{ + /* check event to see if it's for one of our subwindows. If it is, + deal accordingly, and return '1'. Otherwise, return '0' */ + + int rv; + rv = 1; + + if (!pngUp) return 0; + + if (xev->type == Expose) { + int x,y,w,h; + XExposeEvent *e = (XExposeEvent *) xev; + x = e->x; y = e->y; w = e->width; h = e->height; + + /* throw away excess expose events for 'dumb' windows */ + if (e->count > 0 && (e->window == cDial.win)) {} + + else if (e->window == pngW) drawPD(x, y, w, h); + else if (e->window == cDial.win) DRedraw(&cDial); + else if (e->window == gDial.win) DRedraw(&gDial); + else rv = 0; + } + + else if (xev->type == ButtonPress) { + XButtonEvent *e = (XButtonEvent *) xev; + int x,y; + x = e->x; y = e->y; + + if (e->button == Button1) { + if (e->window == pngW) clickPD(x,y); + else if (e->window == cDial.win) DTrack(&cDial,x,y); + else if (e->window == gDial.win) DTrack(&gDial,x,y); + else rv = 0; + } /* button1 */ + else rv = 0; + } /* button press */ + + else if (xev->type == KeyPress) { + XKeyEvent *e = (XKeyEvent *) xev; + char buf[128]; KeySym ks; + int stlen; + + stlen = XLookupString(e,buf,128,&ks,(XComposeStatus *) NULL); + buf[stlen] = '\0'; + + RemapKeyCheck(ks, buf, &stlen); + + if (e->window == pngW) { + if (stlen) { + if (buf[0] == '\r' || buf[0] == '\n') { /* enter */ + FakeButtonPress(&pbut[P_BOK]); + } + else if (buf[0] == '\033') { /* ESC */ + FakeButtonPress(&pbut[P_BCANC]); + } + } + } + else rv = 0; + } + else rv = 0; + + if (rv==0 && (xev->type == ButtonPress || xev->type == KeyPress)) { + XBell(theDisp, 50); + rv = 1; /* eat it */ + } + + return rv; +} + + +/*******************************************/ +void PNGSaveParams(fname, col) + char *fname; + int col; +{ + filename = fname; + colorType = col; +} + + +/*******************************************/ +static void drawPD(x, y, w, h) + int x, y, w, h; +{ + char *title = "Save PNG file..."; + + char ctitle1[20]; + char *ctitle2 = "Useful range"; + char *ctitle3 = "is 2 - 7."; + char *ctitle4 = "Uncompressed = 0"; + + char *ftitle = "Row Filters:"; + + char gtitle[20]; + + int i; + XRectangle xr; + + xr.x = x; xr.y = y; xr.width = w; xr.height = h; + XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted); + + XSetForeground(theDisp, theGC, infofg); + XSetBackground(theDisp, theGC, infobg); + + for (i=0; ix, bp->y, bp->w, bp->h)) break; + } + + if (ijmpbuf)) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return -1; + } + + png_init_io(png_ptr, fp); + + png_set_compression_level(png_ptr, (int)cDial.val); + + /* Don't bother filtering if we aren't compressing the image */ + if (FdefCB.val) + { + if ((int)cDial.val == 0) + png_set_filter(png_ptr, 0, PNG_FILTER_NONE); + } + else + { + filter = FnoneCB.val ? PNG_FILTER_NONE : 0; + filter |= FsubCB.val ? PNG_FILTER_SUB : 0; + filter |= FupCB.val ? PNG_FILTER_UP : 0; + filter |= FavgCB.val ? PNG_FILTER_AVG : 0; + filter |= FPaethCB.val ? PNG_FILTER_PAETH : 0; + + png_set_filter(png_ptr, 0, filter); + } + + info_ptr->width = w; + info_ptr->height = h; + + info_ptr->interlace_type = interCB.val ? 1 : 0; + + if (colorType == F_FULLCOLOR || colorType == F_REDUCED) { + if(ptype == PIC24) { + linesize = 3*w; + info_ptr->color_type = PNG_COLOR_TYPE_RGB; + info_ptr->bit_depth = 8; + } else { + linesize = w; + info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; + if(numcols <= 2) + info_ptr->bit_depth = 1; + else + if(numcols <= 4) + info_ptr->bit_depth = 2; + else + if(numcols <= 16) + info_ptr->bit_depth = 4; + else + info_ptr->bit_depth = 8; + + for(i = 0; i < numcols; i++) { + palette[i].red = rmap[i]; + palette[i].green = gmap[i]; + palette[i].blue = bmap[i]; + } + info_ptr->num_palette = numcols; + info_ptr->palette = palette; + info_ptr->valid |= PNG_INFO_PLTE; + } + } + + else if(colorType == F_GREYSCALE || colorType == F_BWDITHER) { + info_ptr->color_type = PNG_COLOR_TYPE_GRAY; + if(colorType == F_BWDITHER) { + /* shouldn't happen */ + if (ptype == PIC24) FatalError("PIC24 and B/W Stipple in WritePNG()"); + + info_ptr->bit_depth = 1; + if(MONO(rmap[0], gmap[0], bmap[0]) > MONO(rmap[1], gmap[1], bmap[1])) { + remap[0] = 1; + remap[1] = 0; + } + else { + remap[0] = 0; + remap[1] = 1; + } + linesize = w; + } + else { + if(ptype == PIC24) { + linesize = w*3; + info_ptr->bit_depth = 8; + } + else { + int low_presc; + + linesize = w; + + for(i = 0; i < numcols; i++) + remap[i] = MONO(rmap[i], gmap[i], bmap[i]); + + for(; i < 256; i++) + remap[i]=0; + + info_ptr->bit_depth = 8; + + /* Note that this fails most of the time because of gamma */ + /* try to adjust to 4 bit prescision grayscale */ + + low_presc=1; + + for(i = 0; i < numcols; i++) { + if((remap[i] & 0x0f) * 0x11 != remap[i]) { + low_presc = 0; + break; + } + } + + if(low_presc) { + for(i = 0; i < numcols; i++) { + remap[i] &= 0xf; + } + info_ptr->bit_depth = 4; + + /* try to adjust to 2 bit prescision grayscale */ + + for(i = 0; i < numcols; i++) { + if((remap[i] & 0x03) * 0x05 != remap[i]) { + low_presc = 0; + break; + } + } + } + + if(low_presc) { + for(i = 0; i < numcols; i++) { + remap[i] &= 3; + } + info_ptr->bit_depth = 2; + + /* try to adjust to 1 bit prescision grayscale */ + + for(i = 0; i < numcols; i++) { + if((remap[i] & 0x01) * 0x03 != remap[i]) { + low_presc = 0; + break; + } + } + } + + if(low_presc) { + for(i = 0; i < numcols; i++) { + remap[i] &= 1; + } + info_ptr->bit_depth = 1; + } + } + } + } + + else + png_error(png_ptr, "Unknown colorstyle in WritePNG"); + + if ((text = (png_textp)malloc(sizeof(png_text)))) { + sprintf(software, "XV %s", REVDATE); + + text->compression = -1; + text->key = "Software"; + text->text = software; + text->text_length = strlen(text->text); + + info_ptr->max_text = 1; + info_ptr->num_text = 1; + info_ptr->text = text; + } + + Display_Gamma = gDial.val; /* Save the current gamma for loading */ + + info_ptr->gamma = 1.0/gDial.val; + info_ptr->valid |= PNG_INFO_gAMA; + + png_write_info(png_ptr, info_ptr); + + if(info_ptr->bit_depth < 8) + png_set_packing(png_ptr); + + pass=png_set_interlace_handling(png_ptr); + + if((png_line = malloc(linesize)) == NULL) + png_error(png_ptr, "cannot allocate temp image line"); + + for(i = 0; i < pass; i++) { + int j; + p = pic; + for(j = 0; j < h; j++) { + fflush(stdout); + if(info_ptr->color_type == PNG_COLOR_TYPE_GRAY) { + int k; + for(k = 0; k < w; k++) + png_line[k] = ptype==PIC24 ? MONO(p[k*3], p[k*3+1], p[k*3+2]) : + remap[p[k]]; + png_write_row(png_ptr, png_line); + } else /* rbg or palette */ + png_write_row(png_ptr, p); + if((j & 0x1f) == 0) WaitCursor(); + p += linesize; + } + } + + free(png_line); + + if (text) + { + if (picComments && strlen(picComments) && + (savecmnt = (char *)malloc((strlen(picComments) + 1)*sizeof(char)))) { + png_textp tp; + char *comment, *key; + + strcpy(savecmnt, picComments); + key = savecmnt; + tp = text; + info_ptr->num_text = 0; + + comment = strchr(key, ':'); + + do { + /* Allocate a larger structure for comments if necessary */ + if (info_ptr->num_text >= info_ptr->max_text) + { + if ((tp = + realloc(text, (info_ptr->num_text + 2)*sizeof(png_text))) == NULL) + { + break; + } + else + { + text = tp; + tp = &text[info_ptr->num_text]; + info_ptr->max_text += 2; + } + } + + /* See if it looks like a PNG keyword from LoadPNG */ + if(comment && comment[1] == ':' && comment - key <= 80) { + *(comment++) = '\0'; + *(comment++) = '\0'; + + /* If the comment is the 'Software' chunk XV writes, we remove it, + since we have already stored one */ + if (strcmp(key, "Software") == 0 && strncmp(comment, "XV", 2) == 0) { + key = strchr(comment, '\n'); + if(key) + key++; /* skip \n */ + comment = strchr(key, ':'); + } + /* We have another keyword and/or comment to write out */ + else { + tp->key = key; + tp->text = comment; + + /* We have to find the end of this comment, and the next keyword + if there is one */ + do { + key = comment = strchr(comment, ':'); + } while (key && key[1] != ':'); + + /* It looks like another keyword, go backward to the beginning */ + if (key) { + while(key > tp->text && *key != '\n') + key--; + + if (key > tp->text && comment - key <= 80) { + *key = '\0'; + key++; + } + } + + tp->text_length = strlen(tp->text); + + /* We don't have another keyword, so remove the last newline */ + if (!key && tp->text[tp->text_length - 1] == '\n') + { + tp->text[tp->text_length] = '\0'; + tp->text_length--; + } + + tp->compression = tp->text_length > 640 ? 0 : -1; + info_ptr->num_text++; + tp++; + } + } + /* It is just a generic comment */ + else { + tp->key = "Comment"; + tp->text = key; + tp->text_length = strlen(tp->text); + tp->compression = tp->text_length > 750 ? 0 : -1; + info_ptr->num_text++; + key = NULL; + } + } while (key && *key); + } + else + { + info_ptr->num_text = 0; + } + } + info_ptr->text = text; + + png_convert_from_time_t(&(info_ptr->mod_time), time(NULL)); + info_ptr->valid |= PNG_INFO_tIME; + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + if (text) + { + free(text); + if (savecmnt) + free(savecmnt); + } + + return 0; +} + + +/*******************************************/ +int LoadPNG(fname, pinfo) + char *fname; + PICINFO *pinfo; +/*******************************************/ +{ + /* returns '1' on success */ + + FILE *fp; + png_struct *png_ptr; + png_info *info_ptr; + png_color_16 my_background; + int i,j; + int linesize; + int filesize; + int pass; + size_t commentsize; + + fbasename = BaseName(fname); + + pinfo->pic = (byte *) NULL; + pinfo->comment = (char *) NULL; + + read_anything=0; + + /* open the file */ + fp = xv_fopen(fname,"r"); + if (!fp) + { + SetISTR(ISTR_WARNING,"%s: can't open file", fname); + return 0; + } + + /* find the size of the file */ + fseek(fp, 0L, 2); + filesize = ftell(fp); + fseek(fp, 0L, 0); + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, + png_xv_error, png_xv_warning); + if(!png_ptr) { + fclose(fp); + FatalError("malloc failure in LoadPNG"); + } + + info_ptr = png_create_info_struct(png_ptr); + + if(!info_ptr) { + fclose(fp); + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + FatalError("malloc failure in LoadPNG"); + } + + if(setjmp(png_ptr->jmpbuf)) { + fclose(fp); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + if(!read_anything) { + if(pinfo->pic) { + free(pinfo->pic); + pinfo->pic = NULL; + } + if(pinfo->comment) { + free(pinfo->comment); + pinfo->comment = NULL; + } + } + return read_anything; + } + + png_init_io(png_ptr, fp); + png_read_info(png_ptr, info_ptr); + + pinfo->w = pinfo->normw = info_ptr->width; + pinfo->h = pinfo->normh = info_ptr->height; + + pinfo->frmType = F_PNG; + + sprintf(pinfo->fullInfo, "PNG, %d bit ", + info_ptr->bit_depth * info_ptr->channels); + + switch(info_ptr->color_type) { + case PNG_COLOR_TYPE_PALETTE: + strcat(pinfo->fullInfo, "palette color"); + break; + + case PNG_COLOR_TYPE_GRAY: + strcat(pinfo->fullInfo, "grayscale"); + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + strcat(pinfo->fullInfo, "grayscale+alpha"); + break; + + case PNG_COLOR_TYPE_RGB: + strcat(pinfo->fullInfo, "truecolor"); + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + strcat(pinfo->fullInfo, "truecolor+alpha"); + break; + } + + sprintf(pinfo->fullInfo + strlen(pinfo->fullInfo), + ", %sinterlaced. (%d bytes)", + info_ptr->interlace_type ? "" : "non-", filesize); + + sprintf(pinfo->shrtInfo, "%dx%d PNG", info_ptr->width, info_ptr->height); + + if (info_ptr->bit_depth < 8) + png_set_packing(png_ptr); + + if (info_ptr->valid & PNG_INFO_gAMA) + png_set_gamma(png_ptr, Display_Gamma, info_ptr->gamma); + else + png_set_gamma(png_ptr, Display_Gamma, 0.45); + + if (info_ptr->valid & PNG_INFO_bKGD) + png_set_background(png_ptr, &info_ptr->background, + PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); + else { + my_background.red = my_background.green = my_background.blue = + my_background.gray = 0; + png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, + 0, Display_Gamma); + } + + if (info_ptr->bit_depth == 16) + png_set_strip_16(png_ptr); + + if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY || + info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (info_ptr->bit_depth == 1) + pinfo->colType = F_BWDITHER; + else + pinfo->colType = F_GREYSCALE; + png_set_expand(png_ptr); + } + + pass=png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + if(info_ptr->color_type == PNG_COLOR_TYPE_RGB || + info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + linesize = pinfo->w * 3; + pinfo->colType = F_FULLCOLOR; + pinfo->type = PIC24; + } else { + linesize = pinfo->w; + pinfo->type = PIC8; + if(info_ptr->color_type == PNG_COLOR_TYPE_GRAY || + info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + for(i = 0; i < 256; i++) + pinfo->r[i] = pinfo->g[i] = pinfo->b[i] = i; + } else { + pinfo->colType = F_FULLCOLOR; + for(i = 0; i < info_ptr->num_palette; i++) { + pinfo->r[i] = info_ptr->palette[i].red; + pinfo->g[i] = info_ptr->palette[i].green; + pinfo->b[i] = info_ptr->palette[i].blue; + } + } + } + pinfo->pic = calloc((size_t)(linesize*pinfo->h), (size_t)1); + + if(!pinfo->pic) { + png_error(png_ptr, "can't allocate space for PNG image"); + } + + png_start_read_image(png_ptr); + + for(i = 0; i < pass; i++) { + byte *p = pinfo->pic; + for(j = 0; j < pinfo->h; j++) { + png_read_row(png_ptr, p, NULL); + read_anything = 1; + if((j & 0x1f) == 0) WaitCursor(); + p += linesize; + } + } + + png_read_end(png_ptr, info_ptr); + + if(info_ptr->num_text > 0) { + commentsize = 1; + + for(i = 0; i < info_ptr->num_text; i++) + commentsize += strlen(info_ptr->text[i].key) + 1 + + info_ptr->text[i].text_length + 2; + + if((pinfo->comment = malloc(commentsize)) == NULL) { + png_warning(png_ptr,"can't allocate comment string"); + } + else { + pinfo->comment[0] = '\0'; + for(i = 0; i < info_ptr->num_text; i++) { + strcat(pinfo->comment, info_ptr->text[i].key); + strcat(pinfo->comment, "::"); + strcat(pinfo->comment, info_ptr->text[i].text); + strcat(pinfo->comment, "\n"); + } + } + } + + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + + fclose(fp); + + return 1; +} + + +/*******************************************/ +static void +png_xv_error(png_ptr, message) + png_struct *png_ptr; + char *message; +{ + SetISTR(ISTR_WARNING,"%s: libpng error: %s", fbasename, message); + + longjmp(png_ptr->jmpbuf, 1); +} + + +/*******************************************/ +static void +png_xv_warning(png_ptr, message) + png_struct *png_ptr; + char *message; +{ + if (!png_ptr) + return; + + SetISTR(ISTR_WARNING,"%s: libpng warning: %s", fbasename, message); +} + +#endif