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