xref: /freebsd/sys/dev/fb/splash_bmp.c (revision daf1cffce2e07931f27c6c6998652e90df6ba87e)
1 /*-
2  * Copyright (c) 1999 Michael Smith <msmith@freebsd.org>
3  * Copyright (c) 1999 Kazutaka YOKOTA <yokota@freebsd.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29 
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/kernel.h>
33 #include <sys/linker.h>
34 #include <sys/fbio.h>
35 
36 #include <dev/fb/fbreg.h>
37 #include <dev/fb/splashreg.h>
38 
39 #define FADE_TIMEOUT	15	/* sec */
40 #define FADE_LEVELS	10
41 
42 static int splash_mode = -1;
43 static int splash_on = FALSE;
44 
45 static int bmp_start(video_adapter_t *adp);
46 static int bmp_end(video_adapter_t *adp);
47 static int bmp_splash(video_adapter_t *adp, int on);
48 static int bmp_Init(const char *data, int swidth, int sheight, int sdepth);
49 static int bmp_Draw(video_adapter_t *adp);
50 
51 static splash_decoder_t bmp_decoder = {
52     "splash_bmp", bmp_start, bmp_end, bmp_splash, SPLASH_IMAGE,
53 };
54 
55 SPLASH_DECODER(splash_bmp, bmp_decoder);
56 
57 static int
58 bmp_start(video_adapter_t *adp)
59 {
60     /* currently only 256-color modes are supported XXX */
61     static int		modes[] = {
62 			M_VESA_CG640x480,
63 			M_VESA_CG800x600,
64 			M_VESA_CG1024x768,
65     			/*
66 			 * As 320x200 doesn't generally look great,
67 			 * it's least preferred here.
68 			 */
69 			M_VGA_CG320,
70 			-1,
71     };
72     video_info_t 	info;
73     int			i;
74 
75     if ((bmp_decoder.data == NULL) || (bmp_decoder.data_size <= 0)) {
76 	printf("splash_bmp: No bitmap file found\n");
77 	return ENODEV;
78     }
79     for (i = 0; modes[i] >= 0; ++i) {
80 	if (((*vidsw[adp->va_index]->get_info)(adp, modes[i], &info) == 0)
81 	    && (bmp_Init((u_char *)bmp_decoder.data,
82 			 info.vi_width, info.vi_height, info.vi_depth) == 0))
83 	    break;
84     }
85     splash_mode = modes[i];
86     if (splash_mode < 0)
87 	printf("splash_bmp: No appropriate video mode found\n");
88     if (bootverbose)
89 	printf("bmp_start(): splash_mode:%d\n", splash_mode);
90     return ((splash_mode < 0) ? ENODEV : 0);
91 }
92 
93 static int
94 bmp_end(video_adapter_t *adp)
95 {
96     /* nothing to do */
97     return 0;
98 }
99 
100 static int
101 bmp_splash(video_adapter_t *adp, int on)
102 {
103     static u_char	pal[256*3];
104     static long		time_stamp;
105     u_char		tpal[256*3];
106     static int		fading = TRUE, brightness = FADE_LEVELS;
107     struct timeval	tv;
108     int			i;
109 
110     if (on) {
111 	if (!splash_on) {
112 	    /* set up the video mode and draw something */
113 	    if ((*vidsw[adp->va_index]->set_mode)(adp, splash_mode))
114 		return 1;
115 	    if (bmp_Draw(adp))
116 		return 1;
117 	    (*vidsw[adp->va_index]->save_palette)(adp, pal);
118 	    time_stamp = 0;
119 	    splash_on = TRUE;
120 	}
121 	/*
122 	 * This is a kludge to fade the image away.  This section of the
123 	 * code takes effect only after the system is completely up.
124 	 * FADE_TIMEOUT should be configurable.
125 	 */
126 	if (!cold) {
127 	    getmicrotime(&tv);
128 	    if (time_stamp == 0)
129 		time_stamp = tv.tv_sec;
130 	    if (tv.tv_sec > time_stamp + FADE_TIMEOUT) {
131 		if (fading)
132 		    if (brightness == 0) {
133 			fading = FALSE;
134 			brightness++;
135 		    }
136 		    else brightness--;
137 		else
138 		    if (brightness == FADE_LEVELS) {
139 			fading = TRUE;
140 			brightness--;
141 		    }
142 		    else brightness++;
143 		for (i = 0; i < sizeof(pal); ++i) {
144 		    tpal[i] = pal[i] * brightness / FADE_LEVELS;
145 		}
146 		(*vidsw[adp->va_index]->load_palette)(adp, tpal);
147 		time_stamp = tv.tv_sec;
148 	    }
149 	}
150 	return 0;
151     } else {
152 	/* the video mode will be restored by the caller */
153 	splash_on = FALSE;
154 	return 0;
155     }
156 }
157 
158 /*
159 ** Code to handle Microsoft DIB (".BMP") format images.
160 **
161 ** Blame me (msmith@freebsd.org) if this is broken, not Soren.
162 */
163 
164 typedef struct tagBITMAPFILEHEADER {    /* bmfh */
165     u_short	bfType		__attribute__ ((packed));
166     int		bfSize		__attribute__ ((packed));
167     u_short	bfReserved1	__attribute__ ((packed));
168     u_short	bfReserved2	__attribute__ ((packed));
169     int		bfOffBits	__attribute__ ((packed));
170 } BITMAPFILEHEADER;
171 
172 typedef struct tagBITMAPINFOHEADER {    /* bmih */
173     int		biSize		__attribute__ ((packed));
174     int		biWidth		__attribute__ ((packed));
175     int		biHeight	__attribute__ ((packed));
176     short	biPlanes	__attribute__ ((packed));
177     short	biBitCount	__attribute__ ((packed));
178     int		biCompression	__attribute__ ((packed));
179     int		biSizeImage	__attribute__ ((packed));
180     int		biXPelsPerMeter	__attribute__ ((packed));
181     int		biYPelsPerMeter	__attribute__ ((packed));
182     int		biClrUsed	__attribute__ ((packed));
183     int		biClrImportant	__attribute__ ((packed));
184 } BITMAPINFOHEADER;
185 
186 typedef struct tagRGBQUAD {     /* rgbq */
187     u_char	rgbBlue		__attribute__ ((packed));
188     u_char	rgbGreen	__attribute__ ((packed));
189     u_char	rgbRed		__attribute__ ((packed));
190     u_char	rgbReserved	__attribute__ ((packed));
191 } RGBQUAD;
192 
193 typedef struct tagBITMAPINFO {  /* bmi */
194     BITMAPINFOHEADER	bmiHeader	__attribute__ ((packed));
195     RGBQUAD		bmiColors[256]	__attribute__ ((packed));
196 } BITMAPINFO;
197 
198 typedef struct tagBITMAPF
199 {
200     BITMAPFILEHEADER	bmfh	__attribute__ ((packed));
201     BITMAPINFO		bmfi	__attribute__ ((packed));
202 } BITMAPF;
203 
204 #define BI_RGB		0
205 #define BI_RLE8		1
206 #define BI_RLE4		2
207 
208 /*
209 ** all we actually care about the image
210 */
211 typedef struct
212 {
213     int		width,height;		/* image dimensions */
214     int		swidth,sheight;		/* screen dimensions for the current mode */
215     u_char	sdepth;			/* screen depth (1, 4, 8 bpp) */
216     int		ncols;			/* number of colours */
217     u_char	palette[256][3];	/* raw palette data */
218     u_char	format;			/* one of the BI_* constants above */
219     u_char	*data;			/* pointer to the raw data */
220     u_char	*index;			/* running pointer to the data while drawing */
221     u_char	*vidmem;		/* video memory allocated for drawing */
222     video_adapter_t *adp;
223     int		bank;
224 } BMP_INFO;
225 
226 static BMP_INFO bmp_info;
227 
228 static void
229 fill(BMP_INFO *info, int x, int y, int xsize, int ysize)
230 {
231     u_char	*window;
232     int		banksize;
233     int		bank;
234     int		p;
235 
236     banksize = info->adp->va_window_size;
237     bank = (info->adp->va_line_width*y + x)/banksize;
238     window = (u_char *)info->adp->va_window;
239     (*vidsw[info->adp->va_index]->set_win_org)(info->adp, bank*banksize);
240     while (ysize > 0) {
241 	p = (info->adp->va_line_width*y + x)%banksize;
242 	for (; (p + xsize <= banksize) && ysize > 0; --ysize, ++y) {
243 	    generic_bzero(window + p, xsize);
244 	    p += info->adp->va_line_width;
245 	}
246 	if (ysize <= 0)
247 	    break;
248 	if (p < banksize) {
249 	    /* the last line crosses the window boundary */
250 	    generic_bzero(window + p, banksize - p);
251 	}
252 	++bank;				/* next bank */
253 	(*vidsw[info->adp->va_index]->set_win_org)(info->adp, bank*banksize);
254 	if (p < banksize) {
255 	    /* the remaining part of the last line */
256 	    generic_bzero(window, p + xsize - banksize);
257 	    ++y;
258 	    --ysize;
259 	}
260     }
261     info->bank = bank;
262 }
263 
264 /*
265 ** bmp_SetPix
266 **
267 ** Given (info), set the pixel at (x),(y) to (val)
268 **
269 */
270 static void
271 bmp_SetPix(BMP_INFO *info, int x, int y, u_char val)
272 {
273     int		sofs, bofs;
274     u_char	tpv, mask;
275     int		newbank;
276 
277     /*
278      * range check to avoid explosions
279      */
280     if ((x < 0) || (x >= info->swidth) || (y < 0) || (y >= info->sheight))
281 	return;
282 
283     /*
284      * calculate offset into video memory;
285      * because 0,0 is bottom-left for DIB, we have to convert.
286      */
287     sofs = ((info->height - (y+1) + (info->sheight - info->height) / 2)
288 		* info->adp->va_line_width);
289 
290     switch(info->sdepth) {
291     case 1:
292 	sofs += ((x + (info->swidth - info->width) / 2) >> 3);
293 	bofs = x & 0x7;				/* offset within byte */
294 
295 	val &= 1;				/* mask pixel value */
296 	mask = ~(0x80 >> bofs);			/* calculate bit mask */
297 	tpv = *(info->vidmem+sofs) & mask;	/* get screen contents, excluding masked bit */
298 	*(info->vidmem+sofs) = tpv | (val << (8-bofs));	/* write new bit */
299 	break;
300 
301 	/* XXX only correct for non-interleaved modes */
302     case 4:
303 	sofs += ((x + (info->swidth - info->width) / 2) >> 1);
304 	bofs = x & 0x1;				/* offset within byte */
305 
306 	val &= 0xf;				/* mask pixel value */
307 	mask = bofs ? 0x0f : 0xf0;		/* calculate bit mask */
308 	tpv = *(info->vidmem+sofs) & mask;	/* get screen contents, excluding masked bits */
309 	*(info->vidmem+sofs) = tpv | (val << (bofs ? 0 : 4));	/* write new bits */
310 	break;
311 
312     case 8:
313 	sofs += x + (info->swidth - info->width) / 2;
314 	newbank = sofs/info->adp->va_window_size;
315 	if (info->bank != newbank) {
316 	    (*vidsw[info->adp->va_index]->set_win_org)(info->adp, newbank*info->adp->va_window_size);
317 	    info->bank = newbank;
318 	}
319 	sofs %= info->adp->va_window_size;
320 	*(info->vidmem+sofs) = val;
321 	break;
322     }
323 }
324 
325 /*
326 ** bmp_DecodeRLE4
327 **
328 ** Given (data) pointing to a line of RLE4-format data and (line) being the starting
329 ** line onscreen, decode the line.
330 */
331 static void
332 bmp_DecodeRLE4(BMP_INFO *info, int line)
333 {
334     int		count;		/* run count */
335     u_char	val;
336     int		x,y;		/* screen position */
337 
338     x = 0;			/* starting position */
339     y = line;
340 
341     /* loop reading data */
342     for (;;) {
343 	/*
344 	 * encoded mode starts with a run length, and then a byte with
345 	 * two colour indexes to alternate between for the run
346 	 */
347 	if (*info->index) {
348 	    for (count = 0; count < *info->index; count++, x++) {
349 		if (count & 1) {		/* odd count, low nybble */
350 		    bmp_SetPix(info, x, y, *(info->index+1) & 0x0f);
351 		} else {			/* even count, high nybble */
352 		    bmp_SetPix(info, x, y, (*(info->index+1) >>4) & 0x0f);
353 		}
354 	    }
355 	    info->index += 2;
356         /*
357 	 * A leading zero is an escape; it may signal the end of the
358 	 * bitmap, a cursor move, or some absolute data.
359 	 */
360 	} else {	/* zero tag may be absolute mode or an escape */
361 	    switch (*(info->index+1)) {
362 	    case 0:				/* end of line */
363 		info->index += 2;
364 		return;
365 	    case 1:				/* end of bitmap */
366 		info->index = NULL;
367 		return;
368 	    case 2:				/* move */
369 		x += *(info->index + 2);	/* new coords */
370 		y += *(info->index + 3);
371 		info->index += 4;
372 		break;
373 	    default:				/* literal bitmap data */
374 		for (count = 0; count < *(info->index + 1); count++, x++) {
375 		    val = *(info->index + 2 + (count / 2));	/* byte with nybbles */
376 		    if (count & 1) {
377 			val &= 0xf;		/* get low nybble */
378 		    } else {
379 			val = (val >> 4);	/* get high nybble */
380 		    }
381 		    bmp_SetPix(info, x, y, val);
382 		}
383 		/* warning, this depends on integer truncation, do not hand-optimise! */
384 		info->index += 2 + ((count + 3) / 4) * 2;
385 		break;
386 	    }
387 	}
388     }
389 }
390 
391 /*
392 ** bmp_DecodeRLE8
393 ** Given (data) pointing to a line of RLE4-format data and (line) being the starting
394 ** line onscreen, decode the line.
395 */
396 static void
397 bmp_DecodeRLE8(BMP_INFO *info, int line)
398 {
399     int		count;		/* run count */
400     int		x,y;		/* screen position */
401 
402     x = 0;			/* starting position */
403     y = line;
404 
405     /* loop reading data */
406     for(;;) {
407 	/*
408 	 * encoded mode starts with a run length, and then a byte with
409 	 * two colour indexes to alternate between for the run
410 	 */
411 	if (*info->index) {
412 	    for (count = 0; count < *info->index; count++, x++)
413 		bmp_SetPix(info, x, y, *(info->index+1));
414 	    info->index += 2;
415         /*
416 	 * A leading zero is an escape; it may signal the end of the
417 	 * bitmap, a cursor move, or some absolute data.
418 	 */
419 	} else {	/* zero tag may be absolute mode or an escape */
420 	    switch(*(info->index+1)) {
421 	    case 0:				/* end of line */
422 		info->index += 2;
423 		return;
424 	    case 1:				/* end of bitmap */
425 		info->index = NULL;
426 		return;
427 	    case 2:				/* move */
428 		x += *(info->index + 2);	/* new coords */
429 		y += *(info->index + 3);
430 		info->index += 4;
431 		break;
432 	    default:				/* literal bitmap data */
433 		for (count = 0; count < *(info->index + 1); count++, x++)
434 		    bmp_SetPix(info, x, y, *(info->index + 2 + count));
435 		/* must be an even count */
436 		info->index += 2 + count + (count & 1);
437 		break;
438 	    }
439 	}
440     }
441 }
442 
443 /*
444 ** bmp_DecodeLine
445 **
446 ** Given (info) pointing to an image being decoded, (line) being the line currently
447 ** being displayed, decode a line of data.
448 */
449 static void
450 bmp_DecodeLine(BMP_INFO *info, int line)
451 {
452     int		x;
453 
454     switch(info->format) {
455     case BI_RGB:
456 	for (x = 0; x < info->width; x++, info->index++)
457 	    bmp_SetPix(info, x, line, *info->index);
458 	info->index += 3 - (--x % 4);
459 	break;
460     case BI_RLE4:
461 	bmp_DecodeRLE4(info, line);
462 	break;
463     case BI_RLE8:
464 	bmp_DecodeRLE8(info, line);
465 	break;
466     }
467 }
468 
469 /*
470 ** bmp_Init
471 **
472 ** Given a pointer (data) to the image of a BMP file, fill in bmp_info with what
473 ** can be learnt from it.  Return nonzero if the file isn't usable.
474 **
475 ** Take screen dimensions (swidth), (sheight) and (sdepth) and make sure we
476 ** can work with these.
477 */
478 static int
479 bmp_Init(const char *data, int swidth, int sheight, int sdepth)
480 {
481     BITMAPF	*bmf = (BITMAPF *)data;
482     int		pind;
483 
484     bmp_info.data = NULL;	/* assume setup failed */
485 
486     /* check file ID */
487     if (bmf->bmfh.bfType != 0x4d42) {
488 	printf("splash_bmp: not a BMP file\n");
489 	return(1);		/* XXX check word ordering for big-endian ports? */
490     }
491 
492     /* do we understand this bitmap format? */
493     if (bmf->bmfi.bmiHeader.biSize > sizeof(bmf->bmfi.bmiHeader)) {
494 	printf("splash_bmp: unsupported BMP format (size=%d)\n",
495 		bmf->bmfi.bmiHeader.biSize);
496 	return(1);
497     }
498 
499     /* save what we know about the screen */
500     bmp_info.swidth = swidth;
501     bmp_info.sheight = sheight;
502     bmp_info.sdepth = sdepth;
503 
504     /* where's the data? */
505     bmp_info.data = (u_char *)data + bmf->bmfh.bfOffBits;
506 
507     /* image parameters */
508     bmp_info.width = bmf->bmfi.bmiHeader.biWidth;
509     bmp_info.height = bmf->bmfi.bmiHeader.biHeight;
510     bmp_info.format = bmf->bmfi.bmiHeader.biCompression;
511 
512     switch(bmp_info.format) {	/* check compression format */
513     case BI_RGB:
514     case BI_RLE4:
515     case BI_RLE8:
516 	break;
517     default:
518 	printf("splash_bmp: unsupported compression format\n");
519 	return(1);		/* unsupported compression format */
520     }
521 
522     /* palette details */
523     bmp_info.ncols = (bmf->bmfi.bmiHeader.biClrUsed);
524     bzero(bmp_info.palette,sizeof(bmp_info.palette));
525     if (bmp_info.ncols == 0) {	/* uses all of them */
526 	bmp_info.ncols = 1 << bmf->bmfi.bmiHeader.biBitCount;
527     }
528     if ((bmf->bmfi.bmiHeader.biBitCount != sdepth)
529 	|| (bmp_info.ncols > (1 << sdepth))) {
530 	printf("splash_bmp: unsupported color depth (%d bits, %d colors)\n",
531 		bmf->bmfi.bmiHeader.biBitCount, bmp_info.ncols);
532 	return(1);
533     }
534     if ((bmp_info.height > bmp_info.sheight) ||
535 	(bmp_info.width > bmp_info.swidth) ||
536 	(bmp_info.ncols > (1 << sdepth))) {
537 	    return(1);		/* beyond screen capacity */
538     }
539 
540     /* read palette */
541     for (pind = 0; pind < bmp_info.ncols; pind++) {
542 	bmp_info.palette[pind][0] = bmf->bmfi.bmiColors[pind].rgbRed;
543 	bmp_info.palette[pind][1] = bmf->bmfi.bmiColors[pind].rgbGreen;
544 	bmp_info.palette[pind][2] = bmf->bmfi.bmiColors[pind].rgbBlue;
545     }
546     return(0);
547 }
548 
549 /*
550 ** bmp_Draw
551 **
552 ** Render the image.  Return nonzero if that's not possible.
553 **
554 */
555 static int
556 bmp_Draw(video_adapter_t *adp)
557 {
558     int		line;
559 
560     if (bmp_info.data == NULL) {	/* init failed, do nothing */
561 	return(1);
562     }
563 
564     /* clear the screen */
565     bmp_info.vidmem = (u_char *)adp->va_window;
566     bmp_info.adp = adp;
567     /* XXX; the following line is correct only for 8bpp modes */
568     fill(&bmp_info, 0, 0, bmp_info.swidth, bmp_info.sheight);
569     (*vidsw[adp->va_index]->set_win_org)(adp, 0);
570     bmp_info.bank = 0;
571 
572     /* initialise the info structure for drawing */
573     bmp_info.index = bmp_info.data;
574 
575     /* set the palette for our image */
576     (*vidsw[adp->va_index]->load_palette)(adp, (u_char *)&bmp_info.palette);
577 
578     for (line = 0; (line < bmp_info.height) && bmp_info.index; line++) {
579 	bmp_DecodeLine(&bmp_info, line);
580     }
581     return(0);
582 }
583