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