/****************************************************************************** * $Id$ * * Project: MapServer * Purpose: Low level PNG/JPEG/GIF image io native functions * Author: Thomas Bonfort (tbonfort) * ****************************************************************************** * Copyright (c) 2009 Thomas Bonfort * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of this Software or works derived from this Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "mapserver.h" #include #include #include #include #include #ifdef USE_GIF #include #endif typedef struct _streamInfo { FILE *fp; bufferObj *buffer; } streamInfo; static void png_write_data_to_stream(png_structp png_ptr, png_bytep data, png_size_t length) { FILE *fp = ((streamInfo*)png_get_io_ptr(png_ptr))->fp; msIO_fwrite(data,length,1,fp); } static void png_write_data_to_buffer(png_structp png_ptr, png_bytep data, png_size_t length) { bufferObj *buffer = ((streamInfo*)png_get_io_ptr(png_ptr))->buffer; msBufferAppend(buffer,data,length); } static void png_flush_data(png_structp png_ptr) { /* do nothing */ } typedef struct { struct jpeg_destination_mgr pub; unsigned char *data; } ms_destination_mgr; typedef struct { ms_destination_mgr mgr; FILE *stream; } ms_stream_destination_mgr; typedef struct { ms_destination_mgr mgr; bufferObj *buffer; } ms_buffer_destination_mgr; #define OUTPUT_BUF_SIZE 4096 static void jpeg_init_destination (j_compress_ptr cinfo) { ms_destination_mgr *dest = (ms_destination_mgr*) cinfo->dest; /* Allocate the output buffer --- it will be released when done with image */ dest->data = (unsigned char *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof (unsigned char)); dest->pub.next_output_byte = dest->data; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; } static void jpeg_stream_term_destination (j_compress_ptr cinfo) { ms_stream_destination_mgr *dest = (ms_stream_destination_mgr*) cinfo->dest; msIO_fwrite(dest->mgr.data, OUTPUT_BUF_SIZE-dest->mgr.pub.free_in_buffer, 1, dest->stream); dest->mgr.pub.next_output_byte = dest->mgr.data; dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE; } static void jpeg_buffer_term_destination (j_compress_ptr cinfo) { ms_buffer_destination_mgr *dest = (ms_buffer_destination_mgr*) cinfo->dest; msBufferAppend(dest->buffer, dest->mgr.data, OUTPUT_BUF_SIZE-dest->mgr.pub.free_in_buffer); dest->mgr.pub.next_output_byte = dest->mgr.data; dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE; } static boolean jpeg_stream_empty_output_buffer (j_compress_ptr cinfo) { ms_stream_destination_mgr *dest = (ms_stream_destination_mgr*) cinfo->dest; msIO_fwrite(dest->mgr.data, OUTPUT_BUF_SIZE, 1, dest->stream); dest->mgr.pub.next_output_byte = dest->mgr.data; dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE; return TRUE; } static boolean jpeg_buffer_empty_output_buffer (j_compress_ptr cinfo) { ms_buffer_destination_mgr *dest = (ms_buffer_destination_mgr*) cinfo->dest; msBufferAppend(dest->buffer, dest->mgr.data, OUTPUT_BUF_SIZE); dest->mgr.pub.next_output_byte = dest->mgr.data; dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE; return TRUE; } static void msJPEGErrorExit(j_common_ptr cinfo) { jmp_buf* pJmpBuffer = (jmp_buf* ) cinfo->client_data; char buffer[JMSG_LENGTH_MAX]; /* Create the message */ (*cinfo->err->format_message) (cinfo, buffer); msSetError(MS_MISCERR,"libjpeg: %s","jpeg_ErrorExit()", buffer); /* Return control to the setjmp point */ longjmp(*pJmpBuffer, 1); } int saveAsJPEG(mapObj *map, rasterBufferObj *rb, streamInfo *info, outputFormatObj *format) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; int quality; const char* pszOptimized; int optimized; int arithmetic; ms_destination_mgr *dest; JSAMPLE *rowdata = NULL; unsigned int row; jmp_buf setjmp_buffer; quality = atoi(msGetOutputFormatOption( format, "QUALITY", "75")); pszOptimized = msGetOutputFormatOption( format, "OPTIMIZED", "YES"); optimized = EQUAL(pszOptimized, "YES") || EQUAL(pszOptimized, "ON") || EQUAL(pszOptimized, "TRUE"); arithmetic = EQUAL(pszOptimized, "ARITHMETIC"); if (setjmp(setjmp_buffer)) { jpeg_destroy_compress(&cinfo); free(rowdata); return MS_FAILURE; } cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = msJPEGErrorExit; cinfo.client_data = (void *) &(setjmp_buffer); jpeg_create_compress(&cinfo); if (cinfo.dest == NULL) { if(info->fp) { cinfo.dest = (struct jpeg_destination_mgr *) (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof (ms_stream_destination_mgr)); ((ms_stream_destination_mgr*)cinfo.dest)->mgr.pub.empty_output_buffer = jpeg_stream_empty_output_buffer; ((ms_stream_destination_mgr*)cinfo.dest)->mgr.pub.term_destination = jpeg_stream_term_destination; ((ms_stream_destination_mgr*)cinfo.dest)->stream = info->fp; } else { cinfo.dest = (struct jpeg_destination_mgr *) (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof (ms_buffer_destination_mgr)); ((ms_buffer_destination_mgr*)cinfo.dest)->mgr.pub.empty_output_buffer = jpeg_buffer_empty_output_buffer; ((ms_buffer_destination_mgr*)cinfo.dest)->mgr.pub.term_destination = jpeg_buffer_term_destination; ((ms_buffer_destination_mgr*)cinfo.dest)->buffer = info->buffer; } } dest = (ms_destination_mgr*) cinfo.dest; dest->pub.init_destination = jpeg_init_destination; cinfo.image_width = rb->width; cinfo.image_height = rb->height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); if( arithmetic ) cinfo.arith_code = TRUE; else if( optimized ) cinfo.optimize_coding = TRUE; if( arithmetic || optimized ) { /* libjpeg turbo 1.5.2 honours max_memory_to_use, but has no backing */ /* store implementation, so better not set max_memory_to_use ourselves. */ /* See https://github.com/libjpeg-turbo/libjpeg-turbo/issues/162 */ if( cinfo.mem->max_memory_to_use > 0 ) { if (map == NULL || msGetConfigOption(map, "JPEGMEM") == NULL) { /* If the user doesn't provide a value for JPEGMEM, we want to be sure */ /* that at least the image size will be used before creating the temporary file */ cinfo.mem->max_memory_to_use = MS_MAX(cinfo.mem->max_memory_to_use, cinfo.input_components * rb->width * rb->height); } } } jpeg_start_compress(&cinfo, TRUE); rowdata = (JSAMPLE*)malloc(rb->width*cinfo.input_components*sizeof(JSAMPLE)); for(row=0; rowheight; row++) { JSAMPLE *pixptr = rowdata; int col; unsigned char *r,*g,*b; r=rb->data.rgba.r+row*rb->data.rgba.row_step; g=rb->data.rgba.g+row*rb->data.rgba.row_step; b=rb->data.rgba.b+row*rb->data.rgba.row_step; for(col=0; colwidth; col++) { *(pixptr++) = *r; *(pixptr++) = *g; *(pixptr++) = *b; r+=rb->data.rgba.pixel_step; g+=rb->data.rgba.pixel_step; b+=rb->data.rgba.pixel_step; } (void) jpeg_write_scanlines(&cinfo, &rowdata, 1); } /* Step 6: Finish compression */ jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); free(rowdata); return MS_SUCCESS; } /* * sort a given list of rgba entries so that all the opaque pixels are at the end */ int remapPaletteForPNG(rasterBufferObj *rb, rgbPixel *rgb, unsigned char *a, int *num_a) { int bot_idx, top_idx, x; int remap[256]; unsigned int maxval = rb->data.palette.scaling_maxval; assert(rb->type == MS_BUFFER_BYTE_PALETTE); /* ** remap the palette colors so that all entries with ** the maximal alpha value (i.e., fully opaque) are at the end and can ** therefore be omitted from the tRNS chunk. Note that the ordering of ** opaque entries is reversed from how they were previously arranged ** --not that this should matter to anyone. */ for (top_idx = rb->data.palette.num_entries-1, bot_idx = x = 0; x < rb->data.palette.num_entries; ++x) { if (rb->data.palette.palette[x].a == maxval) remap[x] = top_idx--; else remap[x] = bot_idx++; } /* sanity check: top and bottom indices should have just crossed paths */ if (bot_idx != top_idx + 1) { msSetError(MS_MISCERR,"quantization sanity check failed","createPNGPalette()"); return MS_FAILURE; } *num_a = bot_idx; for(x=0; xwidth*rb->height; x++) rb->data.palette.pixels[x] = remap[rb->data.palette.pixels[x]]; for (x = 0; x < rb->data.palette.num_entries; ++x) { if(maxval == 255) { a[remap[x]] = rb->data.palette.palette[x].a; rgb[remap[x]].r = rb->data.palette.palette[x].r; rgb[remap[x]].g = rb->data.palette.palette[x].g; rgb[remap[x]].b = rb->data.palette.palette[x].b; } else { /* rescale palette */ rgb[remap[x]].r = (rb->data.palette.palette[x].r * 255 + (maxval >> 1)) / maxval; rgb[remap[x]].g = (rb->data.palette.palette[x].g * 255 + (maxval >> 1)) / maxval; rgb[remap[x]].b = (rb->data.palette.palette[x].b * 255 + (maxval >> 1)) / maxval; a[remap[x]] = (rb->data.palette.palette[x].a * 255 + (maxval >> 1)) / maxval; } if(a[remap[x]] != 255) { /* un-premultiply pixels */ double da = 255.0/a[remap[x]]; rgb[remap[x]].r *= da; rgb[remap[x]].g *= da; rgb[remap[x]].b *= da; } } return MS_SUCCESS; } int savePalettePNG(rasterBufferObj *rb, streamInfo *info, int compression) { png_infop info_ptr; rgbPixel rgb[256]; unsigned char a[256]; int num_a; int row,sample_depth; png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL,NULL,NULL); assert(rb->type == MS_BUFFER_BYTE_PALETTE); if (!png_ptr) return (MS_FAILURE); png_set_compression_level(png_ptr, compression); png_set_filter (png_ptr,0, PNG_FILTER_NONE); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return (MS_FAILURE); } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return (MS_FAILURE); } if(info->fp) png_set_write_fn(png_ptr,info, png_write_data_to_stream, png_flush_data); else png_set_write_fn(png_ptr,info, png_write_data_to_buffer, png_flush_data); if (rb->data.palette.num_entries <= 2) sample_depth = 1; else if (rb->data.palette.num_entries <= 4) sample_depth = 2; else if (rb->data.palette.num_entries <= 16) sample_depth = 4; else sample_depth = 8; png_set_IHDR(png_ptr, info_ptr, rb->width, rb->height, sample_depth, PNG_COLOR_TYPE_PALETTE, 0, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); remapPaletteForPNG(rb,rgb,a,&num_a); png_set_PLTE(png_ptr, info_ptr, (png_colorp)(rgb),rb->data.palette.num_entries); if(num_a) png_set_tRNS(png_ptr, info_ptr, a,num_a, NULL); png_write_info(png_ptr, info_ptr); png_set_packing(png_ptr); for(row=0; rowheight; row++) { unsigned char *rowptr = &(rb->data.palette.pixels[row*rb->width]); png_write_row(png_ptr, rowptr); } png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); return MS_SUCCESS; } int readPalette(const char *palette, rgbaPixel *entries, unsigned int *nEntries, int useAlpha) { FILE *stream = NULL; char buffer[MS_BUFFER_LENGTH]; *nEntries = 0; stream = fopen(palette, "r"); if(!stream) { msSetError(MS_IOERR, "Error opening palette file %s.", "readPalette()", palette); return MS_FAILURE; } while(fgets(buffer, MS_BUFFER_LENGTH, stream) && *nEntries<256) { int r,g,b,a = 0; /* while there are colors to load */ if(buffer[0] == '#' || buffer[0] == '\n' || buffer[0] == '\r') continue; /* skip comments and blank lines */ if(!useAlpha) { if(3 != sscanf(buffer,"%d,%d,%d\n",&r,&g,&b)) { fclose(stream); msSetError(MS_MISCERR,"failed to parse color %d r,g,b triplet in line \"%s\" from file %s","readPalette()",*nEntries+1,buffer,palette); return MS_FAILURE; } } else { if(4 != sscanf(buffer,"%d,%d,%d,%d\n",&r,&g,&b,&a)) { fclose(stream); msSetError(MS_MISCERR,"failed to parse color %d r,g,b,a quadruplet in line \"%s\" from file %s","readPalette()",*nEntries+1,buffer,palette); return MS_FAILURE; } } if(useAlpha && a != 255) { double da = a/255.0; entries[*nEntries].r = r * da; entries[*nEntries].g = g * da; entries[*nEntries].b = b * da; entries[*nEntries].a = a; } else { entries[*nEntries].r = r; entries[*nEntries].g = g; entries[*nEntries].b = b; entries[*nEntries].a = 255; } (*nEntries)++; } fclose(stream); return MS_SUCCESS; } int saveAsPNG(mapObj *map,rasterBufferObj *rb, streamInfo *info, outputFormatObj *format) { int force_pc256 = MS_FALSE; int force_palette = MS_FALSE; int ret = MS_FAILURE; const char *force_string,*zlib_compression; int compression = -1; zlib_compression = msGetOutputFormatOption( format, "COMPRESSION", NULL); if(zlib_compression && *zlib_compression) { char *endptr; compression = strtol(zlib_compression,&endptr,10); if(*endptr || compression<-1 || compression>9) { msSetError(MS_MISCERR,"failed to parse FORMATOPTION \"COMPRESSION=%s\", expecting integer from 0 to 9.","saveAsPNG()",zlib_compression); return MS_FAILURE; } } force_string = msGetOutputFormatOption( format, "QUANTIZE_FORCE", NULL ); if( force_string && (strcasecmp(force_string,"on") == 0 || strcasecmp(force_string,"yes") == 0 || strcasecmp(force_string,"true") == 0) ) force_pc256 = MS_TRUE; force_string = msGetOutputFormatOption( format, "PALETTE_FORCE", NULL ); if( force_string && (strcasecmp(force_string,"on") == 0 || strcasecmp(force_string,"yes") == 0 || strcasecmp(force_string,"true") == 0) ) force_palette = MS_TRUE; if(force_pc256 || force_palette) { rasterBufferObj qrb; rgbaPixel palette[256], paletteGiven[256]; unsigned int numPaletteGivenEntries; memset(&qrb,0,sizeof(rasterBufferObj)); qrb.type = MS_BUFFER_BYTE_PALETTE; qrb.width = rb->width; qrb.height = rb->height; qrb.data.palette.pixels = (unsigned char*)malloc(qrb.width*qrb.height*sizeof(unsigned char)); qrb.data.palette.scaling_maxval = 255; if(force_pc256) { qrb.data.palette.palette = palette; qrb.data.palette.num_entries = atoi(msGetOutputFormatOption( format, "QUANTIZE_COLORS", "256")); ret = msQuantizeRasterBuffer(rb,&(qrb.data.palette.num_entries),qrb.data.palette.palette, NULL, 0, &qrb.data.palette.scaling_maxval); } else { int colorsWanted = atoi(msGetOutputFormatOption( format, "QUANTIZE_COLORS", "0")); const char *palettePath = msGetOutputFormatOption( format, "PALETTE", "palette.txt"); char szPath[MS_MAXPATHLEN]; if(map) { msBuildPath(szPath, map->mappath, palettePath); palettePath = szPath; } if(readPalette(palettePath,paletteGiven,&numPaletteGivenEntries,format->transparent) != MS_SUCCESS) { return MS_FAILURE; } if(numPaletteGivenEntries == 256 || colorsWanted == 0) { qrb.data.palette.palette = paletteGiven; qrb.data.palette.num_entries = numPaletteGivenEntries; ret = MS_SUCCESS; /* we have a full palette and don't want an additional quantization step */ } else { /* quantize the image, and mix our colours in the resulting palette */ qrb.data.palette.palette = palette; qrb.data.palette.num_entries = MS_MAX(colorsWanted,numPaletteGivenEntries); ret = msQuantizeRasterBuffer(rb,&(qrb.data.palette.num_entries),qrb.data.palette.palette, paletteGiven,numPaletteGivenEntries, &qrb.data.palette.scaling_maxval); } } if(ret != MS_FAILURE) { ret = msClassifyRasterBuffer(rb,&qrb); ret = savePalettePNG(&qrb,info,compression); } msFree(qrb.data.palette.pixels); return ret; } else if(rb->type == MS_BUFFER_BYTE_RGBA) { png_infop info_ptr; int color_type; int row; unsigned int *rowdata; png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL,NULL,NULL); if (!png_ptr) return (MS_FAILURE); png_set_compression_level(png_ptr, compression); png_set_filter (png_ptr,0, PNG_FILTER_NONE); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return (MS_FAILURE); } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return (MS_FAILURE); } if(info->fp) png_set_write_fn(png_ptr,info, png_write_data_to_stream, png_flush_data); else png_set_write_fn(png_ptr,info, png_write_data_to_buffer, png_flush_data); if(rb->data.rgba.a) color_type = PNG_COLOR_TYPE_RGB_ALPHA; else color_type = PNG_COLOR_TYPE_RGB; png_set_IHDR(png_ptr, info_ptr, rb->width, rb->height, 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png_ptr, info_ptr); if(!rb->data.rgba.a && rb->data.rgba.pixel_step==4) png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); rowdata = (unsigned int*)malloc(rb->width*sizeof(unsigned int)); for(row=0; rowheight; row++) { unsigned int *pixptr = rowdata; int col; unsigned char *a,*r,*g,*b; r=rb->data.rgba.r+row*rb->data.rgba.row_step; g=rb->data.rgba.g+row*rb->data.rgba.row_step; b=rb->data.rgba.b+row*rb->data.rgba.row_step; if(rb->data.rgba.a) { a=rb->data.rgba.a+row*rb->data.rgba.row_step; for(col=0; colwidth; col++) { if(*a) { double da = *a/255.0; unsigned char *pix = (unsigned char*)pixptr; pix[0] = *r/da; pix[1] = *g/da; pix[2] = *b/da; pix[3] = *a; } else { *pixptr=0; } pixptr++; a+=rb->data.rgba.pixel_step; r+=rb->data.rgba.pixel_step; g+=rb->data.rgba.pixel_step; b+=rb->data.rgba.pixel_step; } } else { for(col=0; colwidth; col++) { unsigned char *pix = (unsigned char*)pixptr; pix[0] = *r; pix[1] = *g; pix[2] = *b; pixptr++; r+=rb->data.rgba.pixel_step; g+=rb->data.rgba.pixel_step; b+=rb->data.rgba.pixel_step; } } png_write_row(png_ptr,(png_bytep)rowdata); } png_write_end(png_ptr, info_ptr); free(rowdata); png_destroy_write_struct(&png_ptr, &info_ptr); return MS_SUCCESS; } else { msSetError(MS_MISCERR,"Unknown buffer type","saveAsPNG()"); return MS_FAILURE; } } /* For platforms with incomplete ANSI defines. Fortunately, SEEK_SET is defined to be zero by the standard. */ #ifndef SEEK_SET #define SEEK_SET 0 #endif /* SEEK_SET */ int readPNG(char *path, rasterBufferObj *rb) { png_uint_32 width,height; unsigned char *a,*r,*g,*b; int bit_depth,color_type,i; unsigned char **row_pointers; png_structp png_ptr = NULL; png_infop info_ptr = NULL; FILE *stream = fopen(path,"rb"); if(!stream) return MS_FAILURE; /* could pass pointers to user-defined error handlers instead of NULLs: */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose(stream); return MS_FAILURE; /* out of memory */ } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); fclose(stream); return MS_FAILURE; /* out of memory */ } if(setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr,&info_ptr,NULL); fclose(stream); return MS_FAILURE; } png_init_io(png_ptr,stream); png_read_info(png_ptr,info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL,NULL,NULL); rb->width = width; rb->height = height; rb->type = MS_BUFFER_BYTE_RGBA; rb->data.rgba.pixels = (unsigned char*)malloc(width*height*4*sizeof(unsigned char)); row_pointers = (unsigned char**)malloc(height*sizeof(unsigned char*)); rb->data.rgba.pixel_step=4; rb->data.rgba.row_step = width*4; b = rb->data.rgba.b = &rb->data.rgba.pixels[0]; g = rb->data.rgba.g = &rb->data.rgba.pixels[1]; r = rb->data.rgba.r = &rb->data.rgba.pixels[2]; a = rb->data.rgba.a = &rb->data.rgba.pixels[3]; for(i=0; idata.rgba.pixels[i*rb->data.rgba.row_step]); } if (color_type == PNG_COLOR_TYPE_PALETTE) /* expand palette images to RGB */ png_set_expand(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) /* expand low bit-depth grayscale to 8bits */ png_set_expand(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) /* expand transparency chunks to full alpha */ png_set_expand(png_ptr); if (bit_depth == 16) /* scale 16bits down to 8 */ png_set_strip_16(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) /* convert grayscale to rgba */ png_set_gray_to_rgb(png_ptr); png_set_bgr(png_ptr); if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); png_read_update_info(png_ptr, info_ptr); assert(png_get_rowbytes(png_ptr, info_ptr) == rb->data.rgba.row_step); png_read_image(png_ptr, row_pointers); free(row_pointers); row_pointers=NULL; png_read_end(png_ptr,NULL); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); /*premultiply data*/ for(i=0; i> 8; *g = (*g * *a + 255) >> 8; *b = (*b * *a + 255) >> 8; } } a+=4; b+=4; g+=4; r+=4; } fclose(stream); return MS_SUCCESS; } int msSaveRasterBuffer(mapObj *map, rasterBufferObj *rb, FILE *stream, outputFormatObj *format) { if(strcasestr(format->driver,"/png")) { streamInfo info; info.fp = stream; info.buffer = NULL; return saveAsPNG(map, rb,&info,format); } else if(strcasestr(format->driver,"/jpeg")) { streamInfo info; info.fp = stream; info.buffer=NULL; return saveAsJPEG(map, rb,&info,format); } else { msSetError(MS_MISCERR,"unsupported image format\n", "msSaveRasterBuffer()"); return MS_FAILURE; } } int msSaveRasterBufferToBuffer(rasterBufferObj *data, bufferObj *buffer, outputFormatObj *format) { if(strcasestr(format->driver,"/png")) { streamInfo info; info.fp = NULL; info.buffer = buffer; return saveAsPNG(NULL, data,&info,format); } else if(strcasestr(format->driver,"/jpeg")) { streamInfo info; info.fp = NULL; info.buffer=buffer; return saveAsJPEG(NULL, data,&info,format); } else { msSetError(MS_MISCERR,"unsupported image format\n", "msSaveRasterBuffer()"); return MS_FAILURE; } } #ifdef USE_GIF #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 static char const *gif_error_msg(int code) #else static char const *gif_error_msg() #endif { static char msg[80]; #if (!defined GIFLIB_MAJOR) || (GIFLIB_MAJOR < 5) int code = GifLastError(); #endif switch (code) { case E_GIF_ERR_OPEN_FAILED: /* should not see this */ return "Failed to open given file"; case E_GIF_ERR_WRITE_FAILED: return "Write failed"; case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */ return "Screen descriptor already passed to giflib"; case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */ return "Image descriptor already passed to giflib"; case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */ return "Neither global nor local color map set"; case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */ return "Too much pixel data passed to giflib"; case E_GIF_ERR_NOT_ENOUGH_MEM: return "Out of memory"; case E_GIF_ERR_DISK_IS_FULL: return "Disk is full"; case E_GIF_ERR_CLOSE_FAILED: /* should not see this */ return "File close failed"; case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */ return "File not writable"; case D_GIF_ERR_OPEN_FAILED: return "Failed to open file"; case D_GIF_ERR_READ_FAILED: return "Failed to read from file"; case D_GIF_ERR_NOT_GIF_FILE: return "File is not a GIF file"; case D_GIF_ERR_NO_SCRN_DSCR: return "No screen descriptor detected - invalid file"; case D_GIF_ERR_NO_IMAG_DSCR: return "No image descriptor detected - invalid file"; case D_GIF_ERR_NO_COLOR_MAP: return "No global or local color map found"; case D_GIF_ERR_WRONG_RECORD: return "Wrong record type detected - invalid file?"; case D_GIF_ERR_DATA_TOO_BIG: return "Data in file too big for image"; case D_GIF_ERR_NOT_ENOUGH_MEM: return "Out of memory"; case D_GIF_ERR_CLOSE_FAILED: return "Close failed"; case D_GIF_ERR_NOT_READABLE: return "File not opened for read"; case D_GIF_ERR_IMAGE_DEFECT: return "Defective image"; case D_GIF_ERR_EOF_TOO_SOON: return "Unexpected EOF - invalid file"; default: sprintf(msg, "Unknown giflib error code %d", code); return msg; } } /* not fully implemented and tested */ /* missing: set the first pointer to a,r,g,b */ int readGIF(char *path, rasterBufferObj *rb) { int i, j, codeSize, extCode, firstImageRead = MS_FALSE; unsigned char *r,*g,*b,*a; int transIdx = -1; GifFileType *image; GifPixelType *line; GifRecordType recordType; GifByteType *codeBlock, *extension; ColorMapObject *cmap; int interlacedOffsets[] = {0,4,2,1}; int interlacedJumps[] = {8,8,4,2}; #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 int errcode; #endif rb->type = MS_BUFFER_BYTE_RGBA; #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 image = DGifOpenFileName(path, &errcode); if (image == NULL) { msSetError(MS_MISCERR,"failed to load gif image: %s","readGIF()", gif_error_msg(errcode)); return MS_FAILURE; } #else image = DGifOpenFileName(path); if (image == NULL) { msSetError(MS_MISCERR,"failed to load gif image: %s","readGIF()", gif_error_msg()); return MS_FAILURE; } #endif rb->width = image->SWidth; rb->height = image->SHeight; rb->data.rgba.row_step = rb->width * 4; rb->data.rgba.pixel_step = 4; rb->data.rgba.pixels = (unsigned char*)malloc(rb->width*rb->height*4*sizeof(unsigned char)); b = rb->data.rgba.b = &rb->data.rgba.pixels[0]; g = rb->data.rgba.g = &rb->data.rgba.pixels[1]; r = rb->data.rgba.r = &rb->data.rgba.pixels[2]; a = rb->data.rgba.a = &rb->data.rgba.pixels[3]; cmap = (image->Image.ColorMap)?image->Image.ColorMap:image->SColorMap; for(i=0; iwidth*rb->height; i++) { *a = 255; *r = cmap->Colors[image->SBackGroundColor].Red; *g = cmap->Colors[image->SBackGroundColor].Green; *b = cmap->Colors[image->SBackGroundColor].Blue; a+=rb->data.rgba.pixel_step; r+=rb->data.rgba.pixel_step; g+=rb->data.rgba.pixel_step; b+=rb->data.rgba.pixel_step; } do { if (DGifGetRecordType(image, &recordType) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } switch (recordType) { case SCREEN_DESC_RECORD_TYPE: DGifGetScreenDesc(image); break; case IMAGE_DESC_RECORD_TYPE: if (DGifGetImageDesc(image) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } if (!firstImageRead) { int row = image->Image.Top; int col = image->Image.Left; int width = image->Image.Width; int height = image->Image.Height; /* sanity check: */ if(col+width>rb->width || row+height>rb->height) { msSetError(MS_MISCERR,"corrupted gif image, img size exceeds screen size","readGIF()"); return MS_FAILURE; } line = (GifPixelType *) malloc(width * sizeof(GifPixelType)); if(image->Image.Interlace) { int count; for(count=0; count<4; count++) { for(i=row+interlacedOffsets[count]; idata.rgba.row_step + col * rb->data.rgba.pixel_step; a = rb->data.rgba.a + offset; r = rb->data.rgba.r + offset; g = rb->data.rgba.g + offset; b = rb->data.rgba.b + offset; if (DGifGetLine(image, line, width) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg()); #endif return MS_FAILURE; } for(j=0; jColors[pix].Red; *g = cmap->Colors[pix].Green; *b = cmap->Colors[pix].Blue; } else { *a = *r = *g = *b = 0; } a+=rb->data.rgba.pixel_step; r+=rb->data.rgba.pixel_step; g+=rb->data.rgba.pixel_step; b+=rb->data.rgba.pixel_step; } } } } else { for (i = 0; i < height; i++) { int offset = i * rb->data.rgba.row_step + col * rb->data.rgba.pixel_step; a = rb->data.rgba.a + offset; r = rb->data.rgba.r + offset; g = rb->data.rgba.g + offset; b = rb->data.rgba.b + offset; if (DGifGetLine(image, line, width) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg()); #endif return MS_FAILURE; } for(j=0; jColors[pix].Red; *g = cmap->Colors[pix].Green; *b = cmap->Colors[pix].Blue; } else { *a = *r = *g = *b = 0; } a+=rb->data.rgba.pixel_step; r+=rb->data.rgba.pixel_step; g+=rb->data.rgba.pixel_step; b+=rb->data.rgba.pixel_step; } } } free((char *) line); firstImageRead = MS_TRUE; } else { /* Skip all next images */ if (DGifGetCode(image, &codeSize, &codeBlock) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } while (codeBlock != NULL) { if (DGifGetCodeNext(image, &codeBlock) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } } } break; case EXTENSION_RECORD_TYPE: /* skip all extension blocks */ if (DGifGetExtension(image, &extCode, &extension) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } if(extCode == 249 && (extension[1] & 1)) transIdx = extension[4]; for (;;) { if (DGifGetExtensionNext(image, &extension) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } if (extension == NULL) break; if(extension[1] & 1) transIdx = extension[4]; } break; case UNDEFINED_RECORD_TYPE: break; case TERMINATE_RECORD_TYPE: break; default: break; } } while (recordType != TERMINATE_RECORD_TYPE); #if defined GIFLIB_MAJOR && GIFLIB_MINOR && ((GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) || (GIFLIB_MAJOR > 5)) if (DGifCloseFile(image, &errcode) == GIF_ERROR) { msSetError(MS_MISCERR,"failed to close gif after loading: %s","readGIF()", gif_error_msg(errcode)); return MS_FAILURE; } #else if (DGifCloseFile(image) == GIF_ERROR) { #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 msSetError(MS_MISCERR,"failed to close gif after loading: %s","readGIF()", gif_error_msg(image->Error)); #else msSetError(MS_MISCERR,"failed to close gif after loading: %s","readGIF()", gif_error_msg()); #endif return MS_FAILURE; } #endif return MS_SUCCESS; } #endif int msLoadMSRasterBufferFromFile(char *path, rasterBufferObj *rb) { FILE *stream; unsigned char signature[8]; int ret = MS_FAILURE; stream = fopen(path,"rb"); if(!stream) { msSetError(MS_MISCERR, "unable to open file %s for reading", "msLoadMSRasterBufferFromFile()", path); return MS_FAILURE; } if(1 != fread(signature,8,1,stream)) { fclose(stream); msSetError(MS_MISCERR, "Unable to read header from image file %s", "msLoadMSRasterBufferFromFile()",path); return MS_FAILURE; } fclose(stream); if(png_sig_cmp(signature,0,8) == 0) { ret = readPNG(path,rb); } else if (!strncmp((char*)signature,"GIF",3)) { #ifdef USE_GIF ret = readGIF(path,rb); #else msSetError(MS_MISCERR,"pixmap %s seems to be GIF formatted, but this server is compiled without GIF support","readImage()",path); return MS_FAILURE; #endif } else { msSetError(MS_MISCERR,"unsupported pixmap format","readImage()"); return MS_FAILURE; } return ret; }