rnd-20001202-2-src
[rocksndiamonds.git] / src / libgame / image.c
1 /***********************************************************
2 *  Rocks'n'Diamonds -- McDuffin Strikes Back!              *
3 *----------------------------------------------------------*
4 *  (c) 1995-98 Artsoft Entertainment                       *
5 *              Holger Schemel                              *
6 *              Oststrasse 11a                              *
7 *              33604 Bielefeld                             *
8 *              phone: ++49 +521 290471                     *
9 *              email: aeglos@valinor.owl.de                *
10 *----------------------------------------------------------*
11 *  image.c                                                 *
12 ***********************************************************/
13
14 #include "platform.h"
15
16 #if defined(TARGET_X11)
17
18 #include "image.h"
19 #include "pcx.h"
20 #include "misc.h"
21
22 /* for MS-DOS/Allegro, exclude all except newImage() and freeImage() */
23
24 Image *newImage(unsigned int width, unsigned int height, unsigned int depth)
25 {
26   Image *image;
27   const unsigned int bytes_per_pixel = 1;
28   int i;
29
30   if (depth > 8)
31     Error(ERR_EXIT, "images with more than 256 colors are not supported");
32
33   depth = 8;
34   image = checked_malloc(sizeof(Image));
35   image->data = checked_malloc(width * height * bytes_per_pixel);
36   image->width = width;
37   image->height = height;
38   image->depth = depth;
39   image->rgb.used = 0;
40   for (i=0; i<MAX_COLORS; i++)
41     image->rgb.color_used[i] = FALSE;
42
43   return image;
44 }
45
46 void freeImage(Image *image)
47 {
48   free(image->data);
49   free(image);
50 }
51
52 #if defined(PLATFORM_UNIX)
53
54 /* extra colors to try allocating in private color maps to minimize flashing */
55 #define NOFLASH_COLORS 256
56
57 /* architecture independent value-to-memory conversion
58    note: the internal format is big endian */
59
60 #define value_to_memory(value, ptr, length) (                           \
61 (length) == 1 ? (*( (byte *)(ptr)   ) = ( value     ) ) :               \
62 (length) == 2 ? (*( (byte *)(ptr)   ) = (((unsigned long)(value))>> 8), \
63                  *(((byte *)(ptr))+1) = ( value     ) ) :               \
64 (length) == 3 ? (*( (byte *)(ptr)   ) = (((unsigned long)(value))>>16), \
65                  *(((byte *)(ptr))+1) = (((unsigned long)(value))>> 8), \
66                  *(((byte *)(ptr))+2) = ( value     ) ) :               \
67                 (*( (byte *)(ptr)   ) = (((unsigned long)(value))>>24), \
68                  *(((byte *)(ptr))+1) = (((unsigned long)(value))>>16), \
69                  *(((byte *)(ptr))+2) = (((unsigned long)(value))>> 8), \
70                  *(((byte *)(ptr))+3) = ( value     ) ))
71
72 static Pixmap Image_to_Mask(Image *image, Display *display, Window window)
73 {
74   byte *src_ptr, *dst_ptr, *dst_ptr2;
75   unsigned int bytes_per_row;
76   unsigned int x, y;
77   byte bitmask;
78   byte *mask_data;
79   Pixmap mask_pixmap;
80
81   bytes_per_row = (image->width + 7) / 8;
82   mask_data = checked_calloc(bytes_per_row * image->height);
83
84   src_ptr = image->data;
85   dst_ptr = mask_data;
86
87   /* create bitmap data which can be used by 'XCreateBitmapFromData()'
88    * directly to create a pixmap of depth 1 for use as a clip mask for
89    * the corresponding image pixmap
90    */
91
92   for (y=0; y<image->height; y++)
93   {
94     bitmask = 0x01;             /* start with leftmost bit in the byte     */
95     dst_ptr2 = dst_ptr;         /* start with leftmost byte in the row     */
96
97     for (x=0; x<image->width; x++)
98     {
99       if (*src_ptr++)           /* source pixel solid? (pixel index != 0)  */
100         *dst_ptr2 |= bitmask;   /* then write a bit into the image mask    */
101
102       if ((bitmask <<= 1) == 0) /* bit at rightmost byte position reached? */
103       {
104         bitmask = 0x01;         /* start again with leftmost bit position  */
105         dst_ptr2++;             /* continue with next byte in image mask   */
106       }
107     }
108
109     dst_ptr += bytes_per_row;   /* continue with leftmost byte of next row */
110   }
111
112   mask_pixmap = XCreateBitmapFromData(display, window, (char *)mask_data,
113                                       image->width, image->height);
114   free(mask_data);
115
116   return mask_pixmap;
117 }
118
119 static int bitsPerPixelAtDepth(Display *display, int screen, int depth)
120 {
121   XPixmapFormatValues *pixmap_format;
122   int i, num_pixmap_formats, bits_per_pixel = -1;
123
124   /* get Pixmap formats supported by the X server */
125   pixmap_format = XListPixmapFormats(display, &num_pixmap_formats);
126
127   /* find format that matches the given depth */
128   for (i=0; i<num_pixmap_formats; i++)
129     if (pixmap_format[i].depth == depth)
130       bits_per_pixel = pixmap_format[i].bits_per_pixel;
131
132   XFree(pixmap_format);
133
134   if (bits_per_pixel == -1)
135     Error(ERR_EXIT, "cannot find pixmap format for depth %d", depth);
136
137   return bits_per_pixel;
138 }
139
140 XImageInfo *Image_to_Pixmap(Display *display, int screen, Visual *visual,
141                             Window window, GC gc, int depth, Image *image)
142 {
143   static XColor xcolor_private[NOFLASH_COLORS];
144   static int colorcell_used[NOFLASH_COLORS];
145   static Colormap global_cmap = 0;
146   static Pixel *global_cmap_index;
147   static int num_cmap_entries, free_cmap_entries;
148   static boolean private_cmap = FALSE;
149   Pixel *redvalue, *greenvalue, *bluevalue;
150   unsigned int a, c = 0, x, y, bytes_per_pixel, bits_per_pixel;
151   XColor xcolor;
152   XImage *ximage;
153   XImageInfo *ximageinfo;
154   byte *src_ptr, *dst_ptr;
155
156   if (!global_cmap)
157   {
158     if (visual == DefaultVisual(display, screen))
159       global_cmap = DefaultColormap(display, screen);
160     else
161     {
162       global_cmap = XCreateColormap(display, RootWindow(display, screen),
163                                     visual, AllocNone);
164       private_cmap = TRUE;
165     }
166   }
167
168   xcolor.flags = DoRed | DoGreen | DoBlue;
169   redvalue = greenvalue = bluevalue = NULL;
170   ximageinfo = checked_malloc(sizeof(XImageInfo));
171   ximageinfo->display = display;
172   ximageinfo->depth = depth;
173
174   switch (visual->class)
175   {
176     case TrueColor:
177     case DirectColor:
178     {
179       Pixel pixval;
180       unsigned int redcolors, greencolors, bluecolors;
181       unsigned int redstep, greenstep, bluestep;
182       unsigned int redbottom, greenbottom, bluebottom;
183       unsigned int redtop, greentop, bluetop;
184
185       redvalue = (Pixel *)checked_malloc(sizeof(Pixel) * 256);
186       greenvalue = (Pixel *)checked_malloc(sizeof(Pixel) * 256);
187       bluevalue = (Pixel *)checked_malloc(sizeof(Pixel) * 256);
188
189       ximageinfo->cmap = global_cmap;
190
191       retry_direct: /* tag we hit if a DirectColor allocation fails on
192                      * default colormap */
193
194       /* calculate number of distinct colors in each band */
195
196       redcolors = greencolors = bluecolors = 1;
197       for (pixval=1; pixval; pixval <<= 1)
198       {
199         if (pixval & visual->red_mask)
200           redcolors <<= 1;
201         if (pixval & visual->green_mask)
202           greencolors <<= 1;
203         if (pixval & visual->blue_mask)
204           bluecolors <<= 1;
205       }
206       
207       /* consistency check */
208       if (redcolors > visual->map_entries ||
209           greencolors > visual->map_entries ||
210           bluecolors > visual->map_entries)
211         Error(ERR_WARN, "inconsistency in color information");
212
213       redstep = 256 / redcolors;
214       greenstep = 256 / greencolors;
215       bluestep = 256 / bluecolors;
216       redbottom = greenbottom = bluebottom = 0;
217       redtop = greentop = bluetop = 0;
218       for (a=0; a<visual->map_entries; a++)
219       {
220         if (redbottom < 256)
221           redtop = redbottom + redstep;
222         if (greenbottom < 256)
223           greentop = greenbottom + greenstep;
224         if (bluebottom < 256)
225           bluetop = bluebottom + bluestep;
226
227         xcolor.red = (redtop - 1) << 8;
228         xcolor.green = (greentop - 1) << 8;
229         xcolor.blue = (bluetop - 1) << 8;
230         if (!XAllocColor(display, ximageinfo->cmap, &xcolor))
231         {
232           /* if an allocation fails for a DirectColor default visual then
233              we should create a private colormap and try again. */
234
235           if ((visual->class == DirectColor) &&
236               (visual == DefaultVisual(display, screen)))
237           {
238             global_cmap = XCopyColormapAndFree(display, global_cmap);
239             ximageinfo->cmap = global_cmap;
240             private_cmap = TRUE;
241
242             goto retry_direct;
243           }
244
245           /* something completely unexpected happened */
246
247           fprintf(stderr, "imageToXImage: XAllocColor failed on a TrueColor/Directcolor visual\n");
248           free(redvalue);
249           free(greenvalue);
250           free(bluevalue);
251           free(ximageinfo);
252           return NULL;
253         }
254
255         /* fill in pixel values for each band at this intensity */
256
257         while ((redbottom < 256) && (redbottom < redtop))
258           redvalue[redbottom++] = xcolor.pixel & visual->red_mask;
259         while ((greenbottom < 256) && (greenbottom < greentop))
260           greenvalue[greenbottom++] = xcolor.pixel & visual->green_mask;
261         while ((bluebottom < 256) && (bluebottom < bluetop))
262           bluevalue[bluebottom++] = xcolor.pixel & visual->blue_mask;
263       }
264       break;
265     }
266
267     case PseudoColor:
268
269       ximageinfo->cmap = global_cmap;
270
271       for (a=0; a<MAX_COLORS; a++)
272       {
273         XColor xcolor2;
274         unsigned short mask;
275         int color_found;
276         int i;
277
278         if (!image->rgb.color_used[a])
279           continue;
280
281         xcolor.red = *(image->rgb.red + a);
282         xcolor.green = *(image->rgb.green + a);
283         xcolor.blue = *(image->rgb.blue + a);
284   
285         /* look if this color already exists in our colormap */
286         if (!XAllocColor(display, ximageinfo->cmap, &xcolor))
287         {
288           if (!private_cmap)
289           {
290             if (options.verbose)
291               Error(ERR_RETURN, "switching to private colormap");
292
293             /* we just filled up the default colormap -- get a private one
294                which contains all already allocated colors */
295
296             global_cmap = XCopyColormapAndFree(display, global_cmap);
297             ximageinfo->cmap = global_cmap;
298             private_cmap = TRUE;
299
300             /* allocate the rest of the color cells read/write */
301             global_cmap_index =
302               (Pixel *)checked_malloc(sizeof(Pixel) * NOFLASH_COLORS);
303             for (i=0; i<NOFLASH_COLORS; i++)
304               if (!XAllocColorCells(display, global_cmap, FALSE, NULL, 0,
305                                     global_cmap_index + i, 1))
306                 break;
307             num_cmap_entries = free_cmap_entries = i;
308
309             /*
310             printf("We've got %d free colormap entries.\n", free_cmap_entries);
311             */
312
313             /* to minimize colormap flashing, copy default colors and try
314                to keep them as near as possible to the old values */
315
316             for(i=0; i<num_cmap_entries; i++)
317             {
318               xcolor2.pixel = *(global_cmap_index + i);
319               XQueryColor(display, DefaultColormap(display, screen), &xcolor2);
320               XStoreColor(display, global_cmap, &xcolor2);
321               xcolor_private[xcolor2.pixel] = xcolor2;
322               colorcell_used[xcolor2.pixel] = FALSE;
323             }
324
325             /* now we have the default colormap private: all colors we
326                successfully allocated so far are read-only, which is okay,
327                because we don't want to change them anymore -- if we need
328                an existing color again, we get it by XAllocColor; all other
329                colors are read/write and we can set them by XStoreColor,
330                but we will try to overwrite those color cells with our new
331                color which are as close as possible to our new color */
332           }
333
334           /* look for an existing default color close the one we want */
335
336           mask = 0xf000;
337           color_found = FALSE;
338
339           while (!color_found)
340           {
341             for (i=num_cmap_entries-1; i>=0; i--)
342             {
343               xcolor2.pixel = *(global_cmap_index + i);
344               xcolor2 = xcolor_private[xcolor2.pixel];
345
346               if (colorcell_used[xcolor2.pixel])
347                 continue;
348
349               if ((xcolor.red & mask) == (xcolor2.red & mask) &&
350                   (xcolor.green & mask) == (xcolor2.green & mask) &&
351                   (xcolor.blue & mask) == (xcolor2.blue & mask))
352               {
353                 /*
354                 printf("replacing color cell %ld with a close color\n",
355                        xcolor2.pixel);
356                        */
357                 color_found = TRUE;
358                 break;
359               }
360             }
361
362             if (mask == 0x0000)
363               break;
364
365             mask = (mask << 1) & 0xffff;
366           }
367
368           if (!color_found)             /* no more free color cells */
369             Error(ERR_EXIT, "cannot allocate enough color cells");
370
371           xcolor.pixel = xcolor2.pixel;
372           xcolor_private[xcolor.pixel] = xcolor;
373           colorcell_used[xcolor.pixel] = TRUE;
374           XStoreColor(display, ximageinfo->cmap, &xcolor);
375           free_cmap_entries--;
376         }
377
378         *(ximageinfo->index + a) = xcolor.pixel;
379       }
380
381       /*
382       printf("still %d free colormap entries\n", free_cmap_entries);
383       */
384
385       ximageinfo->no = a;       /* number of pixels allocated for this image */
386       break;
387   
388     default:
389       Error(ERR_RETURN, "display class not supported");
390       Error(ERR_EXIT, "DirectColor, TrueColor or PseudoColor display needed");
391       break;
392   }
393
394 #if DEBUG_TIMING
395   debug_print_timestamp(2, "   ALLOCATING IMAGE COLORS:   ");
396 #endif
397
398   /* create XImage from internal image structure and convert it to Pixmap */
399
400   bits_per_pixel = bitsPerPixelAtDepth(display, screen, depth);
401   bytes_per_pixel = (bits_per_pixel + 7) / 8;
402
403   ximage = XCreateImage(display, visual, depth, ZPixmap, 0,
404                         NULL, image->width, image->height,
405                         8, image->width * bytes_per_pixel);
406   ximage->data =
407     checked_malloc(image->width * image->height * bytes_per_pixel);
408   ximage->byte_order = MSBFirst;
409
410   src_ptr = image->data;
411   dst_ptr = (byte *)ximage->data;
412
413   switch (visual->class)
414   {
415     case DirectColor:
416     case TrueColor:
417     {
418       Pixel pixval;
419
420       for (y=0; y<image->height; y++)           /* general case */
421       {
422         for (x=0; x<image->width; x++)
423         {
424           pixval = *src_ptr++;
425           pixval =
426             redvalue[image->rgb.red[pixval] >> 8] |
427             greenvalue[image->rgb.green[pixval] >> 8] |
428             bluevalue[image->rgb.blue[pixval] >> 8];
429           value_to_memory(pixval, dst_ptr, bytes_per_pixel);
430           dst_ptr += bytes_per_pixel;
431         }
432       }
433       break;
434     }
435
436     case PseudoColor:
437     {
438       if (bytes_per_pixel == 1)                 /* (common) special case */
439       {
440         for (y=0; y<image->height; y++)
441           for (x=0; x<image->width; x++)
442             *dst_ptr++ = ximageinfo->index[c + *src_ptr++];
443       }
444       else                                      /* general case */
445       {
446         for (y=0; y<image->height; y++)
447         {
448           for (x=0; x<image->width; x++)
449           {
450             value_to_memory(ximageinfo->index[c + *src_ptr++],
451                             dst_ptr, bytes_per_pixel);
452             dst_ptr += bytes_per_pixel;
453           }
454         }
455       }
456       break;
457     }
458
459     default:
460       Error(ERR_RETURN, "display class not supported");
461       Error(ERR_EXIT, "DirectColor, TrueColor or PseudoColor display needed");
462       break;
463   }
464
465   if (redvalue)
466   {
467     free((byte *)redvalue);
468     free((byte *)greenvalue);
469     free((byte *)bluevalue);
470   }
471
472 #if DEBUG_TIMING
473   debug_print_timestamp(2, "   CONVERTING IMAGE TO XIMAGE:");
474 #endif
475
476   ximageinfo->pixmap = XCreatePixmap(display, window,
477                                      ximage->width, ximage->height,
478                                      ximageinfo->depth);
479
480   XPutImage(ximageinfo->display, ximageinfo->pixmap, gc,
481             ximage, 0, 0, 0, 0, ximage->width, ximage->height);
482
483   free(ximage->data);
484   ximage->data = NULL;
485   XDestroyImage(ximage);
486
487   return(ximageinfo);
488 }
489
490 void freeXImage(Image *image, XImageInfo *ximageinfo)
491 {
492   if (ximageinfo->index != NULL && ximageinfo->no > 0)
493     XFreeColors(ximageinfo->display, ximageinfo->cmap, ximageinfo->index,
494                 ximageinfo->no, 0);
495   /* this       ^^^^^^^^^^^^^^ is wrong, because the used color cells
496    * are somewhere between 0 and MAX_COLORS; there are indeed 'ximageinfo->no'
497    * used color cells, but they are not at array position 0 - 'ximageinfo->no'
498    */
499
500   free(ximageinfo);
501 }
502
503 int Read_PCX_to_Pixmap(Display *display, Window window, GC gc, char *filename,
504                        Pixmap *pixmap, Pixmap *pixmap_mask)
505 {
506   Image *image;
507   XImageInfo *ximageinfo;
508   int screen;
509   Visual *visual;
510   int depth;
511
512 #if DEBUG_TIMING
513   debug_print_timestamp(2, NULL);       /* initialize timestamp function */
514 #endif
515
516   /* read the graphic file in PCX format to image structure */
517   if ((image = Read_PCX_to_Image(filename)) == NULL)
518     return errno_pcx;
519
520 #if DEBUG_TIMING
521   printf("%s:\n", filename);
522   debug_print_timestamp(2, "   READING PCX FILE TO IMAGE: ");
523 #endif
524
525   screen = DefaultScreen(display);
526   visual = DefaultVisual(display, screen);
527   depth = DefaultDepth(display, screen);
528
529   /* convert image structure to X11 Pixmap */
530   if (!(ximageinfo = Image_to_Pixmap(display, screen, visual,
531                                      window, gc, depth, image)))
532     Error(ERR_EXIT, "cannot convert Image to Pixmap");
533
534   /* if a private colormap has been created, install it */
535   if (ximageinfo->cmap != DefaultColormap(display, screen))
536     XSetWindowColormap(display, window, ximageinfo->cmap);
537
538 #if DEBUG_TIMING
539   debug_print_timestamp(2, "   CONVERTING IMAGE TO PIXMAP:");
540 #endif
541
542   /* create clip mask for the image */
543   ximageinfo->pixmap_mask = Image_to_Mask(image, display, window);
544
545 #if DEBUG_TIMING
546   debug_print_timestamp(2, "   CONVERTING IMAGE TO MASK:  ");
547 #endif
548
549   *pixmap = ximageinfo->pixmap;
550   *pixmap_mask = ximageinfo->pixmap_mask;
551
552   return PCX_Success;
553 }
554
555 #endif  /* PLATFORM_UNIX */
556 #endif  /* TARGET_X11 */