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