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