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