1 /*
2  * xvpng.c - load and write routines for 'PNG' format pictures
3  *
4  * callable functions
5  *
6  *    CreatePNGW()
7  *    PNGDialog(vis)
8  *    PNGCheckEvent(xev)
9  *    PNGSaveParams(fname, col)
10  *    LoadPNG(fname, pinfo)
11  *    VersionInfoPNG()
12  */
13 
14 /*#include "copyright.h"*/
15 
16 /* (c) 1995 by Alexander Lehmann <lehmann@mathematik.th-darmstadt.de>
17  *   This file is a suplement to xv and is supplied under the same copying
18  *   conditions (except the shareware part).
19  *   The copyright will be passed on to JB at some future point if he
20  *   so desires.
21  *
22  * Modified by Andreas Dilger <adilger@enel.ucalgary.ca> to fix
23  *   error handling for bad PNGs, add dialogs for interlacing and
24  *   compression selection, and upgrade to libpng-0.89.
25  *
26  * Modified by Greg Roelofs, TenThumbs and others to fix bugs and add
27  *   features.  See README.jumbo for details.
28  */
29 
30 #include "xv.h"
31 
32 #ifdef HAVE_PNG
33 
34 #include "zlib.h"
35 #include "png.h"
36 
37 /*** Stuff for PNG Dialog box ***/
38 #define PWIDE 318
39 #define PHIGH 215
40 
41 #define DISPLAY_GAMMA 2.20  /* default display gamma */
42 #define COMPRESSION   6     /* default zlib compression level, not max
43                                (Z_BEST_COMPRESSION) */
44 
45 /* old
46 #define HAVE_tRNS  (info_ptr->valid & PNG_INFO_tRNS) */
47 #define HAVE_tRNS  png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)
48 
49 #define DWIDE    86
50 #define DHIGH    104
51 #define PFX      (PWIDE-93)
52 #define PFY      44
53 #define PFH      20
54 
55 #define P_BOK    0
56 #define P_BCANC  1
57 #define P_NBUTTS 2
58 
59 #define BUTTH    24
60 
61 #define LF       10   /* a.k.a. '\n' on ASCII machines */
62 #define CR       13   /* a.k.a. '\r' on ASCII machines */
63 
64 /*** local functions ***/
65 static    void drawPD         PARM((int, int, int, int));
66 static    void clickPD        PARM((int, int));
67 static    void doCmd          PARM((int));
68 static    void writePNG       PARM((void));
69 static    int  WritePNG       PARM((FILE *, byte *, int, int, int,
70                                     byte *, byte *, byte *, int));
71 
72 static    void png_xv_error   PARM((png_structp png_ptr,
73                                     png_const_charp message));
74 static    void png_xv_warning PARM((png_structp png_ptr,
75                                     png_const_charp message));
76 
77 /*** local variables ***/
78 static char *filename;
79 static const char *fbasename;
80 static int   colorType;
81 static int   read_anything;
82 static double Display_Gamma = DISPLAY_GAMMA;
83 
84 static DIAL  cDial, gDial;
85 static BUTT  pbut[P_NBUTTS];
86 static CBUTT interCB;
87 static CBUTT FdefCB, FnoneCB, FsubCB, FupCB, FavgCB, FPaethCB;
88 
89 
90 #ifdef PNG_NO_STDIO
91 /* NOTE:  Some sites configure their version of the PNG Library without
92  *        Standard I/O Library interfaces in order to avoid unnecessary inter-
93  * library dependencies at link time for applications that don't need Standard
94  * I/O.  If your site is one of these, the following skeletal stubs, copied
95  * from libpng code, should be enough for this module.  --Scott B. Marovich,
96  * Hewlett-Packard Laboratories, March 2001.
97  */
98 static void
png_default_read_data(png_structp png_ptr,png_bytep data,png_size_t length)99 png_default_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
100 {
101 
102    /* fread() returns 0 on error, so it is OK to store this in a png_size_t
103     * instead of an int, which is what fread() actually returns.
104     */
105    if (fread(data,1,length,(FILE *)png_ptr->io_ptr) != length)
106      png_error(png_ptr, "Read Error");
107 }
108 
109 static void
png_default_write_data(png_structp png_ptr,png_bytep data,png_size_t length)110 png_default_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
111 {
112    if (fwrite(data, 1, length, (FILE *)png_ptr->io_ptr) != length)
113      png_error(png_ptr, "Write Error");
114 }
115 #endif /* PNG_NO_STDIO */
116 
117 
118 /**************************************************************************/
119 /* PNG SAVE DIALOG ROUTINES ***********************************************/
120 /**************************************************************************/
121 
122 
123 /*******************************************/
CreatePNGW()124 void CreatePNGW()
125 {
126   pngW = CreateWindow("xv png", "XVPNG", NULL,
127                       PWIDE, PHIGH, infofg, infobg, 0);
128   if (!pngW) FatalError("can't create PNG window!");
129 
130   XSelectInput(theDisp, pngW, ExposureMask | ButtonPressMask | KeyPressMask);
131 
132   DCreate(&cDial, pngW,  12, 25, DWIDE, DHIGH, (double)Z_NO_COMPRESSION,
133           (double)Z_BEST_COMPRESSION, COMPRESSION, 1.0, 3.0,
134           infofg, infobg, hicol, locol, "Compression", NULL);
135 
136   DCreate(&gDial, pngW, DWIDE+27, 25, DWIDE, DHIGH, 1.0, 3.5,DISPLAY_GAMMA,0.01,0.2,
137           infofg, infobg, hicol, locol, "Disp. Gamma", NULL);
138 
139   CBCreate(&interCB, pngW,  DWIDE+30, DHIGH+3*LINEHIGH+2, "interlace",
140            infofg, infobg, hicol, locol);
141 
142   CBCreate(&FdefCB,   pngW, PFX, PFY, "Default",
143            infofg, infobg, hicol, locol);
144   FdefCB.val = 1;
145 
146   CBCreate(&FnoneCB,  pngW, PFX, FdefCB.y + PFH + 4, "none",
147            infofg, infobg, hicol, locol);
148   CBCreate(&FsubCB,   pngW, PFX, FnoneCB.y + PFH, "sub",
149            infofg, infobg, hicol, locol);
150   CBCreate(&FupCB,    pngW, PFX, FsubCB.y  + PFH, "up",
151            infofg, infobg, hicol, locol);
152   CBCreate(&FavgCB,   pngW, PFX, FupCB.y   + PFH, "average",
153            infofg, infobg, hicol, locol);
154   CBCreate(&FPaethCB, pngW, PFX, FavgCB.y  + PFH, "Paeth",
155            infofg, infobg, hicol, locol);
156 
157   FnoneCB.val = FsubCB.val = FupCB.val = FavgCB.val = FPaethCB.val = 1;
158   CBSetActive(&FnoneCB, !FdefCB.val);
159   CBSetActive(&FsubCB, !FdefCB.val);
160   CBSetActive(&FupCB, !FdefCB.val);
161   CBSetActive(&FavgCB, !FdefCB.val);
162   CBSetActive(&FPaethCB, !FdefCB.val);
163 
164   BTCreate(&pbut[P_BOK], pngW, PWIDE-180-1, PHIGH-10-BUTTH-1, 80, BUTTH,
165           "Ok", infofg, infobg, hicol, locol);
166   BTCreate(&pbut[P_BCANC], pngW, PWIDE-90-1, PHIGH-10-BUTTH-1, 80, BUTTH,
167           "Cancel", infofg, infobg, hicol, locol);
168 
169   XMapSubwindows(theDisp, pngW);
170 }
171 
172 
173 /*******************************************/
PNGDialog(vis)174 void PNGDialog(vis)
175      int vis;
176 {
177   if (vis) {
178     CenterMapWindow(pngW, pbut[P_BOK].x + (int) pbut[P_BOK].w/2,
179                           pbut[P_BOK].y + (int) pbut[P_BOK].h/2,
180                     PWIDE, PHIGH);
181   }
182   else XUnmapWindow(theDisp, pngW);
183   pngUp = vis;
184 }
185 
186 
187 /*******************************************/
PNGCheckEvent(xev)188 int PNGCheckEvent(xev)
189      XEvent *xev;
190 {
191   /* check event to see if it's for one of our subwindows.  If it is,
192      deal accordingly, and return '1'.  Otherwise, return '0' */
193 
194   int rv;
195   rv = 1;
196 
197   if (!pngUp) return 0;
198 
199   if (xev->type == Expose) {
200     int x,y,w,h;
201     XExposeEvent *e = (XExposeEvent *) xev;
202     x = e->x; y = e->y; w = e->width; h = e->height;
203 
204     /* throw away excess expose events for 'dumb' windows */
205     if (e->count > 0 && (e->window == cDial.win)) {}
206 
207     else if (e->window == pngW)        drawPD(x, y, w, h);
208     else if (e->window == cDial.win)   DRedraw(&cDial);
209     else if (e->window == gDial.win)   DRedraw(&gDial);
210     else rv = 0;
211   }
212 
213   else if (xev->type == ButtonPress) {
214     XButtonEvent *e = (XButtonEvent *) xev;
215     int x,y;
216     x = e->x;  y = e->y;
217 
218     if (e->button == Button1) {
219       if      (e->window == pngW)       clickPD(x,y);
220       else if (e->window == cDial.win)  DTrack(&cDial,x,y);
221       else if (e->window == gDial.win)  DTrack(&gDial,x,y);
222       else rv = 0;
223     }  /* button1 */
224     else rv = 0;
225   }  /* button press */
226 
227   else if (xev->type == KeyPress) {
228     XKeyEvent *e = (XKeyEvent *) xev;
229     char buf[128];  KeySym ks;
230     int stlen;
231 
232     stlen = XLookupString(e,buf,128,&ks,(XComposeStatus *) NULL);
233     buf[stlen] = '\0';
234 
235     RemapKeyCheck(ks, buf, &stlen);
236 
237     if (e->window == pngW) {
238       if (stlen) {
239         if (buf[0] == '\r' || buf[0] == '\n') { /* enter */
240           FakeButtonPress(&pbut[P_BOK]);
241         }
242         else if (buf[0] == '\033') {            /* ESC */
243           FakeButtonPress(&pbut[P_BCANC]);
244         }
245       }
246     }
247     else rv = 0;
248   }
249   else rv = 0;
250 
251   if (rv==0 && (xev->type == ButtonPress || xev->type == KeyPress)) {
252     XBell(theDisp, 50);
253     rv = 1;   /* eat it */
254   }
255 
256   return rv;
257 }
258 
259 
260 /*******************************************/
PNGSaveParams(fname,col)261 void PNGSaveParams(fname, col)
262      char *fname;
263      int col;
264 {
265   filename = fname;
266   colorType = col;
267 }
268 
269 
270 /*******************************************/
drawPD(x,y,w,h)271 static void drawPD(x, y, w, h)
272      int x, y, w, h;
273 {
274   const char *title   = "Save PNG file...";
275 
276   char ctitle1[20];
277   const char *ctitle2 = "Useful range";
278   const char *ctitle3 = "is 2 - 7.";
279   const char *ctitle4 = "Uncompressed = 0";
280 
281   const char *ftitle  = "Row Filters:";
282 
283   char gtitle[20];
284 
285   int i;
286   XRectangle xr;
287 
288   xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
289   XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);
290 
291   XSetForeground(theDisp, theGC, infofg);
292   XSetBackground(theDisp, theGC, infobg);
293 
294   for (i=0; i<P_NBUTTS; i++) BTRedraw(&pbut[i]);
295 
296   DrawString(pngW,       15,  6+ASCENT,                          title);
297 
298   sprintf(ctitle1, "Default = %d", COMPRESSION);
299   DrawString(pngW,       18,  6+DHIGH+cDial.y+ASCENT,            ctitle1);
300   DrawString(pngW,       17,  6+DHIGH+cDial.y+ASCENT+LINEHIGH,   ctitle2);
301   DrawString(pngW,       17,  6+DHIGH+cDial.y+ASCENT+2*LINEHIGH, ctitle3);
302   DrawString(pngW,       17,  6+DHIGH+cDial.y+ASCENT+3*LINEHIGH, ctitle4);
303 
304   sprintf(gtitle, "Default = %g", DISPLAY_GAMMA);
305   DrawString(pngW, DWIDE+30,  6+DHIGH+gDial.y+ASCENT,            gtitle);
306 
307   ULineString(pngW, FdefCB.x, FdefCB.y-3-DESCENT, ftitle);
308   XDrawRectangle(theDisp, pngW, theGC, FdefCB.x-11, FdefCB.y-LINEHIGH-3,
309                                        93, 8*LINEHIGH+15);
310   CBRedraw(&FdefCB);
311   XDrawLine(theDisp, pngW, theGC, FdefCB.x-11, FdefCB.y+LINEHIGH+4,
312                                   FdefCB.x+82, FdefCB.y+LINEHIGH+4);
313 
314   CBRedraw(&FnoneCB);
315   CBRedraw(&FupCB);
316   CBRedraw(&FsubCB);
317   CBRedraw(&FavgCB);
318   CBRedraw(&FPaethCB);
319 
320   CBRedraw(&interCB);
321 
322   XSetClipMask(theDisp, theGC, None);
323 }
324 
325 
326 /*******************************************/
clickPD(x,y)327 static void clickPD(x,y)
328      int x,y;
329 {
330   int i;
331   BUTT *bp;
332 
333   /* check BUTTs */
334 
335   for (i=0; i<P_NBUTTS; i++) {
336     bp = &pbut[i];
337     if (PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) break;
338   }
339 
340   if (i<P_NBUTTS) {  /* found one */
341     if (BTTrack(bp)) doCmd(i);
342   }
343 
344   /* check CBUTTs */
345 
346   else if (CBClick(&FdefCB,x,y)) {
347     int oldval = FdefCB.val;
348 
349     CBTrack(&FdefCB);
350 
351     if (oldval != FdefCB.val)
352     {
353       CBSetActive(&FnoneCB, !FdefCB.val);
354       CBSetActive(&FsubCB, !FdefCB.val);
355       CBSetActive(&FupCB, !FdefCB.val);
356       CBSetActive(&FavgCB, !FdefCB.val);
357       CBSetActive(&FPaethCB, !FdefCB.val);
358 
359       CBRedraw(&FnoneCB);
360       CBRedraw(&FupCB);
361       CBRedraw(&FsubCB);
362       CBRedraw(&FavgCB);
363       CBRedraw(&FPaethCB);
364     }
365   }
366   else if (CBClick(&FnoneCB,x,y))  CBTrack(&FnoneCB);
367   else if (CBClick(&FsubCB,x,y))   CBTrack(&FsubCB);
368   else if (CBClick(&FupCB,x,y))    CBTrack(&FupCB);
369   else if (CBClick(&FavgCB,x,y))   CBTrack(&FavgCB);
370   else if (CBClick(&FPaethCB,x,y)) CBTrack(&FPaethCB);
371   else if (CBClick(&interCB,x,y))  CBTrack(&interCB);
372 }
373 
374 
375 /*******************************************/
doCmd(cmd)376 static void doCmd(cmd)
377      int cmd;
378 {
379   switch (cmd) {
380     case P_BOK:
381       {
382         char *fullname;
383 
384         writePNG();
385         PNGDialog(0);
386 
387         fullname = GetDirFullName();
388         if (!ISPIPE(fullname[0])) {
389           XVCreatedFile(fullname);
390           StickInCtrlList(0);
391         }
392       }
393       break;
394 
395     case P_BCANC:
396       PNGDialog(0);
397       break;
398 
399     default:
400       break;
401   }
402 }
403 
404 
405 /*******************************************/
writePNG()406 static void writePNG()
407 {
408   FILE       *fp;
409   int         w, h, nc, rv, ptype, pfree;
410   byte       *inpix, *rmap, *gmap, *bmap;
411 
412   fp = OpenOutFile(filename);
413   if (!fp) return;
414 
415   fbasename = BaseName(filename);
416 
417   WaitCursor();
418   inpix = GenSavePic(&ptype, &w, &h, &pfree, &nc, &rmap, &gmap, &bmap);
419 
420   rv = WritePNG(fp, inpix, ptype, w, h, rmap, gmap, bmap, nc);
421 
422   SetCursors(-1);
423 
424   if (CloseOutFile(fp, filename, rv) == 0) DirBox(0);
425 
426   if (pfree) free(inpix);
427 }
428 
429 
430 /*******************************************/
WritePNG(fp,pic,ptype,w,h,rmap,gmap,bmap,numcols)431 int WritePNG(fp, pic, ptype, w, h, rmap, gmap, bmap, numcols)
432      FILE *fp;
433      byte *pic;
434      int   ptype, w, h;
435      byte *rmap, *gmap, *bmap;
436      int   numcols;
437      /* FIXME?  what's diff between picComments and WriteGIF's comment arg? */
438 {
439   png_struct *png_ptr;
440   png_info   *info_ptr;
441   png_color   palette[256];
442   png_textp   text;
443   byte        r1[256], g1[256], b1[256];  /* storage for deduped palette */
444   byte        pc2nc[256];  /* for duplicated-color remapping (1st level) */
445   byte        remap[256];  /* for bw/grayscale remapping (2nd level) */
446   int         i, j, numuniqcols=0, filter, linesize, pass;
447   byte       *p, *png_line;
448   char        software[256];
449   char       *savecmnt;
450   /* for storing values until all are accumulated, so that the image header can be set in full */
451   int         _bit_depth,_color_type,_interlace_type,_compression_type,_filter_type;
452   png_uint_32 _width,_height;
453   png_time    _mod_time;
454 
455   if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
456        png_xv_error, png_xv_warning)) == NULL) {
457     sprintf(software, "png_create_write_struct() failure in WritePNG");
458     FatalError(software);
459   }
460 
461   if ((info_ptr = png_create_info_struct(png_ptr)) == NULL)
462   {
463     png_destroy_write_struct(&png_ptr, &info_ptr);
464     sprintf(software, "png_create_info_struct() failure in WritePNG");
465     FatalError(software);
466   }
467 
468   if (setjmp(png_jmpbuf(png_ptr))) {
469     png_destroy_write_struct(&png_ptr, &info_ptr);
470     return -1;
471   }
472 
473 #ifdef PNG_NO_STDIO
474   png_set_write_fn(png_ptr, fp, png_default_write_data, NULL);
475   png_set_error_fn(png_ptr, NULL, png_xv_error, png_xv_warning);
476 #else
477   png_init_io(png_ptr, fp);
478 #endif
479 
480   png_set_compression_level(png_ptr, (int)cDial.val);
481 
482   /* Don't bother filtering if we aren't compressing the image */
483   if (FdefCB.val)
484   {
485     if ((int)cDial.val == 0)
486       png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
487   }
488   else
489   {
490     filter  = FnoneCB.val  ? PNG_FILTER_NONE  : 0;
491     filter |= FsubCB.val   ? PNG_FILTER_SUB   : 0;
492     filter |= FupCB.val    ? PNG_FILTER_UP    : 0;
493     filter |= FavgCB.val   ? PNG_FILTER_AVG   : 0;
494     filter |= FPaethCB.val ? PNG_FILTER_PAETH : 0;
495 
496     png_set_filter(png_ptr, 0, filter);
497   }
498 
499   _width = w;
500   _height = h;
501   if (w <= 0 || h <= 0) {
502     SetISTR(ISTR_WARNING, "%s:  image dimensions out of range (%dx%d)",
503       fbasename, w, h);
504     png_destroy_write_struct(&png_ptr, &info_ptr);
505     return -1;
506   }
507 
508   _interlace_type = interCB.val ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
509 
510   linesize = 0;   /* quiet a compiler warning */
511 
512 
513   /* GRR 20070331:  remap palette to eliminate duplicated colors (as in
514    *   xvgifwr.c) */
515   if (ptype == PIC8) {
516     for (i=0; i<256; ++i) {
517       pc2nc[i] = r1[i] = g1[i] = b1[i] = 0;
518     }
519 
520     /* compute number of unique colors */
521     numuniqcols = 0;
522 
523     for (i=0; i<numcols; ++i) {
524       /* see if color #i is already used */
525       for (j=0; j<i; ++j) {
526         if (rmap[i] == rmap[j]  &&  gmap[i] == gmap[j]  &&  bmap[i] == bmap[j])
527           break;
528       }
529 
530       if (j==i) {  /* wasn't found */
531         pc2nc[i] = numuniqcols;
532         r1[numuniqcols] = rmap[i];  /* i.e., r1[pc2nc[i]] == rmap[i] */
533         g1[numuniqcols] = gmap[i];
534         b1[numuniqcols] = bmap[i];
535         ++numuniqcols;
536       }
537       else pc2nc[i] = pc2nc[j];
538     }
539   }
540 
541 
542   /* Appendix G.2 of user manual claims colorType will never be F_REDUCED... */
543   if (colorType == F_FULLCOLOR || colorType == F_REDUCED) {
544     if (ptype == PIC24) {
545       linesize = 3*w;
546       if (linesize/3 < w) {
547         SetISTR(ISTR_WARNING, "%s:  image dimensions too large (%dx%d)",
548           fbasename, w, h);
549         png_destroy_write_struct(&png_ptr, &info_ptr);
550         return -1;
551       }
552       _color_type = PNG_COLOR_TYPE_RGB;
553       _bit_depth = 8;
554     } else /* ptype == PIC8 */ {
555       linesize = w;
556       _color_type = PNG_COLOR_TYPE_PALETTE;
557       if (numuniqcols <= 2)
558         _bit_depth = 1;
559       else
560       if (numuniqcols <= 4)
561         _bit_depth = 2;
562       else
563       if (numuniqcols <= 16)
564         _bit_depth = 4;
565       else
566         _bit_depth = 8;
567 
568       for (i = 0; i < numuniqcols; i++) {
569         palette[i].red   = r1[i];
570         palette[i].green = g1[i];
571         palette[i].blue  = b1[i];
572       }
573 /* cannot find a setter for this, unsure if it is necessary anymore...
574       info_ptr->valid |= PNG_INFO_PLTE;
575 */
576       /* set the header just in case it's needed */
577       png_set_IHDR(png_ptr,info_ptr,_width,_height,_bit_depth,_color_type,
578         _interlace_type,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
579       png_set_PLTE(png_ptr,info_ptr,palette,numuniqcols);
580     }
581   }
582 
583   else if (colorType == F_GREYSCALE || colorType == F_BWDITHER) {
584     _color_type = PNG_COLOR_TYPE_GRAY;
585     if (colorType == F_BWDITHER) {
586       /* shouldn't happen */
587       if (ptype == PIC24) FatalError("PIC24 and B/W Stipple in WritePNG()");
588 
589       _bit_depth = 1;
590       if (MONO(r1[0], g1[0], b1[0]) > MONO(r1[1], g1[1], b1[1])) {
591         remap[0] = 1;
592         remap[1] = 0;
593       }
594       else {
595         remap[0] = 0;
596         remap[1] = 1;
597       }
598       linesize = w;
599     }
600     else /* F_GREYSCALE */ {
601       if (ptype == PIC24) {
602         linesize = 3*w;
603         if (linesize/3 < w) {
604           SetISTR(ISTR_WARNING, "%s:  image dimensions too large (%dx%d)",
605             fbasename, w, h);
606           png_destroy_write_struct(&png_ptr, &info_ptr);
607           return -1;
608         }
609         _bit_depth = 8;
610       }
611       else /* ptype == PIC8 */ {
612         int low_precision;
613 
614         linesize = w;
615 
616         /* NOTE:  currently remap[] is the _secondary_ remapping of "palette"
617          *   colors; its values are the final color/grayscale values, and,
618          *   like r1/g1/b1[], it is _indexed_ by pc2nc[] (which is why its
619          *   values come from r1/g1/b1[] and not from rmap/gmap/bmap[]).
620          *
621          * FIXME (probably):  MONO() will create new duplicates; ideally should
622          *   do extra round of dup-detection (and preferably collapse all
623          *   remapping levels into single LUT).  This stuff is a tad confusing.
624          */
625         for (i = 0; i < numuniqcols; i++)
626           remap[i] = MONO(r1[i], g1[i], b1[i]);
627 
628         for (; i < 256; i++)
629           remap[i]=0;  /* shouldn't be necessary, but... */
630 
631         _bit_depth = 8;
632 
633         /* Note that this fails most of the time because of gamma */
634            /* (and that would be a bug:  GRR FIXME) */
635         /* try to adjust to 4-bit precision grayscale */
636 
637         low_precision=1;
638 
639         for (i = 0; i < numuniqcols; i++) {
640           if ((remap[i] & 0x0f) * 0x11 != remap[i]) {
641             low_precision = 0;
642             break;
643           }
644         }
645 
646         if (low_precision) {
647           for (i = 0; i < numuniqcols; i++) {
648             remap[i] &= 0xf;
649           }
650           _bit_depth = 4;
651 
652           /* try to adjust to 2-bit precision grayscale */
653 
654           for (i = 0; i < numuniqcols; i++) {
655             if ((remap[i] & 0x03) * 0x05 != remap[i]) {
656               low_precision = 0;
657               break;
658             }
659           }
660         }
661 
662         if (low_precision) {
663           for (i = 0; i < numuniqcols; i++) {
664             remap[i] &= 3;
665           }
666           _bit_depth = 2;
667 
668           /* try to adjust to 1-bit precision grayscale */
669 
670           for (i = 0; i < numuniqcols; i++) {
671             if ((remap[i] & 0x01) * 0x03 != remap[i]) {
672               low_precision = 0;
673               break;
674             }
675           }
676         }
677 
678         if (low_precision) {
679           for (i = 0; i < numuniqcols; i++) {
680             remap[i] &= 1;
681           }
682           _bit_depth = 1;
683         }
684       }
685     }
686   }
687 
688   else
689     png_error(png_ptr, "Unknown colorstyle in WritePNG");
690 
691   png_set_IHDR(png_ptr,info_ptr,_width,_height,_bit_depth,_color_type,
692     _interlace_type,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
693 
694   if ((text = (png_textp)malloc(sizeof(png_text)))) {
695     sprintf(software, "XV %s", REVDATE);
696 
697     text->compression = -1;
698     text->key = "Software";
699     text->text = software;
700     text->text_length = strlen(text->text);
701 
702 /* max_text seems to be internal only now, do not set
703     info_ptr->max_text = 1; */
704     png_set_text(png_ptr,info_ptr,text,1);
705   }
706 
707   Display_Gamma = gDial.val;  /* Save the current gamma for loading */
708 
709 // GRR FIXME:  add .Xdefaults option to omit writing gamma (size, cumulative errors when editing)--alternatively, modify save box to include "omit" checkbox
710   png_set_gAMA(png_ptr,info_ptr,1.0/gDial.val);
711 /* doesn't seem to be a way to set valid directly anymore, unnecessary maybe..
712   info_ptr->valid |= PNG_INFO_gAMA; */
713 
714 /* might need to be png_write_info_before_PLTE() ... */
715   png_write_info(png_ptr, info_ptr);
716 
717   if (_bit_depth < 8)
718     png_set_packing(png_ptr);
719 
720   pass=png_set_interlace_handling(png_ptr);
721 
722   if ((png_line = malloc(linesize)) == NULL)
723     png_error(png_ptr, "cannot allocate temp image line");
724     /* FIXME:  should be FatalError() */
725 
726   for (i = 0; i < pass; ++i) {
727     int j;
728     p = pic;
729     for (j = 0; j < h; ++j) {
730       if (_color_type == PNG_COLOR_TYPE_GRAY) {
731         int k;
732         for (k = 0; k < w; ++k)
733           png_line[k] = ptype==PIC24 ? MONO(p[k*3], p[k*3+1], p[k*3+2]) :
734                                        remap[pc2nc[p[k]]];
735         png_write_row(png_ptr, png_line);
736       } else if (_color_type == PNG_COLOR_TYPE_PALETTE) {
737         int k;
738         for (k = 0; k < w; ++k)
739           png_line[k] = pc2nc[p[k]];
740         png_write_row(png_ptr, png_line);
741       } else {  /* PNG_COLOR_TYPE_RGB */
742         png_write_row(png_ptr, p);
743       }
744       if ((j & 0x1f) == 0) WaitCursor();
745       p += linesize;
746     }
747   }
748 
749   free(png_line);
750 
751   savecmnt = NULL;   /* quiet a compiler warning */
752 
753   if (text) {
754     if (picComments && strlen(picComments) &&
755         (savecmnt = (char *)malloc((strlen(picComments) + 1)*sizeof(char)))) {
756       png_textp tp;
757       char *comment, *key;
758       int nt;
759       int mt;
760 
761       strcpy(savecmnt, picComments);
762       key = savecmnt;
763       png_get_text(png_ptr,info_ptr,&tp,&mt); /* to get 'max_text' */
764       tp = text;
765       nt = 0;
766 
767       comment = strchr(key, ':');
768 
769       do  {
770         /* Allocate a larger structure for comments if necessary */
771         if (nt >= mt)
772         {
773           if ((tp =
774               realloc(text, (nt + 2)*sizeof(png_text))) == NULL)
775           {
776             break;
777           }
778           else
779           {
780             text = tp;
781             tp = &text[nt];
782             mt += 2;
783           }
784         }
785 
786         /* See if it looks like a PNG keyword from LoadPNG */
787         /* GRR: should test for strictly < 80, right? (key = 1-79 chars only) */
788         if (comment && comment[1] == ':' && comment - key <= 80) {
789           *(comment++) = '\0';
790           *(comment++) = '\0';
791 
792           /* If the comment is the 'Software' chunk XV writes, we remove it,
793              since we have already stored one */
794           if (strcmp(key, "Software") == 0 && strncmp(comment, "XV", 2) == 0) {
795             key = strchr(comment, '\n');
796             if (key)
797               key++; /* skip \n */
798             comment = strchr(key, ':');
799           }
800           /* We have another keyword and/or comment to write out */
801           else {
802             tp->key = key;
803             tp->text = comment;
804 
805             /* We have to find the end of this comment, and the next keyword
806                if there is one */
807             for (; NULL != (key = comment = strchr(comment, ':')); comment++)
808               if (key[1] == ':')
809                 break;
810 
811             /* It looks like another keyword, go backward to the beginning */
812             if (key) {
813               while (key > tp->text && *key != '\n')
814                 key--;
815 
816               if (key > tp->text && comment - key <= 80) {
817                 *key = '\0';
818                 key++;
819               }
820             }
821 
822             tp->text_length = strlen(tp->text);
823 
824             /* We don't have another keyword, so remove the last newline */
825             if (!key && tp->text[tp->text_length - 1] == '\n')
826             {
827               tp->text[tp->text_length] = '\0';
828               tp->text_length--;
829             }
830 
831             tp->compression = tp->text_length > 640 ? 0 : -1;
832             nt++;
833             tp++;
834           }
835         }
836         /* Just a generic comment:  make sure line-endings are valid for PNG */
837         else {
838           char *p=key, *q=key;     /* only deleting chars, not adding any */
839 
840           while (*p) {
841             if (*p == CR) {        /* lone CR or CR/LF:  EOL either way */
842               *q++ = LF;           /* LF is the only allowed PNG line-ending */
843               if (p[1] == LF)      /* get rid of any original LF */
844                 ++p;
845             } else if (*p == LF)   /* lone LF */
846               *q++ = LF;
847             else
848               *q++ = *p;
849             ++p;
850           }
851           *q = '\0';               /* unnecessary...but what the heck */
852           tp->key = "Comment";
853           tp->text = key;
854           tp->text_length = q - key;
855           tp->compression = tp->text_length > 750 ? 0 : -1;
856           nt++;
857           key = NULL;
858         }
859       } while (key && *key);
860       png_set_text(png_ptr,info_ptr,text,nt);
861     }
862     else {
863       png_set_text(png_ptr,info_ptr,text,0);
864     }
865   }
866 
867   png_convert_from_time_t(&_mod_time, time(NULL));
868   png_set_tIME(png_ptr,info_ptr,&_mod_time);
869 /* dunno how to set validity
870   info_ptr->valid |= PNG_INFO_tIME; */
871 
872   png_write_end(png_ptr, info_ptr);
873   fflush(fp);   /* just in case we core-dump before finishing... */
874 
875   if (text) {
876     free(text);
877     /* must do this or png_destroy_write_struct() 0.97+ will free text again:
878     info_ptr->text = (png_textp)NULL; */
879     if (savecmnt)
880     {
881       free(savecmnt);
882       savecmnt = (char *)NULL;
883     }
884   }
885 
886   png_destroy_write_struct(&png_ptr, &info_ptr);
887 
888   return 0;
889 }
890 
891 
892 /*******************************************/
LoadPNG(fname,pinfo)893 int LoadPNG(fname, pinfo)
894      char    *fname;
895      PICINFO *pinfo;
896 /*******************************************/
897 {
898   /* returns '1' on success */
899 
900   FILE  *fp;
901   png_struct *png_ptr;
902   png_info *info_ptr;
903   png_color_16 my_background;
904   int i,j;
905   int linesize, bufsize;
906   int filesize;
907   int pass;
908   int gray_to_rgb;
909   size_t commentsize;
910   /* temp storage vars for libpng15 migration */
911   int         _bit_depth,_color_type,_interlace_type,_compression_type,_filter_type,_num_text,_num_palette;
912   png_uint_32 _width,_height;
913   png_timep   _mod_time;
914   double      _gamma;
915   png_textp   _text;
916   png_colorp  _palette;
917   png_color_16p _background;
918 
919   fbasename = BaseName(fname);
920 
921   pinfo->pic     = (byte *) NULL;
922   pinfo->comment = (char *) NULL;
923 
924   read_anything=0;
925 
926   /* open the file */
927   fp = xv_fopen(fname,"r");
928   if (!fp) {
929     SetISTR(ISTR_WARNING,"%s:  can't open file", fname);
930     return 0;
931   }
932 
933   /* find the size of the file */
934   fseek(fp, 0L, 2);
935   filesize = ftell(fp);
936   fseek(fp, 0L, 0);
937 
938   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
939                                    png_xv_error, png_xv_warning);
940   if (!png_ptr) {
941     fclose(fp);
942     FatalError("malloc failure in LoadPNG");
943   }
944 
945   info_ptr = png_create_info_struct(png_ptr);
946 
947   if (!info_ptr) {
948     fclose(fp);
949     png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
950     FatalError("malloc failure in LoadPNG");
951   }
952 
953   if (setjmp(png_jmpbuf(png_ptr))) {
954     fclose(fp);
955     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
956     if (!read_anything) {
957       if (pinfo->pic) {
958         free(pinfo->pic);
959         pinfo->pic = NULL;
960       }
961       if (pinfo->comment) {
962         free(pinfo->comment);
963         pinfo->comment = NULL;
964       }
965     }
966     return read_anything;
967   }
968 
969 #ifdef PNG_NO_STDIO
970   png_set_read_fn(png_ptr, fp, png_default_read_data);
971   png_set_error_fn(png_ptr, NULL, png_xv_error, png_xv_warning);
972 #else
973   png_init_io(png_ptr, fp);
974 #endif
975   png_read_info(png_ptr, info_ptr);
976 
977   png_get_IHDR(png_ptr,info_ptr,&_width,&_height,&_bit_depth,&_color_type,&_interlace_type,NULL,NULL);
978 
979   pinfo->w = pinfo->normw = _width;
980   pinfo->h = pinfo->normh = _height;
981   if (pinfo->w <= 0 || pinfo->h <= 0) {
982     SetISTR(ISTR_WARNING, "%s:  image dimensions out of range (%dx%d)",
983       fbasename, pinfo->w, pinfo->h);
984     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
985     return read_anything;
986   }
987 
988   pinfo->frmType = F_PNG;
989 
990   sprintf(pinfo->fullInfo, "PNG, %d bit ",
991           _bit_depth * png_get_channels(png_ptr,info_ptr));
992 
993   switch(_color_type) {
994     case PNG_COLOR_TYPE_PALETTE:
995       strcat(pinfo->fullInfo, "palette color");
996       break;
997 
998     case PNG_COLOR_TYPE_GRAY:
999       strcat(pinfo->fullInfo, "grayscale");
1000       break;
1001 
1002     case PNG_COLOR_TYPE_GRAY_ALPHA:
1003       strcat(pinfo->fullInfo, "grayscale+alpha");
1004       break;
1005 
1006     case PNG_COLOR_TYPE_RGB:
1007       strcat(pinfo->fullInfo, "truecolor");
1008       break;
1009 
1010     case PNG_COLOR_TYPE_RGB_ALPHA:
1011       strcat(pinfo->fullInfo, "truecolor+alpha");
1012       break;
1013   }
1014 
1015   sprintf(pinfo->fullInfo + strlen(pinfo->fullInfo),
1016 	  ", %sinterlaced. (%d bytes)",
1017 	  _interlace_type ? "" : "non-", filesize);
1018 
1019   sprintf(pinfo->shrtInfo, "%lux%lu PNG", _width, _height);
1020 
1021   if (_bit_depth < 8)
1022       png_set_packing(png_ptr);
1023 
1024   if (png_get_valid(png_ptr,info_ptr,PNG_INFO_gAMA)) {
1025     png_get_gAMA(png_ptr,info_ptr,&_gamma);
1026     png_set_gamma(png_ptr, Display_Gamma, _gamma);
1027   }
1028 /*
1029  *else
1030  *  png_set_gamma(png_ptr, Display_Gamma, 0.45);
1031  */
1032 
1033   gray_to_rgb = 0;   /* quiet a compiler warning */
1034 
1035   if (have_imagebg) {
1036     if (_bit_depth == 16) {
1037       my_background.red   = imagebgR;
1038       my_background.green = imagebgG;
1039       my_background.blue  = imagebgB;
1040       my_background.gray = imagebgG;   /* used only if all three equal... */
1041     } else {
1042       my_background.red   = (imagebgR >> 8);
1043       my_background.green = (imagebgG >> 8);
1044       my_background.blue  = (imagebgB >> 8);
1045       my_background.gray = my_background.green;
1046     }
1047     png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN,
1048                        0, Display_Gamma);
1049     if ((_color_type == PNG_COLOR_TYPE_GRAY_ALPHA ||
1050          (_color_type == PNG_COLOR_TYPE_GRAY && HAVE_tRNS)) &&
1051         (imagebgR != imagebgG || imagebgR != imagebgB))  /* i.e., colored bg */
1052     {
1053       png_set_gray_to_rgb(png_ptr);
1054       png_set_expand(png_ptr);
1055       gray_to_rgb = 1;
1056     }
1057   } else {
1058     if (png_get_valid(png_ptr,info_ptr,PNG_INFO_bKGD)) {
1059       png_get_bKGD(png_ptr,info_ptr,&_background);
1060       png_set_background(png_ptr, _background,
1061                          PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
1062     } else {
1063       my_background.red = my_background.green = my_background.blue =
1064         my_background.gray = 0;
1065       png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN,
1066                          0, Display_Gamma);
1067     }
1068   }
1069 
1070   if (_bit_depth == 16)
1071     png_set_strip_16(png_ptr);
1072 
1073   if (_color_type == PNG_COLOR_TYPE_GRAY ||
1074       _color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
1075   {
1076     if (_bit_depth == 1)
1077       pinfo->colType = F_BWDITHER;
1078     else
1079       pinfo->colType = F_GREYSCALE;
1080     png_set_expand(png_ptr);
1081   }
1082 
1083   pass=png_set_interlace_handling(png_ptr);
1084 
1085   png_read_update_info(png_ptr, info_ptr);
1086   /* get HIDR again just in case the info_ptr changed */
1087   png_get_IHDR(png_ptr,info_ptr,&_width,&_height,&_bit_depth,&_color_type,&_interlace_type,NULL,NULL);
1088 
1089   if (_color_type == PNG_COLOR_TYPE_RGB ||
1090      _color_type == PNG_COLOR_TYPE_RGB_ALPHA || gray_to_rgb)
1091   {
1092     linesize = 3 * pinfo->w;
1093     if (linesize/3 < pinfo->w) {   /* know pinfo->w > 0 (see above) */
1094       SetISTR(ISTR_WARNING, "%s:  image dimensions too large (%dx%d)",
1095         fbasename, pinfo->w, pinfo->h);
1096       png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
1097       return read_anything;
1098     }
1099     pinfo->colType = F_FULLCOLOR;
1100     pinfo->type = PIC24;
1101   } else {
1102     linesize = pinfo->w;
1103     pinfo->type = PIC8;
1104     if (_color_type == PNG_COLOR_TYPE_GRAY ||
1105        _color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
1106       for (i = 0; i < 256; i++)
1107         pinfo->r[i] = pinfo->g[i] = pinfo->b[i] = i;
1108     } else {
1109       pinfo->colType = F_FULLCOLOR;
1110       png_get_PLTE(png_ptr,info_ptr,&_palette,&_num_palette);
1111       for (i = 0; i < _num_palette; i++) {
1112         pinfo->r[i] = _palette[i].red;
1113         pinfo->g[i] = _palette[i].green;
1114         pinfo->b[i] = _palette[i].blue;
1115       }
1116     }
1117   }
1118 
1119   bufsize = linesize * pinfo->h;
1120   if (bufsize/linesize < pinfo->h) {  /* know linesize, pinfo->h > 0 (above) */
1121     SetISTR(ISTR_WARNING, "%s:  image dimensions too large (%dx%d)",
1122       fbasename, pinfo->w, pinfo->h);
1123     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
1124     return read_anything;
1125   }
1126   pinfo->pic = calloc((size_t)bufsize, (size_t)1);
1127 
1128   if (!pinfo->pic) {
1129     png_error(png_ptr, "can't allocate space for PNG image");
1130   }
1131 
1132   /*png_start_read_image(png_ptr); -- causes a warning and seems to be unnecessary */
1133 
1134   for (i = 0; i < pass; i++) {
1135     byte *p = pinfo->pic;
1136     for (j = 0; j < pinfo->h; j++) {
1137       png_read_row(png_ptr, p, NULL);
1138       read_anything = 1;
1139       if ((j & 0x1f) == 0) WaitCursor();
1140       p += linesize;
1141     }
1142   }
1143 
1144   png_read_end(png_ptr, info_ptr);
1145 
1146   png_get_text(png_ptr,info_ptr,&_text,&_num_text);
1147   if (_num_text > 0) {
1148     commentsize = 1;
1149 
1150     for (i = 0; i < _num_text; i++)
1151       commentsize += strlen(_text[i].key) + 2 +
1152                      _text[i].text_length + _text[i].itxt_length + 1;
1153 
1154     if ((pinfo->comment = malloc(commentsize)) == NULL) {
1155       png_warning(png_ptr,"can't allocate comment string");
1156     }
1157     else {
1158       pinfo->comment[0] = '\0';
1159       for (i = 0; i < _num_text; i++) {
1160         strcat(pinfo->comment, _text[i].key);
1161         strcat(pinfo->comment, "::");
1162         strcat(pinfo->comment, _text[i].text);
1163         strcat(pinfo->comment, "\n");
1164       }
1165     }
1166   }
1167 
1168   png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
1169 
1170   fclose(fp);
1171 
1172   return 1;
1173 }
1174 
1175 
1176 /*******************************************/
1177 static void
png_xv_error(png_ptr,message)1178 png_xv_error(png_ptr, message)
1179      png_structp png_ptr;
1180      png_const_charp message;
1181 {
1182   SetISTR(ISTR_WARNING,"%s:  libpng error: %s", fbasename, message);
1183 
1184   longjmp(png_jmpbuf(png_ptr), 1);
1185 }
1186 
1187 
1188 /*******************************************/
1189 static void
png_xv_warning(png_ptr,message)1190 png_xv_warning(png_ptr, message)
1191      png_structp png_ptr;
1192      png_const_charp message;
1193 {
1194   if (!png_ptr)
1195     return;
1196 
1197   SetISTR(ISTR_WARNING,"%s:  libpng warning: %s", fbasename, message);
1198 }
1199 
1200 
1201 /*******************************************/
1202 void
VersionInfoPNG()1203 VersionInfoPNG()	/* GRR 19980605 */
1204 {
1205   fprintf(stderr, "   Compiled with libpng %s; using libpng %s.\n",
1206     PNG_LIBPNG_VER_STRING, png_libpng_ver);
1207   fprintf(stderr, "   Compiled with zlib %s; using zlib %s.\n",
1208     ZLIB_VERSION, zlib_version);
1209 }
1210 
1211 #endif /* HAVE_PNG */
1212