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