rnd-20020324-2-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   char *error = "Image_to_Pixmap(): %s";
179
180   if (image->type == IMAGETYPE_TRUECOLOR && depth == 8)
181   {
182     SetError(error, "cannot handle true-color images on 8-bit display");
183     return NULL;
184   }
185
186   if (!global_cmap)
187   {
188     if (visual == DefaultVisual(display, screen))
189       global_cmap = DefaultColormap(display, screen);
190     else
191     {
192       global_cmap = XCreateColormap(display, RootWindow(display, screen),
193                                     visual, AllocNone);
194       private_cmap = TRUE;
195     }
196   }
197
198   xcolor.flags = DoRed | DoGreen | DoBlue;
199   redvalue = greenvalue = bluevalue = NULL;
200   ximageinfo = checked_malloc(sizeof(XImageInfo));
201   ximageinfo->display = display;
202   ximageinfo->depth = depth;
203
204   switch (visual->class)
205   {
206     case TrueColor:
207     case DirectColor:
208     {
209       Pixel pixval;
210       unsigned int redcolors, greencolors, bluecolors;
211       unsigned int redstep, greenstep, bluestep;
212       unsigned int redbottom, greenbottom, bluebottom;
213       unsigned int redtop, greentop, bluetop;
214
215       redvalue = (Pixel *)checked_malloc(sizeof(Pixel) * 256);
216       greenvalue = (Pixel *)checked_malloc(sizeof(Pixel) * 256);
217       bluevalue = (Pixel *)checked_malloc(sizeof(Pixel) * 256);
218
219       ximageinfo->cmap = global_cmap;
220
221       retry_direct: /* tag we hit if a DirectColor allocation fails on
222                      * default colormap */
223
224       /* calculate number of distinct colors in each band */
225
226       redcolors = greencolors = bluecolors = 1;
227       for (pixval=1; pixval; pixval <<= 1)
228       {
229         if (pixval & visual->red_mask)
230           redcolors <<= 1;
231         if (pixval & visual->green_mask)
232           greencolors <<= 1;
233         if (pixval & visual->blue_mask)
234           bluecolors <<= 1;
235       }
236       
237       /* consistency check */
238       if (redcolors > visual->map_entries ||
239           greencolors > visual->map_entries ||
240           bluecolors > visual->map_entries)
241         Error(ERR_WARN, "inconsistency in color information");
242
243       redstep = 256 / redcolors;
244       greenstep = 256 / greencolors;
245       bluestep = 256 / bluecolors;
246       redbottom = greenbottom = bluebottom = 0;
247       redtop = greentop = bluetop = 0;
248       for (a=0; a<visual->map_entries; a++)
249       {
250         if (redbottom < 256)
251           redtop = redbottom + redstep;
252         if (greenbottom < 256)
253           greentop = greenbottom + greenstep;
254         if (bluebottom < 256)
255           bluetop = bluebottom + bluestep;
256
257         xcolor.red = (redtop - 1) << 8;
258         xcolor.green = (greentop - 1) << 8;
259         xcolor.blue = (bluetop - 1) << 8;
260         if (!XAllocColor(display, ximageinfo->cmap, &xcolor))
261         {
262           /* if an allocation fails for a DirectColor default visual then
263              we should create a private colormap and try again. */
264
265           if ((visual->class == DirectColor) &&
266               (visual == DefaultVisual(display, screen)))
267           {
268             global_cmap = XCopyColormapAndFree(display, global_cmap);
269             ximageinfo->cmap = global_cmap;
270             private_cmap = TRUE;
271
272             goto retry_direct;
273           }
274
275           /* something completely unexpected happened */
276
277           fprintf(stderr, "Image_to_Pixmap: XAllocColor failed on a TrueColor/Directcolor visual\n");
278           free(redvalue);
279           free(greenvalue);
280           free(bluevalue);
281           free(ximageinfo);
282           return NULL;
283         }
284
285         /* fill in pixel values for each band at this intensity */
286
287         while ((redbottom < 256) && (redbottom < redtop))
288           redvalue[redbottom++] = xcolor.pixel & visual->red_mask;
289         while ((greenbottom < 256) && (greenbottom < greentop))
290           greenvalue[greenbottom++] = xcolor.pixel & visual->green_mask;
291         while ((bluebottom < 256) && (bluebottom < bluetop))
292           bluevalue[bluebottom++] = xcolor.pixel & visual->blue_mask;
293       }
294       break;
295     }
296
297     case PseudoColor:
298
299       ximageinfo->cmap = global_cmap;
300
301       for (a=0; a<MAX_COLORS; a++)
302       {
303         XColor xcolor2;
304         unsigned short mask;
305         int color_found;
306         int i;
307
308         if (!image->rgb.color_used[a])
309           continue;
310
311         xcolor.red = *(image->rgb.red + a);
312         xcolor.green = *(image->rgb.green + a);
313         xcolor.blue = *(image->rgb.blue + a);
314   
315         /* look if this color already exists in our colormap */
316         if (!XAllocColor(display, ximageinfo->cmap, &xcolor))
317         {
318           if (!private_cmap)
319           {
320             if (options.verbose)
321               Error(ERR_RETURN, "switching to private colormap");
322
323             /* we just filled up the default colormap -- get a private one
324                which contains all already allocated colors */
325
326             global_cmap = XCopyColormapAndFree(display, global_cmap);
327             ximageinfo->cmap = global_cmap;
328             private_cmap = TRUE;
329
330             /* allocate the rest of the color cells read/write */
331             global_cmap_index =
332               (Pixel *)checked_malloc(sizeof(Pixel) * NOFLASH_COLORS);
333             for (i=0; i<NOFLASH_COLORS; i++)
334               if (!XAllocColorCells(display, global_cmap, FALSE, NULL, 0,
335                                     global_cmap_index + i, 1))
336                 break;
337             num_cmap_entries = free_cmap_entries = i;
338
339             /*
340             printf("We've got %d free colormap entries.\n", free_cmap_entries);
341             */
342
343             /* to minimize colormap flashing, copy default colors and try
344                to keep them as near as possible to the old values */
345
346             for(i=0; i<num_cmap_entries; i++)
347             {
348               xcolor2.pixel = *(global_cmap_index + i);
349               XQueryColor(display, DefaultColormap(display, screen), &xcolor2);
350               XStoreColor(display, global_cmap, &xcolor2);
351               xcolor_private[xcolor2.pixel] = xcolor2;
352               colorcell_used[xcolor2.pixel] = FALSE;
353             }
354
355             /* now we have the default colormap private: all colors we
356                successfully allocated so far are read-only, which is okay,
357                because we don't want to change them anymore -- if we need
358                an existing color again, we get it by XAllocColor; all other
359                colors are read/write and we can set them by XStoreColor,
360                but we will try to overwrite those color cells with our new
361                color which are as close as possible to our new color */
362           }
363
364           /* look for an existing default color close the one we want */
365
366           mask = 0xf000;
367           color_found = FALSE;
368
369           while (!color_found)
370           {
371             for (i=num_cmap_entries-1; i>=0; i--)
372             {
373               xcolor2.pixel = *(global_cmap_index + i);
374               xcolor2 = xcolor_private[xcolor2.pixel];
375
376               if (colorcell_used[xcolor2.pixel])
377                 continue;
378
379               if ((xcolor.red & mask) == (xcolor2.red & mask) &&
380                   (xcolor.green & mask) == (xcolor2.green & mask) &&
381                   (xcolor.blue & mask) == (xcolor2.blue & mask))
382               {
383                 /*
384                 printf("replacing color cell %ld with a close color\n",
385                        xcolor2.pixel);
386                        */
387                 color_found = TRUE;
388                 break;
389               }
390             }
391
392             if (mask == 0x0000)
393               break;
394
395             mask = (mask << 1) & 0xffff;
396           }
397
398           if (!color_found)             /* no more free color cells */
399           {
400             SetError(error, "cannot allocate enough color cells");
401             return NULL;
402           }
403
404           xcolor.pixel = xcolor2.pixel;
405           xcolor_private[xcolor.pixel] = xcolor;
406           colorcell_used[xcolor.pixel] = TRUE;
407           XStoreColor(display, ximageinfo->cmap, &xcolor);
408           free_cmap_entries--;
409         }
410
411         *(ximageinfo->index + a) = xcolor.pixel;
412       }
413
414       /*
415       printf("still %d free colormap entries\n", free_cmap_entries);
416       */
417
418       ximageinfo->no = a;       /* number of pixels allocated for this image */
419       break;
420   
421     default:
422       Error(ERR_RETURN,"DirectColor, TrueColor or PseudoColor display needed");
423       SetError(error, "display class not supported");
424       return NULL;
425   }
426
427 #if DEBUG_TIMING
428   debug_print_timestamp(2, "   ALLOCATING IMAGE COLORS:   ");
429 #endif
430
431   /* create XImage from internal image structure and convert it to Pixmap */
432
433   display_bits_per_pixel = bitsPerPixelAtDepth(display, screen, depth);
434   display_bytes_per_pixel = (display_bits_per_pixel + 7) / 8;
435
436   ximage = XCreateImage(display, visual, depth, ZPixmap, 0,
437                         NULL, image->width, image->height,
438                         8, image->width * display_bytes_per_pixel);
439   ximage->data =
440     checked_malloc(image->width * image->height * display_bytes_per_pixel);
441   ximage->byte_order = MSBFirst;
442
443   src_ptr = image->data;
444   dst_ptr = (byte *)ximage->data;
445
446   switch (visual->class)
447   {
448     case DirectColor:
449     case TrueColor:
450     {
451       Pixel pixval;
452
453       switch (image->type)
454       {
455         case IMAGETYPE_RGB:
456         {
457           for (y=0; y<image->height; y++)               /* general case */
458           {
459             for (x=0; x<image->width; x++)
460             {
461               pixval = *src_ptr++;
462               pixval =
463                 redvalue[image->rgb.red[pixval] >> 8] |
464                 greenvalue[image->rgb.green[pixval] >> 8] |
465                 bluevalue[image->rgb.blue[pixval] >> 8];
466               value_to_memory(pixval, dst_ptr, display_bytes_per_pixel);
467               dst_ptr += display_bytes_per_pixel;
468             }
469           }
470           break;
471         }
472
473         case IMAGETYPE_TRUECOLOR:
474         {
475           for (y=0; y<image->height; y++)               /* general case */
476           {
477             for (x=0; x<image->width; x++)
478             {
479               pixval = memory_to_value(src_ptr, image->bytes_per_pixel);
480               pixval =
481                 redvalue[TRUECOLOR_RED(pixval)] |
482                 greenvalue[TRUECOLOR_GREEN(pixval)] |
483                 bluevalue[TRUECOLOR_BLUE(pixval)];
484               value_to_memory(pixval, dst_ptr, display_bytes_per_pixel);
485               src_ptr += image->bytes_per_pixel;
486               dst_ptr += display_bytes_per_pixel;
487             }
488           }
489           break;
490         }
491
492         default:
493           Error(ERR_RETURN, "RGB or TrueColor image needed");
494           SetError(error, "image type not supported");
495           return NULL;
496       }
497       break;
498     }
499
500     case PseudoColor:
501     {
502       if (display_bytes_per_pixel == 1)         /* special case */
503       {
504         for (y=0; y<image->height; y++)
505           for (x=0; x<image->width; x++)
506             *dst_ptr++ = ximageinfo->index[c + *src_ptr++];
507       }
508       else                                      /* general case */
509       {
510         for (y=0; y<image->height; y++)
511         {
512           for (x=0; x<image->width; x++)
513           {
514             value_to_memory(ximageinfo->index[c + *src_ptr++],
515                             dst_ptr, display_bytes_per_pixel);
516             dst_ptr += display_bytes_per_pixel;
517           }
518         }
519       }
520       break;
521     }
522
523     default:
524       Error(ERR_RETURN,"DirectColor, TrueColor or PseudoColor display needed");
525       SetError(error, "display class not supported");
526       return NULL;
527   }
528
529   if (redvalue)
530   {
531     free((byte *)redvalue);
532     free((byte *)greenvalue);
533     free((byte *)bluevalue);
534   }
535
536 #if DEBUG_TIMING
537   debug_print_timestamp(2, "   CONVERTING IMAGE TO XIMAGE:");
538 #endif
539
540   ximageinfo->pixmap = XCreatePixmap(display, window,
541                                      ximage->width, ximage->height,
542                                      ximageinfo->depth);
543
544   XPutImage(ximageinfo->display, ximageinfo->pixmap, gc,
545             ximage, 0, 0, 0, 0, ximage->width, ximage->height);
546
547   free(ximage->data);
548   ximage->data = NULL;
549   XDestroyImage(ximage);
550
551   return(ximageinfo);
552 }
553
554 void freeXImage(Image *image, XImageInfo *ximageinfo)
555 {
556   if (ximageinfo->index != NULL && ximageinfo->no > 0)
557     XFreeColors(ximageinfo->display, ximageinfo->cmap, ximageinfo->index,
558                 ximageinfo->no, 0);
559   /* this       ^^^^^^^^^^^^^^ is wrong, because the used color cells
560    * are somewhere between 0 and MAX_COLORS; there are indeed 'ximageinfo->no'
561    * used color cells, but they are not at array position 0 - 'ximageinfo->no'
562    */
563
564   free(ximageinfo);
565 }
566
567 int Read_PCX_to_Pixmap(Display *display, Window window, GC gc, char *filename,
568                        Pixmap *pixmap, Pixmap *pixmap_mask)
569 {
570   Image *image;
571   XImageInfo *ximageinfo;
572   int screen;
573   Visual *visual;
574   int depth;
575
576 #if DEBUG_TIMING
577   debug_print_timestamp(2, NULL);       /* initialize timestamp function */
578 #endif
579
580   /* read the graphic file in PCX format to image structure */
581   if ((image = Read_PCX_to_Image(filename)) == NULL)
582     return errno_pcx;
583
584 #if DEBUG_TIMING
585   printf("%s:\n", filename);
586   debug_print_timestamp(2, "   READING PCX FILE TO IMAGE: ");
587 #endif
588
589   screen = DefaultScreen(display);
590   visual = DefaultVisual(display, screen);
591   depth = DefaultDepth(display, screen);
592
593   /* convert image structure to X11 Pixmap */
594   if (!(ximageinfo = Image_to_Pixmap(display, screen, visual,
595                                      window, gc, depth, image)))
596     return PCX_OtherError;
597
598   /* if a private colormap has been created, install it */
599   if (ximageinfo->cmap != DefaultColormap(display, screen))
600     XSetWindowColormap(display, window, ximageinfo->cmap);
601
602 #if DEBUG_TIMING
603   debug_print_timestamp(2, "   CONVERTING IMAGE TO PIXMAP:");
604 #endif
605
606   /* create clip mask for the image */
607   ximageinfo->pixmap_mask = Image_to_Mask(image, display, window);
608
609 #if DEBUG_TIMING
610   debug_print_timestamp(2, "   CONVERTING IMAGE TO MASK:  ");
611 #endif
612
613   *pixmap = ximageinfo->pixmap;
614   *pixmap_mask = ximageinfo->pixmap_mask;
615
616   return PCX_Success;
617 }
618
619 #endif  /* PLATFORM_UNIX */
620 #endif  /* TARGET_X11 */