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