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