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