/***** * readPNG.c : XmHTML png image loading routines * * This file Version $Revision: 1.9 $ * * Creation date: Wed Feb 19 03:21:11 GMT+0100 1997 * Last modification: $Date: 1999/07/29 01:26:29 $ * By: $Author: sopwith $ * Current State: $State: Exp $ * * Author: newt * * Portions Copyright (C) 1994 by John Bradley. Used by permission. * Copyright (C) 1994-1997 by Ripley Software Development * All Rights Reserved * * This file is part of the XmHTML Widget Library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****/ /***** * ChangeLog * $Log: readPNG.c,v $ * Revision 1.9 1999/07/29 01:26:29 sopwith * * * Fix all warnings. * * Revision 1.8 1998/09/25 01:09:12 unammx * 1998-09-24 Miguel de Icaza * * * readPNG.c (_XmHTMLReadPNG): I am the one to blame. Fix PNG file * loading. I do not know how I ever commited this. * * Revision 1.7 1998/06/29 06:32:14 tromey * added gnome-config to .cvsignore * * Revision 1.6 1998/04/29 23:23:13 unammx * 1998-04-29 Miguel de Icaza * * * readPNG.c (_XmHTMLReadPNG): Small fix to accomodate the newer * PNG library requirement. * * 1998-04-23 Federico Mena Quintero * * Revision 1.5 1998/02/12 03:09:47 unammx * Merge to Koen's XmHTML 1.1.2 + following fixes: * * Wed Feb 11 20:27:19 1998 Miguel de Icaza * * * gtk-forms.c (freeForm): gtk_destroy_widget is no longer needed * with the refcounting changes. * * * gtk-xmhtml.c (gtk_xmhtml_remove): Only god knows why I was * adding the just removed widget. * * Revision 1.4 1998/01/07 01:45:42 unammx * Gtk/XmHTML is ready to be used by the Gnome hackers now! * Weeeeeee! * * This afternoon: * * - Changes to integrate gtk-xmhtml into an autoconf setup. * * - Changes to make gtk-xmhtml a library to be used by Gnome * (simply include 8bit image conversion. * Streamlined the ppm_quant code and added QuickRGB. * Fixed bad proto for _XmHTMLReReadPNG when PNG support has been disabled. * * Revision 1.6 1997/08/01 13:12:08 newt * Total rewrite: XmHTML now has *FULL* PNG support. * * Revision 1.5 1997/05/28 01:55:45 newt * Image depth support. * * Revision 1.4 1997/04/29 14:31:13 newt * Header files modifications. * * Revision 1.3 1997/03/20 08:15:18 newt * Integrated png image support. * * Revision 1.2 1997/03/11 19:58:58 newt * ImageBuffer changes * * Revision 1.1 1997/03/02 23:02:56 newt * Initial Revision * *****/ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_LIBPNG #include #include #include /* required for full alpha channel processing */ #endif #include "XmHTMLP.h" #include "XmHTMLfuncs.h" #ifdef HAVE_LIBPNG /*** External Function Prototype Declarations ***/ /*** Public Variable Declarations ***/ /*** Private Datatype Declarations ****/ /*** Private Function Prototype Declarations ****/ /* various macros used by the quantization routines */ #define STORE_COLOR(R,G,B,P) do{ \ img_data->cmap[P].red = (R); img_data->cmap[P].green = (G); \ img_data->cmap[P].blue = (B); img_data->cmap[P].pixel = P; \ }while(0) /*** Private Variable Declarations ***/ /* background gamma correction used in alpha channel processing */ #define BG_GAMMA_CORRECTION 2.2222222222 /* the maximum value a color component can have */ #define MAX_RGB_VAL 255 /***** * Name: my_png_error * Return Type: void * Description: png error function * In: * png_ptr: current png stream; * msg: message to be displayed; * Returns: * nothing. * Note: * This routine displays a warning message and terminates PNG reading * by jumping to the point where the error function was set. *****/ static void my_png_error(png_structp png_ptr, String msg) { ImageBuffer *ib = (ImageBuffer*)png_get_io_ptr(png_ptr); _XmHTMLWarning(__WFUNC__(NULL, "png_error"), "libpng error on image %s: %s", ib->file, msg); longjmp(png_ptr->jmpbuf, 1); } /***** * Name: my_png_read * Return Type: void * Description: function called by png when it needs another chunk of data * In: * png_ptr: current png stream; * data: return buffer; * len: no of bytes to be copied. * Returns: * nothing but len bytes are copied into the return buffer. * Note: * This function is used instead of the default png reader (which uses fread) * as we have the file data in memory. *****/ static void my_png_read(png_structp png_ptr, png_bytep data, png_size_t len) { ImageBuffer *ib = (ImageBuffer*)png_get_io_ptr(png_ptr); int size = (int)len; if(ib->size > ib->next) { if(ib->next + size > ib->size) size = ib->size - ib->next; memcpy(data, ib->buffer + ib->next, size); ib->next += size; return; } my_png_error(png_ptr, "Read Error"); } /***** * Name: _XmHTMLReadPNG * Return Type: XmHTMLRawImageData* * Description: reads a PNG (Portable Network Graphics) image * In: * * Returns: * loaded image data upon success or NULL on failure *****/ XmHTMLRawImageData* _XmHTMLReadPNG(TWidget html, ImageBuffer *ib) { png_structp png_ptr; png_infop info_ptr; Byte *data; int i, idx, npass; int width, height, color_type; int ncolors, max_colors; float gamma, fg_gamma; Boolean has_alpha = False, has_cmap = False, do_gamma = True; png_bytep *row_ptrs; char msg[128]; static XmHTMLRawImageData *img_data; img_data = NULL; data = 0; _XmHTMLDebug(15, ("readPNG.c: _XmHTMLreadPNG Start, loading %s\n", ib->file)); /* * get configuration defaults */ if(XmIsHTML(html)) { max_colors = ((XmHTMLWidget)html)->html.max_image_colors; gamma = ((XmHTMLWidget)html)->html.screen_gamma; } else { /* external image support */ if(_xmimage_cfg != NULL && _xmimage_cfg->flags && ImageQuantize) max_colors = _xmimage_cfg->ncolors; else max_colors = MAX_IMAGE_COLORS; if(_xmimage_cfg && (_xmimage_cfg->flags & ImageScreenGamma)) gamma = _xmimage_cfg->gamma; else gamma = XmHTML_DEFAULT_GAMMA; } /* We set up the normal PNG error routines, then override with longjmp. */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); /* Create and initialize the info structure */ if((info_ptr = png_create_info_struct(png_ptr)) == NULL) { /* failed, too bad */ png_destroy_read_struct(&png_ptr, NULL, NULL); return((XmHTMLRawImageData*)NULL); } /* now set error handler */ if(setjmp(png_ptr->jmpbuf)) { /* * PNG signalled an error. Destroy image data, free any allocated * buffers and return NULL. */ _XmHTMLDebug(15, ("_XmHTMLreadPNG: end, libpng internal error.\n")); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if(img_data) FreeRawImage(img_data); if(data) free(data); return((XmHTMLRawImageData*)NULL); } /* * We have the entire image in memory, so we need to set our own * ``fread'' function for libpng to use. */ png_set_read_fn(png_ptr, ib, &my_png_read); /* by now we already know this is a png */ ib->next = 8; png_set_sig_bytes(png_ptr, 8); /* get png info */ png_read_info(png_ptr, info_ptr); /* allocate raw image data */ img_data = (XmHTMLRawImageData*)malloc(sizeof(XmHTMLRawImageData)); ResetRawImage(img_data); /* save width & height */ width = img_data->width = info_ptr->width; height = img_data->height = info_ptr->height; /* image depth */ ib->depth = info_ptr->bit_depth; /* no of colors */ ncolors = img_data->cmapsize = info_ptr->num_palette; /* type of image */ color_type = info_ptr->color_type; /* * The fun stuff. This is based on readPNG by Greg Roelofs as found * in xpaint 2.4.8, which falls under the same distribution note * as readGIF by David Koblas (which is the original author of xpaint). * It has been quite heavily modified but it was an invaluable starting * point! */ switch(color_type) { case PNG_COLOR_TYPE_PALETTE: _XmHTMLDebug(15, ("readPNG.c: PNG_COLOR_TYPE_PALETTE\n")); img_data->color_class = XmIMAGE_COLORSPACE_INDEXED; /* * paletted images never contain more than 256 colors but * check anyway. */ if(ncolors > 256) { sprintf(msg, "PNG_COLOR_TYPE_PALETTE: %i colors reported " "while max is 256.", ncolors); my_png_error(png_ptr, msg); } /* * Paletted images with transparency info are expanded to * RGB with alpha channel. * Actual image creation is postponed until the image is * needed. */ if(info_ptr->valid & PNG_INFO_tRNS) { _XmHTMLDebug(15, ("readPNG.c: tRNS chunk present\n")); png_set_expand(png_ptr); has_alpha = True; do_gamma = False; } else { /* store colormap and allocate buffer for read image data */ AllocRawImageCmap(img_data, ncolors); for(i = 0; i < ncolors; i++) { img_data->cmap[i].red = info_ptr->palette[i].red; img_data->cmap[i].green = info_ptr->palette[i].green; img_data->cmap[i].blue = info_ptr->palette[i].blue; } has_cmap = True; data = (Byte*)malloc(width*height*sizeof(Byte)); } break; case PNG_COLOR_TYPE_RGB: _XmHTMLDebug(15, ("readPNG.c: PNG_COLOR_TYPE_RGB\n")); img_data->color_class = XmIMAGE_COLORSPACE_RGB; if(ib->depth == 16) { _XmHTMLDebug(15, ("readPNG: stripping 48-bit RGB image to " "24 bits\n")); png_set_strip_16(png_ptr); ib->depth = 8; } /* image data */ data = (Byte*)malloc(width*height*sizeof(Byte)*3); break; case PNG_COLOR_TYPE_GRAY: _XmHTMLDebug(15, ("readPNG.c: PNG_COLOR_TYPE_GRAY\n")); img_data->color_class = XmIMAGE_COLORSPACE_GRAYSCALE; if(ib->depth == 16) { _XmHTMLDebug(15, ("readPNG: stripping 16-bit grayscale image " "to 8 bits\n")); png_set_strip_16(png_ptr); ib->depth = 8; } /* * grayscale with transparency is expanded to RGB with alpha * channel. */ if(info_ptr->valid & PNG_INFO_tRNS) { _XmHTMLDebug(15, ("readPNG.c: tRNS chunk present\n")); png_set_gray_to_rgb(png_ptr); png_set_expand(png_ptr); has_alpha = True; do_gamma = False; break; } /* fill in appropriate grayramp */ switch(ib->depth) { case 1: /* allocate colormap */ ncolors = 2; AllocRawImageCmap(img_data, ncolors); /* fill it */ STORE_COLOR(0, 0, 0, 0); STORE_COLOR(255, 255, 255, 1); break; case 2: /* allocate colormap */ ncolors = 4; AllocRawImageCmap(img_data, ncolors); /* fill it */ STORE_COLOR(0, 0, 0, 0); STORE_COLOR(85, 85, 85, 1); /* 255/3 */ STORE_COLOR(170, 170, 170, 2); STORE_COLOR(255, 255, 255, 3); break; case 4: /* allocate colormap */ ncolors = 16; AllocRawImageCmap(img_data, ncolors); /* fill it */ for (i = 0; i < 16; ++i) { idx = i * 17; /* 255/15 */ STORE_COLOR(idx, idx, idx, i); } break; case 8: /* allocate colormap */ ncolors = 256; AllocRawImageCmap(img_data, ncolors); /* fill it */ for (i = 0; i < 256; ++i) STORE_COLOR(i, i, i, i); break; } /* valid colormap */ has_cmap = True; /* image data */ data = (Byte*)malloc(width*height*sizeof(Byte)); break; case PNG_COLOR_TYPE_RGB_ALPHA: _XmHTMLDebug(15, ("readPNG.c: PNG_COLOR_TYPE_RGB_ALPHA\n")); img_data->color_class = XmIMAGE_COLORSPACE_RGB; do_gamma = False; has_alpha = True; /* strip 16bit down to 8bits */ if(ib->depth == 16) png_set_strip_16(png_ptr); break; case PNG_COLOR_TYPE_GRAY_ALPHA: _XmHTMLDebug(15, ("readPNG.c: PNG_COLOR_TYPE_GRAY_ALPHA\n")); img_data->color_class = XmIMAGE_COLORSPACE_GRAYSCALE; do_gamma = False; has_alpha = True; /* expand to rgb */ png_set_gray_to_rgb(png_ptr); break; default: sprintf(msg, "bad PNG image: unknown color type (%d)", info_ptr->color_type); my_png_error(png_ptr, msg); break; } /* * We only substitute background pixel if we don't have an alpha channel * Doing that for alpha channel images would change the colortype of the * current image, leading to weird results. */ if(!has_alpha && info_ptr->valid & PNG_INFO_bKGD) { png_set_background(png_ptr, &(info_ptr->background), PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); img_data->bg = info_ptr->background.index; } /* handle gamma correction */ if(info_ptr->valid & PNG_INFO_gAMA) fg_gamma = info_ptr->gamma; else fg_gamma = 0.45; /* set it */ if(do_gamma) png_set_gamma(png_ptr, gamma, fg_gamma); /* dithering gets handled by caller */ /* one byte per pixel */ if(info_ptr->bit_depth < 8) png_set_packing(png_ptr); /* no tRNS chunk handling, we've expanded it to an alpha channel. */ /* handle interlacing */ if(info_ptr->interlace_type) npass = png_set_interlace_handling(png_ptr); /* and now update everything */ png_read_update_info(png_ptr, info_ptr); /* has possibly changed if we have promoted GrayScale or tRNS chunks */ color_type = info_ptr->color_type; /* new color_type? */ if(color_type == PNG_COLOR_TYPE_RGB_ALPHA) { png_bytep png_data; /* * RGB image with Alpha channel. Actual decoding of this image is * delayed until it's required. * * This approach is needed as at this point we don't know the position * of the image in the document and hence we can't correctly handle * proper alpha channel processing. * * Setting the delayed_creation flag instructs XmHTML to create an * empty XmHTMLImage structure and delays the actual image composition * until the position of this image is known. At that point the painter * will call doAlphaChannel to do the actual image creation. */ row_ptrs = (png_bytep*)malloc(height*sizeof(png_bytep)); png_data = (png_bytep)malloc(height*info_ptr->rowbytes); for(i = 0; i < height; i++) row_ptrs[i] = (png_bytep)png_data + i*info_ptr->rowbytes; /* read it */ png_read_image(png_ptr, row_ptrs); img_data->data = png_data; /* raw image data */ /* no longer needed */ free(row_ptrs); /* set flag so XmHTML will know what to do with this image */ img_data->delayed_creation = True; /* PNG cleanup */ png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); _XmHTMLDebug(15, ("_XmHTMLreadPNG: end, image creation delayed.\n")); /* store image gamma value. We need it later on */ img_data->fg_gamma = fg_gamma; img_data->type = IMAGE_PNG; return(img_data); } /* no alpha channel */ row_ptrs = (png_bytep*)malloc(height*sizeof(png_bytep)); for(i = 0; i < height; ++i) row_ptrs[i] = (png_bytep)data + i*info_ptr->rowbytes; /* read it */ png_read_image(png_ptr, row_ptrs); /* no longer needed */ free(row_ptrs); /***** * We're lucky: having a colormap means we have an indexed image. *****/ if(has_cmap) { img_data->data = data; } else { /***** * RGB image. Convert to paletted image. * First allocate a buffer which will receive the final image data. *****/ img_data->data = (Byte*)malloc(width*height*sizeof(Byte)); if(XmIsHTML(html)) _XmHTMLConvert24to8(data, img_data, ((XmHTMLWidget)html)->html.max_image_colors, ((XmHTMLWidget)html)->html.rgb_conv_mode); else _XmHTMLConvert24to8(data, img_data, MAX_IMAGE_COLORS, XmBEST); /* and we no longer need the image data itself */ free(data); } /* PNG cleanup */ png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); _XmHTMLDebug(15, ("_XmHTMLreadPNG: end, image loaded.\n")); return(img_data); } /***** * Name: _XmHTMLReReadPNG * Return Type: XmHTMLRawImageData* * Description: rereads a PNG (Portable Network Graphics) image * In: * html: current XmHTMLWidget * ib: current image buffer; * x,y: absolute document position for this image; * is_body..: set when this image *is* the body image. * Returns: * loaded image data upon success or NULL on failure * Note: * This function is *only* called for RGB images with a tRNS chunk or * alpha channel support. It's purpose is to reprocess the raw image data * so we can properly deal with the alpha channel. For overall PNG comments * see the above routine. * This routine is called by doAlphaChannel(). *****/ XmHTMLRawImageData* _XmHTMLReReadPNG(XmHTMLWidget html, XmHTMLRawImageData *raw_data, int x, int y, Boolean is_body_image) { Byte *png, *rgb, *pp; /* various ptrs */ Byte *currLine; /* temporary buffer */ int i, j, k; /* various counters */ int width, height; /* image dimensions */ float gamma, fg_gamma; /* display and image gamma */ int background[3]; /* background pixel: R, G, B */ int foreground[4]; /* image pixel: R, G, B, A */ int fbpix[3]; /* final image pixel: R, G, B */ int fb_maxsample; XmHTMLImage *bg_image = NULL; int bg_width = 0, bg_height = 0; Byte *bg_data = NULL; AlphaPtr alpha_buffer; static XmHTMLRawImageData *img_data; /* return data */ /* various alpha channel processing variables */ int ialpha; float alpha, compalpha; float gamfg, linfg, gambg, linbg, comppix, gcvideo; _XmHTMLDebug(15, ("readPNG.c: _XmHTMLRereadPNG Start.\n")); /* background alpha channel stuff */ alpha_buffer = html->html.alpha_buffer; /* display gamma */ gamma = html->html.screen_gamma; /* file gamma */ fg_gamma = raw_data->fg_gamma; fb_maxsample = alpha_buffer->fb_maxsample; /* * If we do not have a body image (or this image *is* the body image), * use the background color. Else we need to get the RGB components * of the colors used by the background image. */ if(!is_body_image && alpha_buffer->ncolors) { bg_image = html->html.body_image; bg_width = bg_image->width; bg_height = bg_image->height; bg_data = bg_image->html_image->data; _XmHTMLDebug(15, ("readPNG.c: using background image.\n")); } else { background[0] = alpha_buffer->background[0]; background[1] = alpha_buffer->background[1]; background[2] = alpha_buffer->background[2]; _XmHTMLDebug(15, ("readPNG.c: using background color.\n")); } /* get image width */ width = raw_data->width; height = raw_data->height; AllocRawImage(img_data, width, height); /* raw png image data (source buffer) */ png = raw_data->data; /* intermediate destination buffer, contains alpha-corrected values */ currLine = (Byte*)malloc(width*height*sizeof(Byte)*3); rgb = currLine; /* final destination buffer */ pp = img_data->data; /***** * Actual alpha channel processing. This code is based on the alpha * channel example in the official W3C PNG Recommendation which uses * floating point all over the place. * * Performance increases can be gained by: * - using lookup tables; * - handling body-image/body-color separate; * - integrate dithering. *****/ for(i = 0; i < height; i++) { /* do a single scanline */ for(j = 0; j < width; ++j) { /* tile position */ if(bg_data) { /* compute correct tile offset */ int dx = (j+x) % bg_width; int dy = (i+y) % bg_height; int i = dy * bg_width + dx; int idx = (int)bg_data[i]; background[0] = alpha_buffer->bg_cmap[idx].red; background[1] = alpha_buffer->bg_cmap[idx].green; background[2] = alpha_buffer->bg_cmap[idx].blue; } /* get foreground color */ foreground[0] = *png++; /* red */ foreground[1] = *png++; /* green */ foreground[2] = *png++; /* blue */ foreground[3] = *png++; /* alpha */ /* * Get integer version of alpha. * Check for opaque and transparent special cases; * no compositing needed if so. * * We show the whole gamma decode/correct process in floating * point, but it would more likely be done with lookup tables. */ ialpha = foreground[3]; if(ialpha == 0) { /* Foreground is transparent. replace with background */ fbpix[0] = background[0]; fbpix[1] = background[1]; fbpix[2] = background[2]; } else if(ialpha == MAX_RGB_VAL) { /* Copy foreground pixel to frame buffer. */ for(k = 0; k < 3; k++) { gamfg = (float) foreground[k] / MAX_RGB_VAL; linfg = pow(gamfg, 1.0/fg_gamma); comppix = linfg; gcvideo = pow(comppix, 1.2/gamma); fbpix[k] = (int) (gcvideo * fb_maxsample + 0.5); } } else { /* * Compositing is necessary. * Get floating-point alpha and its complement. * Note: alpha is always linear; gamma does not affect it. */ alpha = (float) ialpha / MAX_RGB_VAL; compalpha = 1.0 - alpha; for(k = 0; k < 3; k++) { /* * Convert foreground and background to floating * point, then linearize (undo gamma encoding). */ gamfg = (float) foreground[k] / MAX_RGB_VAL; linfg = pow(gamfg, 1.0/fg_gamma); gambg = (float) background[k] / MAX_RGB_VAL; linbg = pow(gambg, BG_GAMMA_CORRECTION); /* Composite */ comppix = linfg * alpha + linbg * compalpha; /* * Gamma correct for display. * Convert to integer frame buffer pixel. * We assume a viewing gamma of 1.2 */ gcvideo = pow(comppix, 1.2/gamma); fbpix[k] = (int) (gcvideo * fb_maxsample + 0.5); } } /* and store it */ *rgb++ = (Byte)fbpix[0]; *rgb++ = (Byte)fbpix[1]; *rgb++ = (Byte)fbpix[2]; } } /* and reduce to 8bit paletted image */ _XmHTMLConvert24to8(currLine, img_data, html->html.max_image_colors, ((XmHTMLWidget)html)->html.rgb_conv_mode); free(currLine); /* copy untouched fields */ img_data->bg = raw_data->bg; img_data->type = raw_data->type; img_data->color_class = raw_data->color_class; _XmHTMLDebug(15, ("_XmHTMLReReadPNG: end, image loaded.\n")); return(img_data); } #else /* !HAVE_LIBPNG */ /* empty func if PNG isn't supported */ /* ARGSUSED */ XmHTMLRawImageData* _XmHTMLReadPNG(TWidget html, ImageBuffer *ib) { return((XmHTMLRawImageData*)NULL); } /* ARGSUSED */ XmHTMLRawImageData* _XmHTMLReReadPNG(XmHTMLWidget html, XmHTMLRawImageData *raw_data, int x, int y, Boolean is_body_image) { return((XmHTMLRawImageData*)NULL); } #endif /* HAVE_LIBPNG */