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