1 /******************************************************************************
2 * $Id$
3 *
4 * Project: MapServer
5 * Purpose: Low level PNG/JPEG/GIF image io native functions
6 * Author: Thomas Bonfort (tbonfort)
7 *
8 ******************************************************************************
9 * Copyright (c) 2009 Thomas Bonfort
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies of this Software or works derived from this Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 ****************************************************************************/
29
30 #include "mapserver.h"
31 #include <png.h>
32 #include <setjmp.h>
33 #include <assert.h>
34 #include <jpeglib.h>
35 #include <stdlib.h>
36
37 #ifdef USE_GIF
38 #include <gif_lib.h>
39 #endif
40
41
42
43 typedef struct _streamInfo {
44 FILE *fp;
45 bufferObj *buffer;
46 } streamInfo;
47
48 static
png_write_data_to_stream(png_structp png_ptr,png_bytep data,png_size_t length)49 void png_write_data_to_stream(png_structp png_ptr, png_bytep data, png_size_t length)
50 {
51 FILE *fp = ((streamInfo*)png_get_io_ptr(png_ptr))->fp;
52 msIO_fwrite(data,length,1,fp);
53 }
54
55 static
png_write_data_to_buffer(png_structp png_ptr,png_bytep data,png_size_t length)56 void png_write_data_to_buffer(png_structp png_ptr, png_bytep data, png_size_t length)
57 {
58 bufferObj *buffer = ((streamInfo*)png_get_io_ptr(png_ptr))->buffer;
59 msBufferAppend(buffer,data,length);
60 }
61
62 static
png_flush_data(png_structp png_ptr)63 void png_flush_data(png_structp png_ptr)
64 {
65 /* do nothing */
66 }
67
68 typedef struct {
69 struct jpeg_destination_mgr pub;
70 unsigned char *data;
71 } ms_destination_mgr;
72
73 typedef struct {
74 ms_destination_mgr mgr;
75 FILE *stream;
76 } ms_stream_destination_mgr;
77
78 typedef struct {
79 ms_destination_mgr mgr;
80 bufferObj *buffer;
81 } ms_buffer_destination_mgr;
82
83 #define OUTPUT_BUF_SIZE 4096
84
85 static void
jpeg_init_destination(j_compress_ptr cinfo)86 jpeg_init_destination (j_compress_ptr cinfo)
87 {
88 ms_destination_mgr *dest = (ms_destination_mgr*) cinfo->dest;
89
90 /* Allocate the output buffer --- it will be released when done with image */
91 dest->data = (unsigned char *)
92 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
93 OUTPUT_BUF_SIZE * sizeof (unsigned char));
94
95 dest->pub.next_output_byte = dest->data;
96 dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
97 }
98
99 static
jpeg_stream_term_destination(j_compress_ptr cinfo)100 void jpeg_stream_term_destination (j_compress_ptr cinfo)
101 {
102 ms_stream_destination_mgr *dest = (ms_stream_destination_mgr*) cinfo->dest;
103 msIO_fwrite(dest->mgr.data, OUTPUT_BUF_SIZE-dest->mgr.pub.free_in_buffer, 1, dest->stream);
104 dest->mgr.pub.next_output_byte = dest->mgr.data;
105 dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE;
106 }
107
108 static
jpeg_buffer_term_destination(j_compress_ptr cinfo)109 void jpeg_buffer_term_destination (j_compress_ptr cinfo)
110 {
111 ms_buffer_destination_mgr *dest = (ms_buffer_destination_mgr*) cinfo->dest;
112 msBufferAppend(dest->buffer, dest->mgr.data, OUTPUT_BUF_SIZE-dest->mgr.pub.free_in_buffer);
113 dest->mgr.pub.next_output_byte = dest->mgr.data;
114 dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE;
115 }
116
117 static
jpeg_stream_empty_output_buffer(j_compress_ptr cinfo)118 boolean jpeg_stream_empty_output_buffer (j_compress_ptr cinfo)
119 {
120 ms_stream_destination_mgr *dest = (ms_stream_destination_mgr*) cinfo->dest;
121 msIO_fwrite(dest->mgr.data, OUTPUT_BUF_SIZE, 1, dest->stream);
122 dest->mgr.pub.next_output_byte = dest->mgr.data;
123 dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE;
124 return TRUE;
125 }
126
127 static
jpeg_buffer_empty_output_buffer(j_compress_ptr cinfo)128 boolean jpeg_buffer_empty_output_buffer (j_compress_ptr cinfo)
129 {
130 ms_buffer_destination_mgr *dest = (ms_buffer_destination_mgr*) cinfo->dest;
131 msBufferAppend(dest->buffer, dest->mgr.data, OUTPUT_BUF_SIZE);
132 dest->mgr.pub.next_output_byte = dest->mgr.data;
133 dest->mgr.pub.free_in_buffer = OUTPUT_BUF_SIZE;
134 return TRUE;
135 }
136
msJPEGErrorExit(j_common_ptr cinfo)137 static void msJPEGErrorExit(j_common_ptr cinfo)
138 {
139 jmp_buf* pJmpBuffer = (jmp_buf* ) cinfo->client_data;
140 char buffer[JMSG_LENGTH_MAX];
141
142 /* Create the message */
143 (*cinfo->err->format_message) (cinfo, buffer);
144
145 msSetError(MS_MISCERR,"libjpeg: %s","jpeg_ErrorExit()", buffer);
146
147 /* Return control to the setjmp point */
148 longjmp(*pJmpBuffer, 1);
149 }
150
saveAsJPEG(mapObj * map,rasterBufferObj * rb,streamInfo * info,outputFormatObj * format)151 int saveAsJPEG(mapObj *map, rasterBufferObj *rb, streamInfo *info,
152 outputFormatObj *format)
153 {
154 struct jpeg_compress_struct cinfo;
155 struct jpeg_error_mgr jerr;
156 int quality;
157 const char* pszOptimized;
158 int optimized;
159 int arithmetic;
160 ms_destination_mgr *dest;
161 JSAMPLE *rowdata = NULL;
162 unsigned int row;
163 jmp_buf setjmp_buffer;
164
165 quality = atoi(msGetOutputFormatOption( format, "QUALITY", "75"));
166 pszOptimized = msGetOutputFormatOption( format, "OPTIMIZED", "YES");
167 optimized = EQUAL(pszOptimized, "YES") || EQUAL(pszOptimized, "ON") ||
168 EQUAL(pszOptimized, "TRUE");
169 arithmetic = EQUAL(pszOptimized, "ARITHMETIC");
170
171 if (setjmp(setjmp_buffer))
172 {
173 jpeg_destroy_compress(&cinfo);
174 free(rowdata);
175 return MS_FAILURE;
176 }
177
178 cinfo.err = jpeg_std_error(&jerr);
179 jerr.error_exit = msJPEGErrorExit;
180 cinfo.client_data = (void *) &(setjmp_buffer);
181 jpeg_create_compress(&cinfo);
182
183 if (cinfo.dest == NULL) {
184 if(info->fp) {
185 cinfo.dest = (struct jpeg_destination_mgr *)
186 (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
187 sizeof (ms_stream_destination_mgr));
188 ((ms_stream_destination_mgr*)cinfo.dest)->mgr.pub.empty_output_buffer = jpeg_stream_empty_output_buffer;
189 ((ms_stream_destination_mgr*)cinfo.dest)->mgr.pub.term_destination = jpeg_stream_term_destination;
190 ((ms_stream_destination_mgr*)cinfo.dest)->stream = info->fp;
191 } else {
192
193 cinfo.dest = (struct jpeg_destination_mgr *)
194 (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
195 sizeof (ms_buffer_destination_mgr));
196 ((ms_buffer_destination_mgr*)cinfo.dest)->mgr.pub.empty_output_buffer = jpeg_buffer_empty_output_buffer;
197 ((ms_buffer_destination_mgr*)cinfo.dest)->mgr.pub.term_destination = jpeg_buffer_term_destination;
198 ((ms_buffer_destination_mgr*)cinfo.dest)->buffer = info->buffer;
199 }
200 }
201 dest = (ms_destination_mgr*) cinfo.dest;
202 dest->pub.init_destination = jpeg_init_destination;
203
204 cinfo.image_width = rb->width;
205 cinfo.image_height = rb->height;
206 cinfo.input_components = 3;
207 cinfo.in_color_space = JCS_RGB;
208 jpeg_set_defaults(&cinfo);
209 jpeg_set_quality(&cinfo, quality, TRUE);
210 if( arithmetic )
211 cinfo.arith_code = TRUE;
212 else if( optimized )
213 cinfo.optimize_coding = TRUE;
214
215 if( arithmetic || optimized ) {
216 /* libjpeg turbo 1.5.2 honours max_memory_to_use, but has no backing */
217 /* store implementation, so better not set max_memory_to_use ourselves. */
218 /* See https://github.com/libjpeg-turbo/libjpeg-turbo/issues/162 */
219 if( cinfo.mem->max_memory_to_use > 0 ) {
220 if (map == NULL || msGetConfigOption(map, "JPEGMEM") == NULL) {
221 /* If the user doesn't provide a value for JPEGMEM, we want to be sure */
222 /* that at least the image size will be used before creating the temporary file */
223 cinfo.mem->max_memory_to_use =
224 MS_MAX(cinfo.mem->max_memory_to_use, cinfo.input_components * rb->width * rb->height);
225 }
226 }
227 }
228
229 jpeg_start_compress(&cinfo, TRUE);
230 rowdata = (JSAMPLE*)malloc(rb->width*cinfo.input_components*sizeof(JSAMPLE));
231
232 for(row=0; row<rb->height; row++) {
233 JSAMPLE *pixptr = rowdata;
234 int col;
235 unsigned char *r,*g,*b;
236 r=rb->data.rgba.r+row*rb->data.rgba.row_step;
237 g=rb->data.rgba.g+row*rb->data.rgba.row_step;
238 b=rb->data.rgba.b+row*rb->data.rgba.row_step;
239 for(col=0; col<rb->width; col++) {
240 *(pixptr++) = *r;
241 *(pixptr++) = *g;
242 *(pixptr++) = *b;
243 r+=rb->data.rgba.pixel_step;
244 g+=rb->data.rgba.pixel_step;
245 b+=rb->data.rgba.pixel_step;
246 }
247 (void) jpeg_write_scanlines(&cinfo, &rowdata, 1);
248 }
249
250 /* Step 6: Finish compression */
251
252 jpeg_finish_compress(&cinfo);
253 jpeg_destroy_compress(&cinfo);
254 free(rowdata);
255 return MS_SUCCESS;
256 }
257
258 /*
259 * sort a given list of rgba entries so that all the opaque pixels are at the end
260 */
remapPaletteForPNG(rasterBufferObj * rb,rgbPixel * rgb,unsigned char * a,int * num_a)261 int remapPaletteForPNG(rasterBufferObj *rb, rgbPixel *rgb, unsigned char *a, int *num_a)
262 {
263 int bot_idx, top_idx, x;
264 int remap[256];
265 unsigned int maxval = rb->data.palette.scaling_maxval;
266
267 assert(rb->type == MS_BUFFER_BYTE_PALETTE);
268
269 /*
270 ** remap the palette colors so that all entries with
271 ** the maximal alpha value (i.e., fully opaque) are at the end and can
272 ** therefore be omitted from the tRNS chunk. Note that the ordering of
273 ** opaque entries is reversed from how they were previously arranged
274 ** --not that this should matter to anyone.
275 */
276
277 for (top_idx = rb->data.palette.num_entries-1, bot_idx = x = 0; x < rb->data.palette.num_entries; ++x) {
278 if (rb->data.palette.palette[x].a == maxval)
279 remap[x] = top_idx--;
280 else
281 remap[x] = bot_idx++;
282 }
283 /* sanity check: top and bottom indices should have just crossed paths */
284 if (bot_idx != top_idx + 1) {
285 msSetError(MS_MISCERR,"quantization sanity check failed","createPNGPalette()");
286 return MS_FAILURE;
287 }
288
289 *num_a = bot_idx;
290
291 for(x=0; x<rb->width*rb->height; x++)
292 rb->data.palette.pixels[x] = remap[rb->data.palette.pixels[x]];
293
294
295 for (x = 0; x < rb->data.palette.num_entries; ++x) {
296 if(maxval == 255) {
297 a[remap[x]] = rb->data.palette.palette[x].a;
298 rgb[remap[x]].r = rb->data.palette.palette[x].r;
299 rgb[remap[x]].g = rb->data.palette.palette[x].g;
300 rgb[remap[x]].b = rb->data.palette.palette[x].b;
301 } else {
302 /* rescale palette */
303 rgb[remap[x]].r = (rb->data.palette.palette[x].r * 255 + (maxval >> 1)) / maxval;
304 rgb[remap[x]].g = (rb->data.palette.palette[x].g * 255 + (maxval >> 1)) / maxval;
305 rgb[remap[x]].b = (rb->data.palette.palette[x].b * 255 + (maxval >> 1)) / maxval;
306 a[remap[x]] = (rb->data.palette.palette[x].a * 255 + (maxval >> 1)) / maxval;
307 }
308 if(a[remap[x]] != 255) {
309 /* un-premultiply pixels */
310 double da = 255.0/a[remap[x]];
311 rgb[remap[x]].r *= da;
312 rgb[remap[x]].g *= da;
313 rgb[remap[x]].b *= da;
314 }
315 }
316
317 return MS_SUCCESS;
318 }
319
savePalettePNG(rasterBufferObj * rb,streamInfo * info,int compression)320 int savePalettePNG(rasterBufferObj *rb, streamInfo *info, int compression)
321 {
322 png_infop info_ptr;
323 rgbPixel rgb[256];
324 unsigned char a[256];
325 int num_a;
326 int row,sample_depth;
327 png_structp png_ptr = png_create_write_struct(
328 PNG_LIBPNG_VER_STRING, NULL,NULL,NULL);
329
330 assert(rb->type == MS_BUFFER_BYTE_PALETTE);
331
332 if (!png_ptr)
333 return (MS_FAILURE);
334
335 png_set_compression_level(png_ptr, compression);
336 png_set_filter (png_ptr,0, PNG_FILTER_NONE);
337
338 info_ptr = png_create_info_struct(png_ptr);
339 if (!info_ptr) {
340 png_destroy_write_struct(&png_ptr,
341 (png_infopp)NULL);
342 return (MS_FAILURE);
343 }
344
345 if (setjmp(png_jmpbuf(png_ptr))) {
346 png_destroy_write_struct(&png_ptr, &info_ptr);
347 return (MS_FAILURE);
348 }
349 if(info->fp)
350 png_set_write_fn(png_ptr,info, png_write_data_to_stream, png_flush_data);
351 else
352 png_set_write_fn(png_ptr,info, png_write_data_to_buffer, png_flush_data);
353
354
355 if (rb->data.palette.num_entries <= 2)
356 sample_depth = 1;
357 else if (rb->data.palette.num_entries <= 4)
358 sample_depth = 2;
359 else if (rb->data.palette.num_entries <= 16)
360 sample_depth = 4;
361 else
362 sample_depth = 8;
363
364 png_set_IHDR(png_ptr, info_ptr, rb->width, rb->height,
365 sample_depth, PNG_COLOR_TYPE_PALETTE,
366 0, PNG_COMPRESSION_TYPE_DEFAULT,
367 PNG_FILTER_TYPE_DEFAULT);
368
369 remapPaletteForPNG(rb,rgb,a,&num_a);
370
371 png_set_PLTE(png_ptr, info_ptr, (png_colorp)(rgb),rb->data.palette.num_entries);
372 if(num_a)
373 png_set_tRNS(png_ptr, info_ptr, a,num_a, NULL);
374
375 png_write_info(png_ptr, info_ptr);
376 png_set_packing(png_ptr);
377
378 for(row=0; row<rb->height; row++) {
379 unsigned char *rowptr = &(rb->data.palette.pixels[row*rb->width]);
380 png_write_row(png_ptr, rowptr);
381 }
382 png_write_end(png_ptr, info_ptr);
383 png_destroy_write_struct(&png_ptr, &info_ptr);
384 return MS_SUCCESS;
385 }
386
readPalette(const char * palette,rgbaPixel * entries,unsigned int * nEntries,int useAlpha)387 int readPalette(const char *palette, rgbaPixel *entries, unsigned int *nEntries, int useAlpha)
388 {
389 FILE *stream = NULL;
390 char buffer[MS_BUFFER_LENGTH];
391 *nEntries = 0;
392
393 stream = fopen(palette, "r");
394 if(!stream) {
395 msSetError(MS_IOERR, "Error opening palette file %s.", "readPalette()", palette);
396 return MS_FAILURE;
397 }
398
399 while(fgets(buffer, MS_BUFFER_LENGTH, stream) && *nEntries<256) {
400 int r,g,b,a = 0;
401 /* while there are colors to load */
402 if(buffer[0] == '#' || buffer[0] == '\n' || buffer[0] == '\r')
403 continue; /* skip comments and blank lines */
404 if(!useAlpha) {
405 if(3 != sscanf(buffer,"%d,%d,%d\n",&r,&g,&b)) {
406 fclose(stream);
407 msSetError(MS_MISCERR,"failed to parse color %d r,g,b triplet in line \"%s\" from file %s","readPalette()",*nEntries+1,buffer,palette);
408 return MS_FAILURE;
409 }
410 } else {
411 if(4 != sscanf(buffer,"%d,%d,%d,%d\n",&r,&g,&b,&a)) {
412 fclose(stream);
413 msSetError(MS_MISCERR,"failed to parse color %d r,g,b,a quadruplet in line \"%s\" from file %s","readPalette()",*nEntries+1,buffer,palette);
414 return MS_FAILURE;
415 }
416 }
417 if(useAlpha && a != 255) {
418 double da = a/255.0;
419 entries[*nEntries].r = r * da;
420 entries[*nEntries].g = g * da;
421 entries[*nEntries].b = b * da;
422 entries[*nEntries].a = a;
423 } else {
424 entries[*nEntries].r = r;
425 entries[*nEntries].g = g;
426 entries[*nEntries].b = b;
427 entries[*nEntries].a = 255;
428 }
429 (*nEntries)++;
430 }
431 fclose(stream);
432 return MS_SUCCESS;
433 }
434
saveAsPNG(mapObj * map,rasterBufferObj * rb,streamInfo * info,outputFormatObj * format)435 int saveAsPNG(mapObj *map,rasterBufferObj *rb, streamInfo *info, outputFormatObj *format)
436 {
437 int force_pc256 = MS_FALSE;
438 int force_palette = MS_FALSE;
439
440 int ret = MS_FAILURE;
441
442 const char *force_string,*zlib_compression;
443 int compression = -1;
444
445 zlib_compression = msGetOutputFormatOption( format, "COMPRESSION", NULL);
446 if(zlib_compression && *zlib_compression) {
447 char *endptr;
448 compression = strtol(zlib_compression,&endptr,10);
449 if(*endptr || compression<-1 || compression>9) {
450 msSetError(MS_MISCERR,"failed to parse FORMATOPTION \"COMPRESSION=%s\", expecting integer from 0 to 9.","saveAsPNG()",zlib_compression);
451 return MS_FAILURE;
452 }
453 }
454
455
456 force_string = msGetOutputFormatOption( format, "QUANTIZE_FORCE", NULL );
457 if( force_string && (strcasecmp(force_string,"on") == 0 || strcasecmp(force_string,"yes") == 0 || strcasecmp(force_string,"true") == 0) )
458 force_pc256 = MS_TRUE;
459
460 force_string = msGetOutputFormatOption( format, "PALETTE_FORCE", NULL );
461 if( force_string && (strcasecmp(force_string,"on") == 0 || strcasecmp(force_string,"yes") == 0 || strcasecmp(force_string,"true") == 0) )
462 force_palette = MS_TRUE;
463
464 if(force_pc256 || force_palette) {
465 rasterBufferObj qrb;
466 rgbaPixel palette[256], paletteGiven[256];
467 unsigned int numPaletteGivenEntries;
468 memset(&qrb,0,sizeof(rasterBufferObj));
469 qrb.type = MS_BUFFER_BYTE_PALETTE;
470 qrb.width = rb->width;
471 qrb.height = rb->height;
472 qrb.data.palette.pixels = (unsigned char*)malloc(qrb.width*qrb.height*sizeof(unsigned char));
473 qrb.data.palette.scaling_maxval = 255;
474 if(force_pc256) {
475 qrb.data.palette.palette = palette;
476 qrb.data.palette.num_entries = atoi(msGetOutputFormatOption( format, "QUANTIZE_COLORS", "256"));
477 ret = msQuantizeRasterBuffer(rb,&(qrb.data.palette.num_entries),qrb.data.palette.palette,
478 NULL, 0,
479 &qrb.data.palette.scaling_maxval);
480 } else {
481 int colorsWanted = atoi(msGetOutputFormatOption( format, "QUANTIZE_COLORS", "0"));
482 const char *palettePath = msGetOutputFormatOption( format, "PALETTE", "palette.txt");
483 char szPath[MS_MAXPATHLEN];
484 if(map) {
485 msBuildPath(szPath, map->mappath, palettePath);
486 palettePath = szPath;
487 }
488 if(readPalette(palettePath,paletteGiven,&numPaletteGivenEntries,format->transparent) != MS_SUCCESS) {
489 return MS_FAILURE;
490 }
491
492 if(numPaletteGivenEntries == 256 || colorsWanted == 0) {
493 qrb.data.palette.palette = paletteGiven;
494 qrb.data.palette.num_entries = numPaletteGivenEntries;
495 ret = MS_SUCCESS;
496
497 /* we have a full palette and don't want an additional quantization step */
498 } else {
499 /* quantize the image, and mix our colours in the resulting palette */
500 qrb.data.palette.palette = palette;
501 qrb.data.palette.num_entries = MS_MAX(colorsWanted,numPaletteGivenEntries);
502 ret = msQuantizeRasterBuffer(rb,&(qrb.data.palette.num_entries),qrb.data.palette.palette,
503 paletteGiven,numPaletteGivenEntries,
504 &qrb.data.palette.scaling_maxval);
505 }
506 }
507 if(ret != MS_FAILURE) {
508 ret = msClassifyRasterBuffer(rb,&qrb);
509 ret = savePalettePNG(&qrb,info,compression);
510 }
511 msFree(qrb.data.palette.pixels);
512 return ret;
513 } else if(rb->type == MS_BUFFER_BYTE_RGBA) {
514 png_infop info_ptr;
515 int color_type;
516 int row;
517 unsigned int *rowdata;
518 png_structp png_ptr = png_create_write_struct(
519 PNG_LIBPNG_VER_STRING, NULL,NULL,NULL);
520
521 if (!png_ptr)
522 return (MS_FAILURE);
523
524 png_set_compression_level(png_ptr, compression);
525 png_set_filter (png_ptr,0, PNG_FILTER_NONE);
526
527 info_ptr = png_create_info_struct(png_ptr);
528 if (!info_ptr) {
529 png_destroy_write_struct(&png_ptr,
530 (png_infopp)NULL);
531 return (MS_FAILURE);
532 }
533
534 if (setjmp(png_jmpbuf(png_ptr))) {
535 png_destroy_write_struct(&png_ptr, &info_ptr);
536 return (MS_FAILURE);
537 }
538 if(info->fp)
539 png_set_write_fn(png_ptr,info, png_write_data_to_stream, png_flush_data);
540 else
541 png_set_write_fn(png_ptr,info, png_write_data_to_buffer, png_flush_data);
542
543 if(rb->data.rgba.a)
544 color_type = PNG_COLOR_TYPE_RGB_ALPHA;
545 else
546 color_type = PNG_COLOR_TYPE_RGB;
547
548 png_set_IHDR(png_ptr, info_ptr, rb->width, rb->height,
549 8, color_type, PNG_INTERLACE_NONE,
550 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
551
552 png_write_info(png_ptr, info_ptr);
553
554 if(!rb->data.rgba.a && rb->data.rgba.pixel_step==4)
555 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
556
557 rowdata = (unsigned int*)malloc(rb->width*sizeof(unsigned int));
558 for(row=0; row<rb->height; row++) {
559 unsigned int *pixptr = rowdata;
560 int col;
561 unsigned char *a,*r,*g,*b;
562 r=rb->data.rgba.r+row*rb->data.rgba.row_step;
563 g=rb->data.rgba.g+row*rb->data.rgba.row_step;
564 b=rb->data.rgba.b+row*rb->data.rgba.row_step;
565 if(rb->data.rgba.a) {
566 a=rb->data.rgba.a+row*rb->data.rgba.row_step;
567 for(col=0; col<rb->width; col++) {
568 if(*a) {
569 double da = *a/255.0;
570 unsigned char *pix = (unsigned char*)pixptr;
571 pix[0] = *r/da;
572 pix[1] = *g/da;
573 pix[2] = *b/da;
574 pix[3] = *a;
575 } else {
576 *pixptr=0;
577 }
578 pixptr++;
579 a+=rb->data.rgba.pixel_step;
580 r+=rb->data.rgba.pixel_step;
581 g+=rb->data.rgba.pixel_step;
582 b+=rb->data.rgba.pixel_step;
583 }
584 } else {
585 for(col=0; col<rb->width; col++) {
586 unsigned char *pix = (unsigned char*)pixptr;
587 pix[0] = *r;
588 pix[1] = *g;
589 pix[2] = *b;
590 pixptr++;
591 r+=rb->data.rgba.pixel_step;
592 g+=rb->data.rgba.pixel_step;
593 b+=rb->data.rgba.pixel_step;
594 }
595 }
596
597 png_write_row(png_ptr,(png_bytep)rowdata);
598
599 }
600 png_write_end(png_ptr, info_ptr);
601 free(rowdata);
602 png_destroy_write_struct(&png_ptr, &info_ptr);
603 return MS_SUCCESS;
604 } else {
605 msSetError(MS_MISCERR,"Unknown buffer type","saveAsPNG()");
606 return MS_FAILURE;
607 }
608 }
609
610 /* For platforms with incomplete ANSI defines. Fortunately,
611 SEEK_SET is defined to be zero by the standard. */
612
613 #ifndef SEEK_SET
614 #define SEEK_SET 0
615 #endif /* SEEK_SET */
616
617
readPNG(char * path,rasterBufferObj * rb)618 int readPNG(char *path, rasterBufferObj *rb)
619 {
620 png_uint_32 width,height;
621 unsigned char *a,*r,*g,*b;
622 int bit_depth,color_type,i;
623 unsigned char **row_pointers;
624 png_structp png_ptr = NULL;
625 png_infop info_ptr = NULL;
626
627 FILE *stream = fopen(path,"rb");
628 if(!stream)
629 return MS_FAILURE;
630
631 /* could pass pointers to user-defined error handlers instead of NULLs: */
632 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
633 if (!png_ptr) {
634 fclose(stream);
635 return MS_FAILURE; /* out of memory */
636 }
637
638 info_ptr = png_create_info_struct(png_ptr);
639 if (!info_ptr) {
640 png_destroy_read_struct(&png_ptr, NULL, NULL);
641 fclose(stream);
642 return MS_FAILURE; /* out of memory */
643 }
644
645 if(setjmp(png_jmpbuf(png_ptr))) {
646 png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
647 fclose(stream);
648 return MS_FAILURE;
649 }
650
651 png_init_io(png_ptr,stream);
652 png_read_info(png_ptr,info_ptr);
653 png_get_IHDR(png_ptr, info_ptr, &width, &height,
654 &bit_depth, &color_type,
655 NULL,NULL,NULL);
656
657 rb->width = width;
658 rb->height = height;
659 rb->type = MS_BUFFER_BYTE_RGBA;
660 rb->data.rgba.pixels = (unsigned char*)malloc(width*height*4*sizeof(unsigned char));
661 row_pointers = (unsigned char**)malloc(height*sizeof(unsigned char*));
662 rb->data.rgba.pixel_step=4;
663 rb->data.rgba.row_step = width*4;
664 b = rb->data.rgba.b = &rb->data.rgba.pixels[0];
665 g = rb->data.rgba.g = &rb->data.rgba.pixels[1];
666 r = rb->data.rgba.r = &rb->data.rgba.pixels[2];
667 a = rb->data.rgba.a = &rb->data.rgba.pixels[3];
668
669 for(i=0; i<height; i++) {
670 row_pointers[i] = &(rb->data.rgba.pixels[i*rb->data.rgba.row_step]);
671 }
672
673 if (color_type == PNG_COLOR_TYPE_PALETTE)
674 /* expand palette images to RGB */
675 png_set_expand(png_ptr);
676 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
677 /* expand low bit-depth grayscale to 8bits */
678 png_set_expand(png_ptr);
679 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
680 /* expand transparency chunks to full alpha */
681 png_set_expand(png_ptr);
682 if (bit_depth == 16)
683 /* scale 16bits down to 8 */
684 png_set_strip_16(png_ptr);
685 if (color_type == PNG_COLOR_TYPE_GRAY ||
686 color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
687 /* convert grayscale to rgba */
688 png_set_gray_to_rgb(png_ptr);
689
690 png_set_bgr(png_ptr);
691 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
692 color_type == PNG_COLOR_TYPE_PALETTE)
693 png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
694
695 png_read_update_info(png_ptr, info_ptr);
696 assert(png_get_rowbytes(png_ptr, info_ptr) == rb->data.rgba.row_step);
697
698 png_read_image(png_ptr, row_pointers);
699 free(row_pointers);
700 row_pointers=NULL;
701 png_read_end(png_ptr,NULL);
702 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
703
704 /*premultiply data*/
705 for(i=0; i<width*height; i++) {
706 if(*a < 255) {
707 if(*a == 0) {
708 *r=*g=*b=0;
709 } else {
710 *r = (*r * *a + 255) >> 8;
711 *g = (*g * *a + 255) >> 8;
712 *b = (*b * *a + 255) >> 8;
713 }
714 }
715 a+=4;
716 b+=4;
717 g+=4;
718 r+=4;
719 }
720
721 fclose(stream);
722 return MS_SUCCESS;
723
724 }
725
msSaveRasterBuffer(mapObj * map,rasterBufferObj * rb,FILE * stream,outputFormatObj * format)726 int msSaveRasterBuffer(mapObj *map, rasterBufferObj *rb, FILE *stream,
727 outputFormatObj *format)
728 {
729 if(strcasestr(format->driver,"/png")) {
730 streamInfo info;
731 info.fp = stream;
732 info.buffer = NULL;
733
734 return saveAsPNG(map, rb,&info,format);
735 } else if(strcasestr(format->driver,"/jpeg")) {
736 streamInfo info;
737 info.fp = stream;
738 info.buffer=NULL;
739
740 return saveAsJPEG(map, rb,&info,format);
741 } else {
742 msSetError(MS_MISCERR,"unsupported image format\n", "msSaveRasterBuffer()");
743 return MS_FAILURE;
744 }
745 }
746
msSaveRasterBufferToBuffer(rasterBufferObj * data,bufferObj * buffer,outputFormatObj * format)747 int msSaveRasterBufferToBuffer(rasterBufferObj *data, bufferObj *buffer,
748 outputFormatObj *format)
749 {
750 if(strcasestr(format->driver,"/png")) {
751 streamInfo info;
752 info.fp = NULL;
753 info.buffer = buffer;
754 return saveAsPNG(NULL, data,&info,format);
755 } else if(strcasestr(format->driver,"/jpeg")) {
756 streamInfo info;
757 info.fp = NULL;
758 info.buffer=buffer;
759 return saveAsJPEG(NULL, data,&info,format);
760 } else {
761 msSetError(MS_MISCERR,"unsupported image format\n", "msSaveRasterBuffer()");
762 return MS_FAILURE;
763 }
764 }
765
766 #ifdef USE_GIF
767 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
gif_error_msg(int code)768 static char const *gif_error_msg(int code)
769 #else
770 static char const *gif_error_msg()
771 #endif
772 {
773 static char msg[80];
774
775 #if (!defined GIFLIB_MAJOR) || (GIFLIB_MAJOR < 5)
776 int code = GifLastError();
777 #endif
778 switch (code) {
779 case E_GIF_ERR_OPEN_FAILED: /* should not see this */
780 return "Failed to open given file";
781
782 case E_GIF_ERR_WRITE_FAILED:
783 return "Write failed";
784
785 case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
786 return "Screen descriptor already passed to giflib";
787
788 case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
789 return "Image descriptor already passed to giflib";
790
791 case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
792 return "Neither global nor local color map set";
793
794 case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
795 return "Too much pixel data passed to giflib";
796
797 case E_GIF_ERR_NOT_ENOUGH_MEM:
798 return "Out of memory";
799
800 case E_GIF_ERR_DISK_IS_FULL:
801 return "Disk is full";
802
803 case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
804 return "File close failed";
805
806 case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
807 return "File not writable";
808
809 case D_GIF_ERR_OPEN_FAILED:
810 return "Failed to open file";
811
812 case D_GIF_ERR_READ_FAILED:
813 return "Failed to read from file";
814
815 case D_GIF_ERR_NOT_GIF_FILE:
816 return "File is not a GIF file";
817
818 case D_GIF_ERR_NO_SCRN_DSCR:
819 return "No screen descriptor detected - invalid file";
820
821 case D_GIF_ERR_NO_IMAG_DSCR:
822 return "No image descriptor detected - invalid file";
823
824 case D_GIF_ERR_NO_COLOR_MAP:
825 return "No global or local color map found";
826
827 case D_GIF_ERR_WRONG_RECORD:
828 return "Wrong record type detected - invalid file?";
829
830 case D_GIF_ERR_DATA_TOO_BIG:
831 return "Data in file too big for image";
832
833 case D_GIF_ERR_NOT_ENOUGH_MEM:
834 return "Out of memory";
835
836 case D_GIF_ERR_CLOSE_FAILED:
837 return "Close failed";
838
839 case D_GIF_ERR_NOT_READABLE:
840 return "File not opened for read";
841
842 case D_GIF_ERR_IMAGE_DEFECT:
843 return "Defective image";
844
845 case D_GIF_ERR_EOF_TOO_SOON:
846 return "Unexpected EOF - invalid file";
847
848 default:
849 sprintf(msg, "Unknown giflib error code %d", code);
850 return msg;
851 }
852 }
853
854
855 /* not fully implemented and tested */
856 /* missing: set the first pointer to a,r,g,b */
readGIF(char * path,rasterBufferObj * rb)857 int readGIF(char *path, rasterBufferObj *rb)
858 {
859 int i, j, codeSize, extCode, firstImageRead = MS_FALSE;
860 unsigned char *r,*g,*b,*a;
861 int transIdx = -1;
862 GifFileType *image;
863 GifPixelType *line;
864 GifRecordType recordType;
865 GifByteType *codeBlock, *extension;
866 ColorMapObject *cmap;
867 int interlacedOffsets[] = {0,4,2,1};
868 int interlacedJumps[] = {8,8,4,2};
869 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
870 int errcode;
871 #endif
872
873
874 rb->type = MS_BUFFER_BYTE_RGBA;
875 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
876 image = DGifOpenFileName(path, &errcode);
877 if (image == NULL) {
878 msSetError(MS_MISCERR,"failed to load gif image: %s","readGIF()", gif_error_msg(errcode));
879 return MS_FAILURE;
880 }
881 #else
882 image = DGifOpenFileName(path);
883 if (image == NULL) {
884 msSetError(MS_MISCERR,"failed to load gif image: %s","readGIF()", gif_error_msg());
885 return MS_FAILURE;
886 }
887 #endif
888 rb->width = image->SWidth;
889 rb->height = image->SHeight;
890 rb->data.rgba.row_step = rb->width * 4;
891 rb->data.rgba.pixel_step = 4;
892 rb->data.rgba.pixels = (unsigned char*)malloc(rb->width*rb->height*4*sizeof(unsigned char));
893 b = rb->data.rgba.b = &rb->data.rgba.pixels[0];
894 g = rb->data.rgba.g = &rb->data.rgba.pixels[1];
895 r = rb->data.rgba.r = &rb->data.rgba.pixels[2];
896 a = rb->data.rgba.a = &rb->data.rgba.pixels[3];
897
898 cmap = (image->Image.ColorMap)?image->Image.ColorMap:image->SColorMap;
899 for(i=0; i<rb->width*rb->height; i++) {
900 *a = 255;
901 *r = cmap->Colors[image->SBackGroundColor].Red;
902 *g = cmap->Colors[image->SBackGroundColor].Green;
903 *b = cmap->Colors[image->SBackGroundColor].Blue;
904 a+=rb->data.rgba.pixel_step;
905 r+=rb->data.rgba.pixel_step;
906 g+=rb->data.rgba.pixel_step;
907 b+=rb->data.rgba.pixel_step;
908 }
909
910 do {
911 if (DGifGetRecordType(image, &recordType) == GIF_ERROR) {
912 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
913 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error));
914 #else
915 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg());
916 #endif
917 return MS_FAILURE;
918 }
919
920 switch (recordType) {
921 case SCREEN_DESC_RECORD_TYPE:
922 DGifGetScreenDesc(image);
923 break;
924 case IMAGE_DESC_RECORD_TYPE:
925 if (DGifGetImageDesc(image) == GIF_ERROR) {
926 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
927 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error));
928 #else
929 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg());
930 #endif
931 return MS_FAILURE;
932 }
933 if (!firstImageRead) {
934
935 int row = image->Image.Top;
936 int col = image->Image.Left;
937 int width = image->Image.Width;
938 int height = image->Image.Height;
939
940 /* sanity check: */
941 if(col+width>rb->width || row+height>rb->height) {
942 msSetError(MS_MISCERR,"corrupted gif image, img size exceeds screen size","readGIF()");
943 return MS_FAILURE;
944 }
945
946 line = (GifPixelType *) malloc(width * sizeof(GifPixelType));
947 if(image->Image.Interlace) {
948 int count;
949 for(count=0; count<4; count++) {
950 for(i=row+interlacedOffsets[count]; i<row+height; i+=interlacedJumps[count]) {
951 int offset = i * rb->data.rgba.row_step + col * rb->data.rgba.pixel_step;
952 a = rb->data.rgba.a + offset;
953 r = rb->data.rgba.r + offset;
954 g = rb->data.rgba.g + offset;
955 b = rb->data.rgba.b + offset;
956 if (DGifGetLine(image, line, width) == GIF_ERROR) {
957 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
958 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg(image->Error));
959 #else
960 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg());
961 #endif
962 return MS_FAILURE;
963 }
964
965 for(j=0; j<width; j++) {
966 GifPixelType pix = line[j];
967 if(transIdx == -1 || pix != transIdx) {
968 *a = 255;
969 *r = cmap->Colors[pix].Red;
970 *g = cmap->Colors[pix].Green;
971 *b = cmap->Colors[pix].Blue;
972 } else {
973 *a = *r = *g = *b = 0;
974 }
975 a+=rb->data.rgba.pixel_step;
976 r+=rb->data.rgba.pixel_step;
977 g+=rb->data.rgba.pixel_step;
978 b+=rb->data.rgba.pixel_step;
979 }
980 }
981 }
982 } else {
983 for (i = 0; i < height; i++) {
984 int offset = i * rb->data.rgba.row_step + col * rb->data.rgba.pixel_step;
985 a = rb->data.rgba.a + offset;
986 r = rb->data.rgba.r + offset;
987 g = rb->data.rgba.g + offset;
988 b = rb->data.rgba.b + offset;
989 if (DGifGetLine(image, line, width) == GIF_ERROR) {
990 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
991 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg(image->Error));
992 #else
993 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()",gif_error_msg());
994 #endif
995 return MS_FAILURE;
996 }
997 for(j=0; j<width; j++) {
998 GifPixelType pix = line[j];
999 if(transIdx == -1 || pix != transIdx) {
1000 *a = 255;
1001 *r = cmap->Colors[pix].Red;
1002 *g = cmap->Colors[pix].Green;
1003 *b = cmap->Colors[pix].Blue;
1004 } else {
1005 *a = *r = *g = *b = 0;
1006 }
1007 a+=rb->data.rgba.pixel_step;
1008 r+=rb->data.rgba.pixel_step;
1009 g+=rb->data.rgba.pixel_step;
1010 b+=rb->data.rgba.pixel_step;
1011 }
1012 }
1013 }
1014 free((char *) line);
1015 firstImageRead = MS_TRUE;
1016 } else {
1017 /* Skip all next images */
1018 if (DGifGetCode(image, &codeSize, &codeBlock) == GIF_ERROR) {
1019 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
1020 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error));
1021 #else
1022 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg());
1023 #endif
1024 return MS_FAILURE;
1025 }
1026 while (codeBlock != NULL) {
1027 if (DGifGetCodeNext(image, &codeBlock) == GIF_ERROR) {
1028 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
1029 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error));
1030 #else
1031 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg());
1032 #endif
1033 return MS_FAILURE;
1034 }
1035 }
1036 }
1037 break;
1038 case EXTENSION_RECORD_TYPE:
1039 /* skip all extension blocks */
1040 if (DGifGetExtension(image, &extCode, &extension) == GIF_ERROR) {
1041 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
1042 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error));
1043 #else
1044 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg());
1045 #endif
1046 return MS_FAILURE;
1047 }
1048 if(extCode == 249 && (extension[1] & 1))
1049 transIdx = extension[4];
1050 for (;;) {
1051 if (DGifGetExtensionNext(image, &extension) == GIF_ERROR) {
1052 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
1053 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg(image->Error));
1054 #else
1055 msSetError(MS_MISCERR,"corrupted gif image?: %s","readGIF()", gif_error_msg());
1056 #endif
1057 return MS_FAILURE;
1058 }
1059 if (extension == NULL)
1060 break;
1061 if(extension[1] & 1)
1062 transIdx = extension[4];
1063 }
1064 break;
1065 case UNDEFINED_RECORD_TYPE:
1066 break;
1067 case TERMINATE_RECORD_TYPE:
1068 break;
1069 default:
1070 break;
1071 }
1072
1073 } while (recordType != TERMINATE_RECORD_TYPE);
1074
1075
1076 #if defined GIFLIB_MAJOR && GIFLIB_MINOR && ((GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) || (GIFLIB_MAJOR > 5))
1077 if (DGifCloseFile(image, &errcode) == GIF_ERROR) {
1078 msSetError(MS_MISCERR,"failed to close gif after loading: %s","readGIF()", gif_error_msg(errcode));
1079 return MS_FAILURE;
1080 }
1081 #else
1082 if (DGifCloseFile(image) == GIF_ERROR) {
1083 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
1084 msSetError(MS_MISCERR,"failed to close gif after loading: %s","readGIF()", gif_error_msg(image->Error));
1085 #else
1086 msSetError(MS_MISCERR,"failed to close gif after loading: %s","readGIF()", gif_error_msg());
1087 #endif
1088 return MS_FAILURE;
1089 }
1090 #endif
1091
1092 return MS_SUCCESS;
1093 }
1094 #endif
1095
msLoadMSRasterBufferFromFile(char * path,rasterBufferObj * rb)1096 int msLoadMSRasterBufferFromFile(char *path, rasterBufferObj *rb)
1097 {
1098 FILE *stream;
1099 unsigned char signature[8];
1100 int ret = MS_FAILURE;
1101 stream = fopen(path,"rb");
1102 if(!stream) {
1103 msSetError(MS_MISCERR, "unable to open file %s for reading", "msLoadMSRasterBufferFromFile()", path);
1104 return MS_FAILURE;
1105 }
1106 if(1 != fread(signature,8,1,stream)) {
1107 fclose(stream);
1108 msSetError(MS_MISCERR, "Unable to read header from image file %s", "msLoadMSRasterBufferFromFile()",path);
1109 return MS_FAILURE;
1110 }
1111 fclose(stream);
1112 if(png_sig_cmp(signature,0,8) == 0) {
1113 ret = readPNG(path,rb);
1114 } else if (!strncmp((char*)signature,"GIF",3)) {
1115 #ifdef USE_GIF
1116 ret = readGIF(path,rb);
1117 #else
1118 msSetError(MS_MISCERR,"pixmap %s seems to be GIF formatted, but this server is compiled without GIF support","readImage()",path);
1119 return MS_FAILURE;
1120 #endif
1121 } else {
1122 msSetError(MS_MISCERR,"unsupported pixmap format","readImage()");
1123 return MS_FAILURE;
1124 }
1125 return ret;
1126 }
1127