xref: /freebsd/sys/dev/fb/splash_bmp.c (revision 2da199da53835ee2d9228a60717fd2d0fccf9e50)
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  * $Id: splash_bmp.c,v 1.5 1999/01/26 10:00:02 yokota Exp $
28  */
29 
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/kernel.h>
33 #include <sys/linker.h>
34 
35 #include <machine/console.h>
36 
37 #include <dev/fb/fbreg.h>
38 #include <dev/fb/splashreg.h>
39 
40 #define FADE_TIMEOUT	300	/* sec */
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 	return ENODEV;
77     for (i = 0; modes[i] >= 0; ++i) {
78 	if (((*vidsw[adp->va_index]->get_info)(adp, modes[i], &info) == 0)
79 	    && (bmp_Init((u_char *)bmp_decoder.data,
80 			 info.vi_width, info.vi_height, info.vi_depth) == 0))
81 	    break;
82     }
83     splash_mode = modes[i];
84     if (bootverbose)
85 	printf("bmp_start(): splash_mode:%d\n", splash_mode);
86     return ((splash_mode < 0) ? ENODEV : 0);
87 }
88 
89 static int
90 bmp_end(video_adapter_t *adp)
91 {
92     /* nothing to do */
93     return 0;
94 }
95 
96 static int
97 bmp_splash(video_adapter_t *adp, int on)
98 {
99     static u_char	pal[256*3];
100     static long		time_stamp;
101     struct timeval	tv;
102     int			i;
103 
104     if (on) {
105 	if (!splash_on) {
106 	    /* set up the video mode and draw something */
107 	    if ((*vidsw[adp->va_index]->set_mode)(adp, splash_mode))
108 		return 1;
109 	    if (bmp_Draw(adp))
110 		return 1;
111 	    (*vidsw[adp->va_index]->save_palette)(adp, pal);
112 	    time_stamp = 0;
113 	    splash_on = TRUE;
114 	}
115 	/*
116 	 * This is a kludge to fade the image away.  This section of the
117 	 * code takes effect only after the system is completely up.
118 	 * FADE_TIMEOUT should be configurable.
119 	 */
120 	if (!cold) {
121 	    getmicrotime(&tv);
122 	    if (time_stamp == 0)
123 		time_stamp = tv.tv_sec;
124 	    if (tv.tv_sec > time_stamp + FADE_TIMEOUT) {
125 		for (i = 0; i < sizeof(pal); ++i) {
126 		    if (pal[i] > 40)
127 			pal[i] -= 4;
128 		}
129 		(*vidsw[adp->va_index]->load_palette)(adp, pal);
130 	    }
131 	}
132 	return 0;
133     } else {
134 	/* the video mode will be restored by the caller */
135 	splash_on = FALSE;
136 	return 0;
137     }
138 }
139 
140 /*
141 ** Code to handle Microsoft DIB (".BMP") format images.
142 **
143 ** Blame me (msmith@freebsd.org) if this is broken, not Soren.
144 */
145 
146 typedef struct tagBITMAPFILEHEADER {    /* bmfh */
147     u_short	bfType		__attribute__ ((packed));
148     int		bfSize		__attribute__ ((packed));
149     u_short	bfReserved1	__attribute__ ((packed));
150     u_short	bfReserved2	__attribute__ ((packed));
151     int		bfOffBits	__attribute__ ((packed));
152 } BITMAPFILEHEADER;
153 
154 typedef struct tagBITMAPINFOHEADER {    /* bmih */
155     int		biSize		__attribute__ ((packed));
156     int		biWidth		__attribute__ ((packed));
157     int		biHeight	__attribute__ ((packed));
158     short	biPlanes	__attribute__ ((packed));
159     short	biBitCount	__attribute__ ((packed));
160     int		biCompression	__attribute__ ((packed));
161     int		biSizeImage	__attribute__ ((packed));
162     int		biXPelsPerMeter	__attribute__ ((packed));
163     int		biYPelsPerMeter	__attribute__ ((packed));
164     int		biClrUsed	__attribute__ ((packed));
165     int		biClrImportant	__attribute__ ((packed));
166 } BITMAPINFOHEADER;
167 
168 typedef struct tagRGBQUAD {     /* rgbq */
169     u_char	rgbBlue		__attribute__ ((packed));
170     u_char	rgbGreen	__attribute__ ((packed));
171     u_char	rgbRed		__attribute__ ((packed));
172     u_char	rgbReserved	__attribute__ ((packed));
173 } RGBQUAD;
174 
175 typedef struct tagBITMAPINFO {  /* bmi */
176     BITMAPINFOHEADER	bmiHeader	__attribute__ ((packed));
177     RGBQUAD		bmiColors[256]	__attribute__ ((packed));
178 } BITMAPINFO;
179 
180 typedef struct tagBITMAPF
181 {
182     BITMAPFILEHEADER	bmfh	__attribute__ ((packed));
183     BITMAPINFO		bmfi	__attribute__ ((packed));
184 } BITMAPF;
185 
186 #define BI_RGB		0
187 #define BI_RLE8		1
188 #define BI_RLE4		2
189 
190 /*
191 ** all we actually care about the image
192 */
193 typedef struct
194 {
195     int		width,height;		/* image dimensions */
196     int		swidth,sheight;		/* screen dimensions for the current mode */
197     u_char	sdepth;			/* screen depth (1, 4, 8 bpp) */
198     int		ncols;			/* number of colours */
199     u_char	palette[256][3];	/* raw palette data */
200     u_char	format;			/* one of the BI_* constants above */
201     u_char	*data;			/* pointer to the raw data */
202     u_char	*index;			/* running pointer to the data while drawing */
203     u_char	*vidmem;		/* video memory allocated for drawing */
204     video_adapter_t *adp;
205     int		bank;
206 } BMP_INFO;
207 
208 static BMP_INFO bmp_info;
209 
210 static void
211 fill(BMP_INFO *info, int x, int y, int xsize, int ysize)
212 {
213     u_char	*window;
214     int		banksize;
215     int		bank;
216     int		p;
217 
218     banksize = info->adp->va_window_size;
219     bank = (info->adp->va_line_width*y + x)/banksize;
220     window = (u_char *)info->adp->va_window;
221     (*vidsw[info->adp->va_index]->set_win_org)(info->adp, bank*banksize);
222     while (ysize > 0) {
223 	p = (info->adp->va_line_width*y + x)%banksize;
224 	for (; (p + xsize <= banksize) && ysize > 0; --ysize, ++y) {
225 	    generic_bzero(window + p, xsize);
226 	    p += info->adp->va_line_width;
227 	}
228 	if (ysize <= 0)
229 	    break;
230 	if (p < banksize) {
231 	    /* the last line crosses the window boundary */
232 	    generic_bzero(window + p, banksize - p);
233 	}
234 	++bank;				/* next bank */
235 	(*vidsw[info->adp->va_index]->set_win_org)(info->adp, bank*banksize);
236 	if (p < banksize) {
237 	    /* the remaining part of the last line */
238 	    generic_bzero(window, p + xsize - banksize);
239 	    ++y;
240 	    --ysize;
241 	}
242     }
243     info->bank = bank;
244 }
245 
246 /*
247 ** bmp_SetPix
248 **
249 ** Given (info), set the pixel at (x),(y) to (val)
250 **
251 */
252 static void
253 bmp_SetPix(BMP_INFO *info, int x, int y, u_char val)
254 {
255     int		sofs, bofs;
256     u_char	tpv, mask;
257     int		newbank;
258 
259     /*
260      * range check to avoid explosions
261      */
262     if ((x < 0) || (x >= info->swidth) || (y < 0) || (y >= info->sheight))
263 	return;
264 
265     /*
266      * calculate offset into video memory;
267      * because 0,0 is bottom-left for DIB, we have to convert.
268      */
269     sofs = ((info->height - (y+1) + (info->sheight - info->height) / 2)
270 		* info->adp->va_line_width);
271 
272     switch(info->sdepth) {
273     case 1:
274 	sofs += ((x + (info->swidth - info->width) / 2) >> 3);
275 	bofs = x & 0x7;				/* offset within byte */
276 
277 	val &= 1;				/* mask pixel value */
278 	mask = ~(0x80 >> bofs);			/* calculate bit mask */
279 	tpv = *(info->vidmem+sofs) & mask;	/* get screen contents, excluding masked bit */
280 	*(info->vidmem+sofs) = tpv | (val << (8-bofs));	/* write new bit */
281 	break;
282 
283 	/* XXX only correct for non-interleaved modes */
284     case 4:
285 	sofs += ((x + (info->swidth - info->width) / 2) >> 1);
286 	bofs = x & 0x1;				/* offset within byte */
287 
288 	val &= 0xf;				/* mask pixel value */
289 	mask = bofs ? 0x0f : 0xf0;		/* calculate bit mask */
290 	tpv = *(info->vidmem+sofs) & mask;	/* get screen contents, excluding masked bits */
291 	*(info->vidmem+sofs) = tpv | (val << (bofs ? 0 : 4));	/* write new bits */
292 	break;
293 
294     case 8:
295 	sofs += x + (info->swidth - info->width) / 2;
296 	newbank = sofs/info->adp->va_window_size;
297 	if (info->bank != newbank) {
298 	    (*vidsw[info->adp->va_index]->set_win_org)(info->adp, newbank*info->adp->va_window_size);
299 	    info->bank = newbank;
300 	}
301 	sofs %= info->adp->va_window_size;
302 	*(info->vidmem+sofs) = val;
303 	break;
304     }
305 }
306 
307 /*
308 ** bmp_DecodeRLE4
309 **
310 ** Given (data) pointing to a line of RLE4-format data and (line) being the starting
311 ** line onscreen, decode the line.
312 */
313 static void
314 bmp_DecodeRLE4(BMP_INFO *info, int line)
315 {
316     int		count;		/* run count */
317     u_char	val;
318     int		x,y;		/* screen position */
319 
320     x = 0;			/* starting position */
321     y = line;
322 
323     /* loop reading data */
324     for (;;) {
325 	/*
326 	 * encoded mode starts with a run length, and then a byte with
327 	 * two colour indexes to alternate between for the run
328 	 */
329 	if (*info->index) {
330 	    for (count = 0; count < *info->index; count++, x++) {
331 		if (count & 1) {		/* odd count, low nybble */
332 		    bmp_SetPix(info, x, y, *(info->index+1) & 0x0f);
333 		} else {			/* even count, high nybble */
334 		    bmp_SetPix(info, x, y, (*(info->index+1) >>4) & 0x0f);
335 		}
336 	    }
337 	    info->index += 2;
338         /*
339 	 * A leading zero is an escape; it may signal the end of the
340 	 * bitmap, a cursor move, or some absolute data.
341 	 */
342 	} else {	/* zero tag may be absolute mode or an escape */
343 	    switch (*(info->index+1)) {
344 	    case 0:				/* end of line */
345 		info->index += 2;
346 		return;
347 	    case 1:				/* end of bitmap */
348 		info->index = NULL;
349 		return;
350 	    case 2:				/* move */
351 		x += *(info->index + 2);	/* new coords */
352 		y += *(info->index + 3);
353 		info->index += 4;
354 		break;
355 	    default:				/* literal bitmap data */
356 		for (count = 0; count < *(info->index + 1); count++, x++) {
357 		    val = *(info->index + 2 + (count / 2));	/* byte with nybbles */
358 		    if (count & 1) {
359 			val &= 0xf;		/* get low nybble */
360 		    } else {
361 			val = (val >> 4);	/* get high nybble */
362 		    }
363 		    bmp_SetPix(info, x, y, val);
364 		}
365 		/* warning, this depends on integer truncation, do not hand-optimise! */
366 		info->index += 2 + ((count + 3) / 4) * 2;
367 		break;
368 	    }
369 	}
370     }
371 }
372 
373 /*
374 ** bmp_DecodeRLE8
375 ** Given (data) pointing to a line of RLE4-format data and (line) being the starting
376 ** line onscreen, decode the line.
377 */
378 static void
379 bmp_DecodeRLE8(BMP_INFO *info, int line)
380 {
381     int		count;		/* run count */
382     int		x,y;		/* screen position */
383 
384     x = 0;			/* starting position */
385     y = line;
386 
387     /* loop reading data */
388     for(;;) {
389 	/*
390 	 * encoded mode starts with a run length, and then a byte with
391 	 * two colour indexes to alternate between for the run
392 	 */
393 	if (*info->index) {
394 	    for (count = 0; count < *info->index; count++, x++)
395 		bmp_SetPix(info, x, y, *(info->index+1));
396 	    info->index += 2;
397         /*
398 	 * A leading zero is an escape; it may signal the end of the
399 	 * bitmap, a cursor move, or some absolute data.
400 	 */
401 	} else {	/* zero tag may be absolute mode or an escape */
402 	    switch(*(info->index+1)) {
403 	    case 0:				/* end of line */
404 		info->index += 2;
405 		return;
406 	    case 1:				/* end of bitmap */
407 		info->index = NULL;
408 		return;
409 	    case 2:				/* move */
410 		x += *(info->index + 2);	/* new coords */
411 		y += *(info->index + 3);
412 		info->index += 4;
413 		break;
414 	    default:				/* literal bitmap data */
415 		for (count = 0; count < *(info->index + 1); count++, x++)
416 		    bmp_SetPix(info, x, y, *(info->index + 2 + count));
417 		/* must be an even count */
418 		info->index += 2 + count + (count & 1);
419 		break;
420 	    }
421 	}
422     }
423 }
424 
425 /*
426 ** bmp_DecodeLine
427 **
428 ** Given (info) pointing to an image being decoded, (line) being the line currently
429 ** being displayed, decode a line of data.
430 */
431 static void
432 bmp_DecodeLine(BMP_INFO *info, int line)
433 {
434     int		x;
435 
436     switch(info->format) {
437     case BI_RGB:
438 	for (x = 0; x < info->width; x++, info->index++)
439 	    bmp_SetPix(info, x, line, *info->index);
440 	info->index += 3 - (--x % 4);
441 	break;
442     case BI_RLE4:
443 	bmp_DecodeRLE4(info, line);
444 	break;
445     case BI_RLE8:
446 	bmp_DecodeRLE8(info, line);
447 	break;
448     }
449 }
450 
451 /*
452 ** bmp_Init
453 **
454 ** Given a pointer (data) to the image of a BMP file, fill in bmp_info with what
455 ** can be learnt from it.  Return nonzero if the file isn't usable.
456 **
457 ** Take screen dimensions (swidth), (sheight) and (sdepth) and make sure we
458 ** can work with these.
459 */
460 static int
461 bmp_Init(const char *data, int swidth, int sheight, int sdepth)
462 {
463     BITMAPF	*bmf = (BITMAPF *)data;
464     int		pind;
465 
466     bmp_info.data = NULL;	/* assume setup failed */
467 
468     /* check file ID */
469     if (bmf->bmfh.bfType != 0x4d42) {
470 	return(1);		/* XXX check word ordering for big-endian ports? */
471     }
472 
473     /* save what we know about the screen */
474     bmp_info.swidth = swidth;
475     bmp_info.sheight = sheight;
476     bmp_info.sdepth = sdepth;
477 
478     /* where's the data? */
479     bmp_info.data = (u_char *)data + bmf->bmfh.bfOffBits;
480 
481     /* image parameters */
482     bmp_info.width = bmf->bmfi.bmiHeader.biWidth;
483     bmp_info.height = bmf->bmfi.bmiHeader.biHeight;
484     bmp_info.format = bmf->bmfi.bmiHeader.biCompression;
485 
486     switch(bmp_info.format) {	/* check compression format */
487     case BI_RGB:
488     case BI_RLE4:
489     case BI_RLE8:
490 	break;
491     default:
492 	return(1);		/* unsupported compression format */
493     }
494 
495     /* palette details */
496     bmp_info.ncols = (bmf->bmfi.bmiHeader.biClrUsed);
497     bzero(bmp_info.palette,sizeof(bmp_info.palette));
498     if (bmp_info.ncols == 0) {	/* uses all of them */
499 	bmp_info.ncols = 1 << bmf->bmfi.bmiHeader.biBitCount;
500     }
501     if ((bmp_info.height > bmp_info.sheight) ||
502 	(bmp_info.width > bmp_info.swidth) ||
503 	(bmp_info.ncols > (1 << sdepth))) {
504 	    return(1);		/* beyond screen capacity */
505     }
506 
507     /* read palette */
508     for (pind = 0; pind < bmp_info.ncols; pind++) {
509 	bmp_info.palette[pind][0] = bmf->bmfi.bmiColors[pind].rgbRed;
510 	bmp_info.palette[pind][1] = bmf->bmfi.bmiColors[pind].rgbGreen;
511 	bmp_info.palette[pind][2] = bmf->bmfi.bmiColors[pind].rgbBlue;
512     }
513     return(0);
514 }
515 
516 /*
517 ** bmp_Draw
518 **
519 ** Render the image.  Return nonzero if that's not possible.
520 **
521 */
522 static int
523 bmp_Draw(video_adapter_t *adp)
524 {
525     int		line;
526 
527     if (bmp_info.data == NULL) {	/* init failed, do nothing */
528 	return(1);
529     }
530 
531     /* clear the screen */
532     bmp_info.vidmem = (u_char *)adp->va_window;
533     bmp_info.adp = adp;
534     /* XXX; the following line is correct only for 8bpp modes */
535     fill(&bmp_info, 0, 0, bmp_info.swidth, bmp_info.sheight);
536     (*vidsw[adp->va_index]->set_win_org)(adp, 0);
537     bmp_info.bank = 0;
538 
539     /* initialise the info structure for drawing */
540     bmp_info.index = bmp_info.data;
541 
542     /* set the palette for our image */
543     (*vidsw[adp->va_index]->load_palette)(adp, (u_char *)&bmp_info.palette);
544 
545     for (line = 0; (line < bmp_info.height) && bmp_info.index; line++) {
546 	bmp_DecodeLine(&bmp_info, line);
547     }
548     return(0);
549 }
550