1 /*
2  *    Example program for the Allegro library, by Shawn Hargreaves.
3  *
4  *    This program shows one way to implement colored lighting effects
5  *    in a hicolor video mode. Warning: it is not for the faint of heart!
6  *    This is by no means the simplest or easiest to understand method,
7  *    I just thought it was a cool concept that would be worth
8  *    demonstrating.
9  *
10  *    The basic approach is to select a 15 or 16 bit screen mode, but
11  *    then draw onto 24 bit memory bitmaps. Since we only need the bottom
12  *    5 bits of each 8 bit color in order to store 15 bit data within a
13  *    24 bit location, we can fit a light level into the top 3 bits.
14  *    The tricky bit is that these aren't actually 24 bit images at all:
15  *    they are implemented as 8 bit memory bitmaps, and we just store the
16  *    red level in one pixel, green in the next, and blue in the next,
17  *    making the total image be three times wider than we really wanted.
18  *    This allows us to use all the normal 256 color graphics routines
19  *    for drawing onto our memory surfaces, most importantly the lookup
20  *    table translucency, which can be used to combine the low 5 bits
21  *    of color and the top 3 bits of light in a single drawing operation.
22  *    Some trickery is needed to load 24 bit data into this fake 8 bit
23  *    format, and of course it needs a custom routine to convert the
24  *    resulting image while copying it across to the hardware screen.
25  *
26  *    This program chugs slightly on my p133, but not significantly
27  *    worse than any double buffering in what amounts to a 1920x640,
28  *    256 color resolution. The light blending doesn't seem to slow
29  *    it down too badly, so I think this technique would be quite usable
30  *    on faster machines and in lower resolution hicolor modes. The
31  *    biggest problem is that although you keep the full 15 bit color
32  *    resolution, you only get 3 bits of light, ie. 8 light levels.
33  *    You can do some nice colored light patches, but smooth gradients
34  *    aren't going to work too well :-)
35  */
36 
37 
38 #include <allegro.h>
39 
40 
41 
42 /* double buffer bitmap */
43 BITMAP *buffer;
44 
45 
46 /* the two moving bitmap images */
47 BITMAP *image1;
48 BITMAP *image2;
49 
50 
51 /* the light map, drawn wherever the mouse pointer is */
52 BITMAP *lightmap;
53 
54 
55 /* give images some light of their own. Make this zero for total black */
56 #define AMBIENT_LIGHT      0x20
57 
58 
59 
60 /* converts a bitmap from the normal Allegro format into our magic layout */
get_magic_bitmap_format(BITMAP * orig,PALETTE pal)61 BITMAP *get_magic_bitmap_format(BITMAP *orig, PALETTE pal)
62 {
63    BITMAP *bmp;
64    int c, r, g, b;
65    int x, y;
66    int bpp;
67 
68    /* create an 8 bpp image, three times as wide as the original */
69    bmp = create_bitmap_ex(8, orig->w*3, orig->h);
70 
71    /* store info about the original bitmap format */
72    bpp = bitmap_color_depth(orig);
73    select_palette(pal);
74 
75    /* convert the data */
76    for (y=0; y<orig->h; y++) {
77       for (x=0; x<orig->w; x++) {
78 	 c = getpixel(orig, x, y);
79 
80 	 r = getr_depth(bpp, c);
81 	 g = getg_depth(bpp, c);
82 	 b = getb_depth(bpp, c);
83 
84 	 bmp->line[y][x*3] = r*31/255 | AMBIENT_LIGHT;
85 	 bmp->line[y][x*3+1] = g*31/255 | AMBIENT_LIGHT;
86 	 bmp->line[y][x*3+2] = b*31/255 | AMBIENT_LIGHT;
87       }
88    }
89 
90    /* return our new, magic format version of the image */
91    return bmp;
92 }
93 
94 
95 
96 /* creates the light graphic for the mouse pointer, in our magic format */
create_light_graphic(void)97 BITMAP *create_light_graphic(void)
98 {
99    BITMAP *bmp;
100    int x, y;
101    int dx, dy;
102    int dist, dir;
103    int r, g, b;
104 
105    bmp = create_bitmap_ex(8, 256*3, 256);
106 
107    /* draw the light (a circular color gradient) */
108    for (y=0; y<256; y++) {
109       for (x=0; x<256; x++) {
110 	 dx = x-128;
111 	 dy = y-128;
112 
113 	 dist = fixtoi(fixsqrt(itofix(CLAMP(-32768, dx*dx+dy*dy, 32767))));
114 
115 	 dir = fixtoi(fixatan2(itofix(dy), itofix(dx)) + itofix(128));
116 
117 	 hsv_to_rgb(dir*360.0/256.0, CLAMP(0, dist/128.0, 1), 1, &r, &g, &b);
118 
119 	 r = r * (128-dist) / 1024;
120 	 g = g * (128-dist) / 1024;
121 	 b = b * (128-dist) / 1024;
122 
123 	 bmp->line[y][x*3] = CLAMP(0, r, 7) << 5;
124 	 bmp->line[y][x*3+1] = CLAMP(0, g, 7) << 5;
125 	 bmp->line[y][x*3+2] = CLAMP(0, b, 7) << 5;
126       }
127    }
128 
129    /* draw some solid pixels as well (a cross in the middle ) */
130    #define magic_putpix(x, y, r, g, b)       \
131    {                                         \
132       bmp->line[y][(x)*3] &= 0xE0;           \
133       bmp->line[y][(x)*3+1] &= 0xE0;         \
134       bmp->line[y][(x)*3+2] &= 0xE0;         \
135 					     \
136       bmp->line[y][(x)*3] |= r;              \
137       bmp->line[y][(x)*3+1] |= g;            \
138       bmp->line[y][(x)*3+2] |= b;            \
139    }
140 
141    for (x=-15; x<=15; x++) {
142       for (y=-2; y<=2; y++) {
143 	 magic_putpix(128+x, 128+y, x+15, 15-x, 0);
144 	 magic_putpix(128+y, 128+x, x+15, x+15, 15-x);
145       }
146    }
147 
148    return bmp;
149 }
150 
151 
152 
153 #ifdef ALLEGRO_LITTLE_ENDIAN
154 
155 /* lookup tables for speeding up the color conversion */
156 unsigned short rgtable[65536];
157 unsigned long brtable[65536];
158 unsigned short gbtable[65536];
159 
160 
161 
162 /* builds some helper tables for doing color conversions */
generate_conversion_tables(void)163 void generate_conversion_tables(void)
164 {
165    int r, g, b;
166    int cr, cg, cb;
167 
168    /* this table combines a 16 bit r+g value into a screen pixel */
169    for (r=0; r<256; r++) {
170       cr = (r&31) * (r>>5) * 255/217;
171       for (g=0; g<256; g++) {
172 	 cg = (g&31) * (g>>5) * 255/217;
173 	 rgtable[r+g*256] = makecol(cr, cg, 0);
174       }
175    }
176 
177    /* this table combines a 16 bit b+r value into a screen pixel */
178    for (b=0; b<256; b++) {
179       cb = (b&31) * (b>>5) * 255/217;
180       for (r=0; r<256; r++) {
181 	 cr = (r&31) * (r>>5) * 255/217;
182 	 brtable[b+r*256] = makecol(0, 0, cb) | (makecol(cr, 0, 0) << 16);
183       }
184    }
185 
186    /* this table combines a 16 bit g+b value into a screen pixel */
187    for (g=0; g<256; g++) {
188       cg = (g&31) * (g>>5) * 255/217;
189       for (b=0; b<256; b++) {
190 	 cb = (b&31) * (b>>5) * 255/217;
191 	 gbtable[g+b*256] = makecol(0, cg, cb);
192       }
193    }
194 }
195 
196 
197 
198 /* copies from our magic format data onto a normal Allegro screen bitmap */
blit_magic_format_to_screen(BITMAP * bmp)199 void blit_magic_format_to_screen(BITMAP *bmp)
200 {
201    uintptr_t addr;
202    uint32_t *data;
203    unsigned long in1, in2, in3;
204    unsigned long out1, out2;
205    int x, y;
206 
207    /* Warning: this function contains some rather hairy optimisations :-)
208     * The fastest way to copy large amounts of data is in aligned 32 bit
209     * chunks. If we expand it out to process 4 pixels per cycle, we can
210     * do this by reading 12 bytes (4 pixels) from the 24 bit source data,
211     * and writing 8 bytes (4 pixels) to the 16 bit destination. Then the
212     * only problem is how to convert our 12 bytes of data into a suitable
213     * 8 byte format, once we've got it loaded into registers. This is done
214     * by some lookup tables, which are arranged so they can process 2 color
215     * components in a single lookup, and allow me to precalculate the
216     * makecol() operation.
217     *
218     * Warning #2: this code assumes little-endianess.
219     *
220     * Here is a (rather confusing) attempt to diagram the logic of the
221     * lookup table lighting conversion from 24 to 16 bit format in little-
222     * endian format:
223     *
224     *
225     *  inputs: |     (dword 1)     |     (dword 2)     |     (dword 3)     |
226     *  pixels: |   (pixel1)   |   (pixel2)   |   (pixel3)   |   (pixel4)   |
227     *  bytes:  | r1   g1   b1   r2   g2   b2   r3   g3   b3   r4   g4   b4 |
228     *          |    |         |         |         |         |         |    |
229     *  lookup: | rgtable   brtable   gbtable   rgtable   brtable   gbtable |
230     *          |    |         |         |         |         |         |    |
231     *  pixels: |   (pixel1)   |   (pixel2)   |   (pixel3)   |   (pixel4)   |
232     *  outputs |          (dword 1)          |          (dword 2)          |
233     *
234     *
235     * For reference, here is the original, non-optimised but much easier
236     * to understand version of this routine:
237     *
238     *    _farsetsel(screen->seg);
239     *
240     *    for (y=0; y<SCREEN_H; y++) {
241     *       addr = bmp_write_line(screen, y);
242     *
243     *       for (x=0; x<SCREEN_W; x++) {
244     *          r = bmp->line[y][x*3];
245     *          g = bmp->line[y][x*3+1];
246     *          b = bmp->line[y][x*3+2];
247     *
248     *          r = (r&31) * (r>>5) * 255/217;
249     *          g = (g&31) * (g>>5) * 255/217;
250     *          b = (b&31) * (b>>5) * 255/217;
251     *
252     *          _farnspokew(addr, makecol(r, g, b));
253     *
254     *          addr += 2;
255     *       }
256     *    }
257     */
258 
259    bmp_select(screen);
260 
261    for (y=0; y<SCREEN_H; y++) {
262       addr = bmp_write_line(screen, y);
263       data = (uint32_t *)bmp->line[y];
264 
265       for (x=0; x<SCREEN_W/4; x++) {
266 	 in1 = *(data++);
267 	 in2 = *(data++);
268 	 in3 = *(data++);
269 
270 	 /* trust me, this does make sense, really :-) */
271 	 out1 = rgtable[in1&0xFFFF] |
272 		brtable[in1>>16] |
273 		(gbtable[in2&0xFFFF] << 16);
274 
275 	 out2 = rgtable[in2>>16] |
276 		brtable[in3&0xFFFF] |
277 		(gbtable[in3>>16] << 16);
278 
279 	 bmp_write32(addr, out1);
280 	 bmp_write32(addr+4, out2);
281 
282 	 addr += 8;
283       }
284    }
285 
286    bmp_unwrite_line(screen);
287 }
288 
289 
290 #elif defined ALLEGRO_BIG_ENDIAN
291 
292 
293 /* lookup tables for speeding up the color conversion */
294 unsigned short bgtable[65536];
295 unsigned long rbtable[65536];
296 unsigned short grtable[65536];
297 
298 
299 
300 /* builds some helper tables for doing color conversions */
generate_conversion_tables(void)301 void generate_conversion_tables(void)
302 {
303    int r, g, b;
304    int cr, cg, cb;
305 
306    /* this table combines a 16 bit b+g value into a screen pixel */
307    for (b=0; b<256; b++) {
308       cb = (b&31) * (b>>5) * 255/217;
309       for (g=0; g<256; g++) {
310 	 cg = (g&31) * (g>>5) * 255/217;
311 	 bgtable[b+g*256] = makecol(0, cg, cb);
312       }
313    }
314 
315    /* this table combines a 16 bit g+r value into a screen pixel */
316    for (r=0; r<256; r++) {
317       cr = (r&31) * (r>>5) * 255/217;
318       for (b=0; b<256; b++) {
319 	 cb = (b&31) * (b>>5) * 255/217;
320 	 rbtable[r+b*256] = makecol(cr, 0, 0) | (makecol(0, 0, cb) << 16);
321       }
322    }
323 
324    /* this table combines a 16 bit r+r value into a screen pixel */
325    for (g=0; g<256; g++) {
326       cg = (g&31) * (g>>5) * 255/217;
327       for (r=0; r<256; r++) {
328 	 cr = (r&31) * (r>>5) * 255/217;
329 	 grtable[g+r*256] = makecol(cr, cg, 0);
330       }
331    }
332 }
333 
334 
335 
336 /* copies from our magic format data onto a normal Allegro screen bitmap */
blit_magic_format_to_screen(BITMAP * bmp)337 void blit_magic_format_to_screen(BITMAP *bmp)
338 {
339    uintptr_t addr;
340    uint32_t *data;
341    unsigned long in1, in2, in3, temp1, temp2, temp3;
342    unsigned long out1, out2;
343    int x, y;
344 
345    /* Warning: this is the big-endian version of the routine above. We need
346     * a different lookup tables arrangement and we also need to shuffle the
347     * bytes order before doing the lookup.
348     *
349     * Here is a (rather confusing) attempt to diagram the logic of the
350     * lookup table lighting conversion from 24 to 16 bit format in big-
351     * endian format:
352     *
353     *
354     *  inputs: |     (dword 1)     |     (dword 2)     |     (dword 3)     |
355     *  pixels: |   (pixel1)   |   (pixel2)   |   (pixel3)   |   (pixel4)   |
356     *  bytes:  | r2   b1   g1   r1   g3   r3   b2   g2   b4   g4   r4   b3 |
357     *  bytes2: | b2   g2   r2   b1   g1   r1   b4   g4   r4   b3   g3   r3 |
358     *          |    |         |         |         |         |         |    |
359     *  lookup: | bgtable   rbtable   grtable   bgtable   rbtable   grtable |
360     *          |    |         |         |         |         |         |    |
361     *  pixels: |   (pixel2)   |   (pixel1)   |   (pixel4)   |   (pixel3)   |
362     *  outputs |          (dword 1)          |          (dword 2)          |
363     */
364 
365    bmp_select(screen);
366 
367    for (y=0; y<SCREEN_H; y++) {
368       addr = bmp_write_line(screen, y);
369       data = (uint32_t *)bmp->line[y];
370 
371       for (x=0; x<SCREEN_W/4; x++) {
372 	 in1 = *(data++);
373 	 in2 = *(data++);
374 	 in3 = *(data++);
375 
376 	 /* trust me, this does make sense, really :-) */
377 	 temp1 = (in1 << 16) | (in2 >> 16);
378 	 temp2 = (in1 >> 16) | (in3 << 16);
379 	 temp3 = (in3 >> 16) | (in2 << 16);
380 
381 	 out1 = bgtable[temp1&0xFFFF] |
382 	        rbtable[temp1>>16] |
383 		(grtable[temp2&0xFFFF] << 16);
384 
385 	 out2 = bgtable[temp2>>16] |
386 	        rbtable[temp3&0xFFFF] |
387 		(grtable[temp3>>16] << 16);
388 
389 	 bmp_write32(addr, out1);
390 	 bmp_write32(addr+4, out2);
391 
392 	 addr += 8;
393       }
394    }
395 
396    bmp_unwrite_line(screen);
397 }
398 
399 
400 #elif !defined SCAN_DEPEND
401 #error Unknown endianess!
402 #endif
403 
404 
405 
main(int argc,char * argv[])406 int main(int argc, char *argv[])
407 {
408    BITMAP *tmp;
409    PALETTE pal;
410    char buf[256];
411    int x, y, xc, yc, xl, yl, c, l;
412 
413    if (allegro_init() != 0)
414       return 1;
415    install_keyboard();
416    install_timer();
417    install_mouse();
418    set_color_conversion(COLORCONV_NONE);
419 
420    /* set a 15 or 16 bpp video mode */
421    set_color_depth(16);
422    if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0) {
423       set_color_depth(15);
424       if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0) {
425 	 set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
426 	 allegro_message("Error setting a 15 or 16 bpp 640x480 video mode\n%s\n", allegro_error);
427 	 return 1;
428       }
429    }
430 
431    /* create the double buffer, 8 bpp and three times as wide as the screen */
432    buffer = create_bitmap_ex(8, SCREEN_W*3, SCREEN_H);
433 
434    /* load the first picture */
435    replace_filename(buf, argv[0], "allegro.pcx", sizeof(buf));
436    tmp = load_bitmap(buf, pal);
437    if (!tmp) {
438       destroy_bitmap(buffer);
439       set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
440       allegro_message("Error reading %s!\n", buf);
441       return 1;
442    }
443 
444    /* convert it into our special format */
445    image1 = get_magic_bitmap_format(tmp, pal);
446    destroy_bitmap(tmp);
447 
448    /* load the second picture */
449    replace_filename(buf, argv[0], "mysha.pcx", sizeof(buf));
450    tmp = load_bitmap(buf, pal);
451    if (!tmp) {
452       destroy_bitmap(buffer);
453       destroy_bitmap(image1);
454       set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
455       allegro_message("Error reading %s!\n", buf);
456       return 1;
457    }
458 
459    /* convert it into our special format */
460    image2 = get_magic_bitmap_format(tmp, pal);
461    destroy_bitmap(tmp);
462 
463    /* create the light map image */
464    lightmap = create_light_graphic();
465 
466    /* create our custom color blending map, which does translucency in the
467     * bottom five bits and adds the light levels in the top three bits.
468     * This version just does a 50% translucency if you are drawing sprites
469     * with it, but you could easily make other color maps for different
470     * alpha levels, or for doing additive color, which can work happily
471     * in parallel with the light blending.
472     */
473    color_map = malloc(sizeof(COLOR_MAP));
474 
475    for (x=0; x<256; x++) {
476       for (y=0; y<256; y++) {
477 	 xc = x&31;
478 	 yc = y&31;
479 
480 	 xl = x>>5;
481 	 yl = y>>5;
482 
483 	 if (xc)
484 	    c = (xc+yc)/2;
485 	 else
486 	    c = yc;
487 
488 	 l = xl+yl;
489 	 if (l > 7)
490 	    l = 7;
491 
492 	 color_map->data[x][y] = c | (l<<5);
493       }
494    }
495 
496    /* generate tables for converting pixels from magic->screen format */
497    generate_conversion_tables();
498 
499    /* display the animation */
500    while (!keypressed()) {
501       poll_mouse();
502 
503       clear_bitmap(buffer);
504 
505       /* we can draw the graphics using normal calls, just as if they were
506        * regular 256 color images. Everything is just three times as wide
507        * as it would usually be, so we need to make sure that we only ever
508        * draw to an x coordinate that is a multiple of three (otherwise
509        * all the colors would get shifted out of phase with each other).
510        */
511       blit(image1, buffer, 0, 0, 0, retrace_count%(SCREEN_H+image1->h)-image1->h, image1->w, image1->h);
512       blit(image2, buffer, 0, 0, buffer->w-image2->w, buffer->h-(retrace_count*2/3)%(SCREEN_H+image2->h), image2->w, image2->h);
513 
514       /* now we overlay translucent graphics and lights. Having set up a
515        * suitable color blending table, these can be done at the same time,
516        * either drawing a series of images some of which are translucent
517        * sprites and some of which are light maps, or if we want, we can
518        * just draw a single bitmap containing both color and light data
519        * with a single call, like this! You could also use graphics
520        * primitives like circlefill() to draw the lights, as long as you
521        * do it in a translucent mode.
522        */
523       draw_trans_sprite(buffer, lightmap, (mouse_x-lightmap->w/6)*3, mouse_y-lightmap->h/2);
524 
525       /* this function is the key to the whole thing, converting from our
526        * weird 5+3 interleaved format into a regular hicolor pixel that
527        * can be displayed on your monitor.
528        */
529       blit_magic_format_to_screen(buffer);
530    }
531 
532    clear_keybuf();
533 
534    destroy_bitmap(buffer);
535    destroy_bitmap(image1);
536    destroy_bitmap(image2);
537    destroy_bitmap(lightmap);
538 
539    free(color_map);
540 
541    return 0;
542 }
543 
544 END_OF_MAIN()
545