1 /* Part of XPCE --- The SWI-Prolog GUI toolkit
2
3 Author: Jan Wielemaker and Anjo Anjewierden
4 E-mail: jan@swi.psy.uva.nl
5 WWW: http://www.swi.psy.uva.nl/projects/xpce/
6 Copyright (c) 2000-2013, University of Amsterdam
7 All rights reserved.
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions
11 are met:
12
13 1. Redistributions of source code must retain the above copyright
14 notice, this list of conditions and the following disclaimer.
15
16 2. Redistributions in binary form must reproduce the above copyright
17 notice, this list of conditions and the following disclaimer in
18 the documentation and/or other materials provided with the
19 distribution.
20
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 #include "include.h"
36
37 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
38 Actually, this module does both medium-level JPEG and GIF writing. The
39 low-level routines are in the img directory. These routines are called
40 by msimage.c, which in turn implements the OS specific version of
41 gra/image.c implementing class image.
42 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
43
44 #if defined(__MINGW32__)
45 #define XMD_H
46 #endif
47
48 #ifdef HAVE_LIBJPEG
49 #ifdef __RPCNDR_H__
50 #define HAVE_BOOLEAN /* prevent jmorecfg.h from redefining it */
51 #endif
52 #undef GLOBAL /* conflict */
53 #include <jpeglib.h>
54 #include <jerror.h>
55 #include <setjmp.h>
56
57
58 extern void jpeg_iostream_dest(j_compress_ptr cinfo, IOSTREAM *outfile);
59 extern void jpeg_iostream_src(j_decompress_ptr cinfo, IOSTREAM* infile);
60 extern void attach_dib_image(Image image, BITMAPINFO *bmi, BYTE *bits);
61
62 int
write_jpeg_file(IOSTREAM * fd,Image image,HBITMAP bm)63 write_jpeg_file(IOSTREAM *fd, Image image, HBITMAP bm)
64 { BITMAP bitmap;
65 int width, height;
66 int y;
67 HDC hdc;
68 HBITMAP obm;
69 struct jpeg_compress_struct cinfo;
70 struct jpeg_error_mgr jerr;
71 JSAMPLE *row;
72 DisplayObj d = image->display;
73 HPALETTE ohpal=0, hpal;
74
75 if ( !GetObject(bm, sizeof(BITMAP), &bitmap) )
76 { Cprintf("write_jpeg_file(): GetObject() failed\n");
77 return -1;
78 }
79
80 if ( isNil(d) )
81 d = CurrentDisplay(image);
82 if ( instanceOfObject(d->colour_map, ClassColourMap) )
83 hpal = getPaletteColourMap(d->colour_map);
84 else
85 hpal = NULL;
86
87 width = bitmap.bmWidth;
88 height = bitmap.bmHeight;
89 /*depth = bitmap.bmPlanes * bitmap.bmBitsPixel;*/
90
91 hdc = CreateCompatibleDC(NULL);
92 if ( hpal )
93 { ohpal = SelectPalette(hdc, hpal, FALSE);
94 RealizePalette(hdc);
95 }
96 obm = ZSelectObject(hdc, bm);
97
98 row = pceMalloc(sizeof(JSAMPLE)*3*width);
99
100 cinfo.err = jpeg_std_error(&jerr);
101 jpeg_create_compress(&cinfo);
102 jpeg_iostream_dest(&cinfo, fd);
103
104 cinfo.image_width = width;
105 cinfo.image_height = height;
106 cinfo.input_components = 3;
107 cinfo.in_color_space = JCS_RGB;
108 jpeg_set_defaults(&cinfo);
109
110 jpeg_start_compress(&cinfo, TRUE);
111
112 for(y=0; y<height; y++)
113 { int x;
114 JSAMPLE *s = row;
115
116 for(x=0; x<width; x++)
117 { COLORREF c = GetPixel(hdc, x, y);
118
119 *s++ = GetRValue(c);
120 *s++ = GetGValue(c);
121 *s++ = GetBValue(c);
122 DEBUG(NAME_jpeg, Cprintf("#%02x%02x%02x", s[-3], s[-2], s[-1]));
123 }
124 DEBUG(NAME_jpeg, Cprintf("\n"));
125
126 jpeg_write_scanlines(&cinfo, &row, 1);
127 }
128
129 pceFree(row);
130 jpeg_finish_compress(&cinfo);
131 jpeg_destroy_compress(&cinfo);
132
133 ZSelectObject(hdc, obm);
134 if ( ohpal )
135 SelectPalette(hdc, ohpal, FALSE);
136 DeleteDC(hdc);
137
138 return 0;
139 }
140
141 /*******************************
142 * READING JPEG *
143 *******************************/
144
145 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
146 status read_jpeg_file(IOSTREAM *fd, Image image)
147
148 Reads JPEG from a stream and attaches a DIB to the image. If an error
149 occurs this routine ensures the file-pointer is not moved, so we can try
150 other image-formats.
151
152 On colour-mapped displays, this routine passes the colour-map of the
153 display to the JPEG library to reach at an optimal rendered image.
154 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
155
156 struct jpeg_colour_map
157 { int size;
158 int allocated;
159 JSAMPLE *colours[3];
160 RGBQUAD *dib_colours;
161 };
162
163 static JpegColourMap
alloc_jpeg_cmap(int size)164 alloc_jpeg_cmap(int size)
165 { JpegColourMap map = alloc(sizeof(*map));
166
167 map->allocated = size;
168 map->size = 0;
169 map->colours[0] = alloc(sizeof(JSAMPLE)*size);
170 map->colours[1] = alloc(sizeof(JSAMPLE)*size);
171 map->colours[2] = alloc(sizeof(JSAMPLE)*size);
172 map->dib_colours = alloc(sizeof(RGBQUAD)*size);
173
174 return map;
175 }
176
177 void
free_jpeg_cmap(JpegColourMap map)178 free_jpeg_cmap(JpegColourMap map)
179 { unalloc(sizeof(JSAMPLE)*map->allocated, map->colours[0]);
180 unalloc(sizeof(JSAMPLE)*map->allocated, map->colours[1]);
181 unalloc(sizeof(JSAMPLE)*map->allocated, map->colours[2]);
182 unalloc(sizeof(RGBQUAD)*map->allocated, map->dib_colours);
183
184 unalloc(sizeof(*map), map);
185 }
186
187
188 JpegColourMap
jpeg_cmap_from_colour_map(ColourMap cm,DisplayObj d)189 jpeg_cmap_from_colour_map(ColourMap cm, DisplayObj d)
190 { WsCmdata data = getWsCmdata(cm);
191 Vector colours;
192
193 if ( data->jpeg_cmap )
194 return data->jpeg_cmap;
195
196 if ( (colours = get(cm, NAME_colours, EAV)) )
197 { int ncolors = valInt(colours->size);
198 int i=0;
199 Colour e;
200 JpegColourMap map = alloc_jpeg_cmap(ncolors);
201
202 for_vector(colours, e,
203 { if ( notNil(e) )
204 { int r; int g; int b;
205 if ( isDefault(e->red) )
206 getXrefObject(e, d); /* Open the colour */
207
208 r = valInt(e->red)>>8;
209 g = valInt(e->green)>>8;
210 b = valInt(e->blue)>>8;
211
212 map->colours[0][i] = r;
213 map->colours[1][i] = g;
214 map->colours[2][i] = b;
215
216 map->dib_colours[i].rgbRed = r;
217 map->dib_colours[i].rgbGreen = g;
218 map->dib_colours[i].rgbBlue = b;
219
220 i++;
221 }
222 });
223
224 map->size = i;
225 data->jpeg_cmap = map;
226
227 return map;
228 }
229
230 return NULL;
231 }
232
233
234 /*******************************
235 * ERRORS *
236 *******************************/
237
238 struct my_jpeg_error_mgr
239 { struct jpeg_error_mgr jerr;
240 jmp_buf jmp_context;
241 };
242
243
244 static void
my_exit(j_common_ptr cl)245 my_exit(j_common_ptr cl)
246 { struct jpeg_decompress_struct *cinfo = (struct jpeg_decompress_struct *)cl;
247 struct my_jpeg_error_mgr *jerr = (struct my_jpeg_error_mgr *)cinfo->err;
248
249 longjmp(jerr->jmp_context, 1);
250 }
251
252
253 status
read_jpeg_file(IOSTREAM * fd,Image image)254 read_jpeg_file(IOSTREAM *fd, Image image)
255 { struct jpeg_decompress_struct cinfo;
256 struct my_jpeg_error_mgr jerr;
257 long here = Stell(fd);
258 long row_stride;
259 int width, height, bwidth, image_size;
260 JSAMPLE **buff;
261 BYTE *data;
262 BITMAPINFO *dib = NULL;
263 BITMAPINFOHEADER *header = NULL; /* silence compiler */
264 DisplayObj d = image->display;
265 int outline;
266 JpegColourMap cmap = NULL;
267
268 cinfo.err = jpeg_std_error((struct jpeg_error_mgr *)&jerr);
269 if ( setjmp(jerr.jmp_context) )
270 { switch(jerr.jerr.msg_code)
271 { case JERR_OUT_OF_MEMORY:
272 return sysPce("Not enough memory");
273 case JERR_NO_SOI:
274 break; /* invalid */
275 default:
276 DEBUG(NAME_image,
277 { char buf[1024];
278
279 (*jerr.jerr.format_message)((j_common_ptr)&cinfo, buf);
280 Cprintf("JPEG: %s\n", buf);
281 });
282 break; /* also invalid */
283 }
284
285 jpeg_destroy_decompress(&cinfo);
286
287 Sseek(fd, here, SEEK_SET);
288 fail;
289 }
290 jerr.jerr.error_exit = my_exit;
291
292 jpeg_create_decompress(&cinfo);
293 jpeg_iostream_src(&cinfo, fd);
294
295 jpeg_save_markers(&cinfo, JPEG_COM, 0xffff);
296 jpeg_read_header(&cinfo, TRUE);
297
298 /* colourmap handling */
299 if ( cinfo.output_components == 3 )
300 { if ( isNil(d) )
301 d = CurrentDisplay(image);
302 openDisplay(d);
303
304 if ( ws_depth_display(d) < 16 &&
305 instanceOfObject(d->colour_map, ClassColourMap) &&
306 (cmap = jpeg_cmap_from_colour_map(d->colour_map, d)) )
307 { dib = pceMalloc(sizeof(dib->bmiHeader)+cmap->size*sizeof(RGBQUAD));
308
309 header = &dib->bmiHeader;
310 memset(header, 0, sizeof(*header));
311 memcpy(&dib->bmiColors[0], cmap->dib_colours,
312 cmap->size*sizeof(RGBQUAD));
313 header->biBitCount = 8;
314 header->biClrUsed = cmap->size;
315
316 cinfo.colormap = cmap->colours;
317 cinfo.actual_number_of_colors = cmap->size;
318 cinfo.quantize_colors = TRUE;
319 }
320 }
321
322 jpeg_start_decompress(&cinfo);
323 row_stride = cinfo.output_width * cinfo.output_components;
324 buff = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo,
325 JPOOL_IMAGE, row_stride, 1);
326
327 width = cinfo.image_width;
328 height = cinfo.image_height;
329 if ( cmap )
330 bwidth = width;
331 else
332 bwidth = ((width*3+3)&0xfffc); /* why is this? */
333 image_size = bwidth*height;
334
335 data = pceMalloc(image_size);
336
337 for(outline = height-1;
338 cinfo.output_scanline < cinfo.output_height;
339 outline--)
340 { int i;
341 BYTE *src, *dest;
342
343 dest = data + bwidth*outline;
344
345 jpeg_read_scanlines(&cinfo, buff, 1);
346 i = width;
347 src = buff[0];
348
349 if ( cmap ) /* colour-mapped */
350 { while(i--)
351 { *dest++ = *src++;
352 }
353 } else
354 { switch( cinfo.output_components )
355 { case 1: /* grayscale JPEG */
356 while(i--)
357 { *dest++ = src[0];
358 *dest++ = src[0];
359 *dest++ = src[0];
360 src++;
361 }
362 break;
363 case 3: /* RGB JPEG */
364 while(i--)
365 { *dest++ = src[2];
366 *dest++ = src[1];
367 *dest++ = src[0];
368 src += 3;
369 }
370 break;
371 default: /* We don't have this */
372 Sseek(fd, here, SEEK_SET);
373 Cprintf("JPeg with %d output_components??\n");
374 fail;
375 }
376
377 memset(dest, 0, bwidth - width*3);
378 }
379 }
380
381 if ( cinfo.marker_list )
382 { jpeg_saved_marker_ptr m;
383 Chain ch;
384
385 attributeObject(image, NAME_comment, (ch=newObject(ClassChain, EAV)));
386
387 for(m = cinfo.marker_list; m; m = m->next )
388 { if ( m->marker == JPEG_COM )
389 { string s;
390
391 if ( str_set_n_ascii(&s, m->data_length, (char*)m->data) )
392 appendChain(ch, StringToString(&s));
393 }
394 }
395 }
396
397 jpeg_finish_decompress(&cinfo);
398 jpeg_destroy_decompress(&cinfo);
399
400 if ( !dib )
401 { dib = pceMalloc(sizeof(*dib));
402 header = &dib->bmiHeader;
403 memset(dib, 0, sizeof(*dib));
404 header->biBitCount = 24;
405 }
406 header->biSize = sizeof(BITMAPINFOHEADER);
407 header->biWidth = width;
408 header->biHeight = height;
409 header->biPlanes = 1;
410 header->biCompression = BI_RGB;
411 header->biSizeImage = image_size;
412
413 attach_dib_image(image, dib, data);
414
415 succeed;
416 }
417
418 #endif /*HAVE_LIBJPEG*/
419
420
421
422 /*******************************
423 * GIF (SHOULD MOVE) *
424 *******************************/
425
426 #ifdef O_GIFWRITE
427 #include <img/gifwrite.h>
428 typedef unsigned char GSAMPLE;
429 static GSAMPLE *mask_bits(HBITMAP mask); /* forwards */
430
431 #define ROUND(p, n) ((((p) + (n) - 1) & ~((n) - 1)))
432
433 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
434 Old versions used GetPixel(). Now it uses GetDIBits(), which probably
435 gives a big performance boost. Unfortutanely though we need packed RGB
436 and GetDIBits() returns a word-aligned array of RGB triples Billy nicely
437 orders as BGR. Hence the shifting and swapping ...
438
439 Note that the height field of the structure is set to the negative
440 height, the bits are extracted top-to-bottom rather than MS native
441 bottom-to-top.
442 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
443
444 int
write_gif_file(IOSTREAM * fd,Image image,HBITMAP bm,HBITMAP mask)445 write_gif_file(IOSTREAM *fd, Image image, HBITMAP bm, HBITMAP mask)
446 { int width = valInt(image->size->w);
447 int height = valInt(image->size->h);
448 int rval, sl;
449 HDC hdc;
450 GSAMPLE *data, *maskdata = NULL;
451 DisplayObj d = image->display;
452 HPALETTE ohpal=0, hpal;
453 BITMAPINFO info;
454
455 if ( isNil(d) )
456 d = CurrentDisplay(image);
457 if ( instanceOfObject(d->colour_map, ClassColourMap) )
458 hpal = getPaletteColourMap(d->colour_map);
459 else
460 hpal = NULL;
461
462 hdc = CreateCompatibleDC(NULL);
463 if ( hpal )
464 { ohpal = SelectPalette(hdc, hpal, FALSE);
465 RealizePalette(hdc);
466 }
467
468 memset(&info, 0, sizeof(info));
469 info.bmiHeader.biSize = sizeof(info.bmiHeader);
470 info.bmiHeader.biWidth = width;
471 info.bmiHeader.biHeight = -height; /* work top-down */
472 info.bmiHeader.biPlanes = 1;
473 info.bmiHeader.biBitCount = 24;
474 info.bmiHeader.biCompression = BI_RGB; /* from WINGDI, this is 0 */
475
476 if ( !(sl = GetDIBits(hdc, bm,
477 0, height,
478 NULL,
479 &info,
480 DIB_RGB_COLORS)) )
481 { Cprintf("%s: GetDIBits() returned %d", pp(image), sl);
482 return FALSE;
483 } else
484 { DEBUG(NAME_image,
485 { Cprintf("%s: GetDIBits() returned %d; ", pp(image), sl);
486 Cprintf("Image = %dx%dx%d (%ld bytes)\n",
487 info.bmiHeader.biWidth,
488 info.bmiHeader.biHeight,
489 info.bmiHeader.biBitCount,
490 info.bmiHeader.biSizeImage);
491 });
492
493 data = pceMalloc(info.bmiHeader.biSizeImage);
494 height = abs(info.bmiHeader.biHeight);
495 width = info.bmiHeader.biWidth;
496
497 if ( !GetDIBits(hdc, bm,
498 0, height,
499 data,
500 &info,
501 DIB_RGB_COLORS) )
502 { Cprintf("%s: GetDIBits() failed to get bits\n", pp(image));
503 return FALSE;
504 }
505
506 /* adjust alignment and colours */
507 { int outlensl = width*3;
508 int inlensl = ROUND(outlensl, sizeof(DWORD));
509 int y;
510
511 for(y=0; y<height; y++)
512 { GSAMPLE *p = data+y*outlensl;
513 int x;
514
515 if ( inlensl != outlensl )
516 memcpy(p, data+y*inlensl, outlensl);
517 /* swap blue/red */
518 for(x=0; x<width; x++, p+=3)
519 { GSAMPLE tmp = p[0];
520 p[0] = p[2];
521 p[2] = tmp;
522 }
523 }
524 }
525 }
526
527 if ( mask )
528 maskdata = mask_bits(mask);
529
530 rval = gifwrite_rgb(fd, data, maskdata, width, height);
531 pceFree(data);
532 if ( maskdata )
533 pceFree(maskdata);
534
535 if ( ohpal )
536 SelectPalette(hdc, ohpal, FALSE);
537 DeleteDC(hdc);
538
539 return rval;
540 }
541
542
543 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
544 mask_bits() returns a bitmask for gifwrite_rgb(). This mask is 1 at
545 transparent pixels and scanlines are byte-aligned. MS scanlines are
546 word-aligned.
547 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
548
549 static GSAMPLE *
mask_bits(HBITMAP mask)550 mask_bits(HBITMAP mask)
551 { HDC hdc = CreateCompatibleDC(NULL);
552 GSAMPLE *data = NULL;
553 BITMAPINFO *info = alloca(sizeof(BITMAPINFOHEADER)+2*sizeof(RGBQUAD));
554 BITMAP bitmap;
555 int outlensl, inlensl; /* scanline lengths */
556 int width, height, y;
557
558 if ( !GetObject(mask, sizeof(BITMAP), &bitmap) )
559 { Cprintf("mask_bits(): GetObject() failed\n");
560 goto out;
561 }
562 /*Cprintf("Mask is %dx%d\n", bitmap.bmWidth, bitmap.bmHeight);*/
563
564 memset(info, 0, sizeof(*info));
565 info->bmiHeader.biSize = sizeof(info->bmiHeader);
566 info->bmiHeader.biWidth = bitmap.bmWidth;
567 info->bmiHeader.biHeight = -bitmap.bmHeight;
568 info->bmiHeader.biPlanes = 1;
569 info->bmiHeader.biBitCount = 1;
570 info->bmiHeader.biCompression = BI_RGB;
571 /* get info */
572 if ( !GetDIBits(hdc, mask,
573 0, bitmap.bmHeight,
574 NULL,
575 info,
576 DIB_RGB_COLORS) )
577 { Cprintf("%d: mask_bits(): GetDIBits() failed\n", __LINE__);
578 goto out;
579 }
580 /*Cprintf("Mask: %d bytes\n", info->bmiHeader.biSizeImage);*/
581
582 /* get the bits */
583 data = pceMalloc(info->bmiHeader.biSizeImage);
584 height = abs(info->bmiHeader.biHeight);
585 width = info->bmiHeader.biWidth;
586
587 if ( !GetDIBits(hdc, mask,
588 0, height,
589 data,
590 info,
591 DIB_RGB_COLORS) )
592 { Cprintf("Mask: GetDIBits() failed to get mask bits\n");
593 return FALSE;
594 }
595
596 inlensl = ROUND(width, sizeof(DWORD)*8)/8;
597 outlensl = (width+7)/8;
598
599 for(y=0; y<height; y++)
600 { GSAMPLE *in = data + y * inlensl;
601 GSAMPLE *out = data + y * outlensl;
602
603 memcpy(out, in, outlensl);
604
605 #if 0
606 { int x;
607 int m = 0x80;
608 GSAMPLE *p = data + y * outlensl;
609
610 for(x=0; x<info->bmiHeader.biWidth; x++)
611 { Cprintf("%c", p[0]&m ? '.' : '*');
612 m>>=1;
613 if ( !m )
614 { m = 0x80;
615 p++;
616 }
617 }
618 Cprintf("\n");
619 }
620 #endif
621 }
622
623 out:
624 DeleteDC(hdc);
625
626 return data;
627 }
628
629
630
631 #endif /*HAVE_LIBJPEG*/
632