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