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