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