/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2006,2007 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); /* Uncomment following define to enable TGA debug. */ //#define TGA_DEBUG #define dump_int_field(x) grub_dprintf ("tga", #x " = %d (0x%04x)\n", x, x); #if defined(TGA_DEBUG) static grub_command_t cmd; #endif enum { GRUB_TGA_IMAGE_TYPE_NONE = 0, GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_INDEXCOLOR = 1, GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_TRUECOLOR = 2, GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_BLACK_AND_WHITE = 3, GRUB_TGA_IMAGE_TYPE_RLE_INDEXCOLOR = 9, GRUB_TGA_IMAGE_TYPE_RLE_TRUECOLOR = 10, GRUB_TGA_IMAGE_TYPE_RLE_BLACK_AND_WHITE = 11, }; enum { GRUB_TGA_COLOR_MAP_TYPE_NONE = 0, GRUB_TGA_COLOR_MAP_TYPE_INCLUDED = 1 }; enum { GRUB_TGA_IMAGE_ORIGIN_RIGHT = 0x10, GRUB_TGA_IMAGE_ORIGIN_TOP = 0x20 }; struct grub_tga_header { grub_uint8_t id_length; grub_uint8_t color_map_type; grub_uint8_t image_type; /* Color Map Specification. */ grub_uint16_t color_map_first_index; grub_uint16_t color_map_length; grub_uint8_t color_map_bpp; /* Image Specification. */ grub_uint16_t image_x_origin; grub_uint16_t image_y_origin; grub_uint16_t image_width; grub_uint16_t image_height; grub_uint8_t image_bpp; grub_uint8_t image_descriptor; } __attribute__ ((packed)); struct tga_data { struct grub_tga_header hdr; int uses_rle; int pktlen; int bpp; int is_rle; grub_uint8_t pixel[4]; grub_uint8_t palette[256][3]; struct grub_video_bitmap *bitmap; grub_file_t file; unsigned image_width; unsigned image_height; }; static grub_err_t fetch_pixel (struct tga_data *data) { if (!data->uses_rle) { if (grub_file_read (data->file, &data->pixel[0], data->bpp) != data->bpp) return grub_errno; return GRUB_ERR_NONE; } if (!data->pktlen) { grub_uint8_t type; if (grub_file_read (data->file, &type, sizeof (type)) != sizeof(type)) return grub_errno; data->is_rle = !!(type & 0x80); data->pktlen = (type & 0x7f) + 1; if (data->is_rle && grub_file_read (data->file, &data->pixel[0], data->bpp) != data->bpp) return grub_errno; } if (!data->is_rle && grub_file_read (data->file, &data->pixel[0], data->bpp) != data->bpp) return grub_errno; data->pktlen--; return GRUB_ERR_NONE; } static grub_err_t tga_load_palette (struct tga_data *data) { grub_size_t len = grub_le_to_cpu32 (data->hdr.color_map_length) * 3; if (len > sizeof (data->palette)) len = sizeof (data->palette); if (grub_file_read (data->file, &data->palette, len) != (grub_ssize_t) len) return grub_errno; #ifndef GRUB_CPU_WORDS_BIGENDIAN { grub_size_t i; for (i = 0; 3 * i < len; i++) { grub_uint8_t t; t = data->palette[i][0]; data->palette[i][0] = data->palette[i][2]; data->palette[i][2] = t; } } #endif return GRUB_ERR_NONE; } static grub_err_t tga_load_index_color (struct tga_data *data) { unsigned int x; unsigned int y; grub_uint8_t *ptr; for (y = 0; y < data->image_height; y++) { ptr = data->bitmap->data; if ((data->hdr.image_descriptor & GRUB_TGA_IMAGE_ORIGIN_TOP) != 0) ptr += y * data->bitmap->mode_info.pitch; else ptr += (data->image_height - 1 - y) * data->bitmap->mode_info.pitch; for (x = 0; x < data->image_width; x++) { grub_err_t err; err = fetch_pixel (data); if (err) return err; ptr[0] = data->palette[data->pixel[0]][0]; ptr[1] = data->palette[data->pixel[0]][1]; ptr[2] = data->palette[data->pixel[0]][2]; ptr += 3; } } return GRUB_ERR_NONE; } static grub_err_t tga_load_grayscale (struct tga_data *data) { unsigned int x; unsigned int y; grub_uint8_t *ptr; for (y = 0; y < data->image_height; y++) { ptr = data->bitmap->data; if ((data->hdr.image_descriptor & GRUB_TGA_IMAGE_ORIGIN_TOP) != 0) ptr += y * data->bitmap->mode_info.pitch; else ptr += (data->image_height - 1 - y) * data->bitmap->mode_info.pitch; for (x = 0; x < data->image_width; x++) { grub_err_t err; err = fetch_pixel (data); if (err) return err; ptr[0] = data->pixel[0]; ptr[1] = data->pixel[0]; ptr[2] = data->pixel[0]; ptr += 3; } } return GRUB_ERR_NONE; } static grub_err_t tga_load_truecolor_R8G8B8 (struct tga_data *data) { unsigned int x; unsigned int y; grub_uint8_t *ptr; for (y = 0; y < data->image_height; y++) { ptr = data->bitmap->data; if ((data->hdr.image_descriptor & GRUB_TGA_IMAGE_ORIGIN_TOP) != 0) ptr += y * data->bitmap->mode_info.pitch; else ptr += (data->image_height - 1 - y) * data->bitmap->mode_info.pitch; for (x = 0; x < data->image_width; x++) { grub_err_t err; err = fetch_pixel (data); if (err) return err; #ifdef GRUB_CPU_WORDS_BIGENDIAN ptr[0] = data->pixel[0]; ptr[1] = data->pixel[1]; ptr[2] = data->pixel[2]; #else ptr[0] = data->pixel[2]; ptr[1] = data->pixel[1]; ptr[2] = data->pixel[0]; #endif ptr += 3; } } return GRUB_ERR_NONE; } static grub_err_t tga_load_truecolor_R8G8B8A8 (struct tga_data *data) { unsigned int x; unsigned int y; grub_uint8_t *ptr; for (y = 0; y < data->image_height; y++) { ptr = data->bitmap->data; if ((data->hdr.image_descriptor & GRUB_TGA_IMAGE_ORIGIN_TOP) != 0) ptr += y * data->bitmap->mode_info.pitch; else ptr += (data->image_height - 1 - y) * data->bitmap->mode_info.pitch; for (x = 0; x < data->image_width; x++) { grub_err_t err; err = fetch_pixel (data); if (err) return err; #ifdef GRUB_CPU_WORDS_BIGENDIAN ptr[0] = data->pixel[0]; ptr[1] = data->pixel[1]; ptr[2] = data->pixel[2]; ptr[3] = data->pixel[3]; #else ptr[0] = data->pixel[2]; ptr[1] = data->pixel[1]; ptr[2] = data->pixel[0]; ptr[3] = data->pixel[3]; #endif ptr += 4; } } return GRUB_ERR_NONE; } static grub_err_t grub_video_reader_tga (struct grub_video_bitmap **bitmap, const char *filename) { grub_ssize_t pos; struct tga_data data; grub_memset (&data, 0, sizeof (data)); data.file = grub_buffile_open (filename, 0); if (! data.file) return grub_errno; /* TGA Specification states that we SHOULD start by reading ID from end of file, but we really don't care about that as we are not going to support developer area & extensions at this point. */ /* Read TGA header from beginning of file. */ if (grub_file_read (data.file, &data.hdr, sizeof (data.hdr)) != sizeof (data.hdr)) { grub_file_close (data.file); return grub_errno; } /* Skip ID field. */ pos = grub_file_tell (data.file); pos += data.hdr.id_length; grub_file_seek (data.file, pos); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (data.file); return grub_errno; } grub_dprintf("tga", "tga: header\n"); dump_int_field(data.hdr.id_length); dump_int_field(data.hdr.color_map_type); dump_int_field(data.hdr.image_type); dump_int_field(data.hdr.color_map_first_index); dump_int_field(data.hdr.color_map_length); dump_int_field(data.hdr.color_map_bpp); dump_int_field(data.hdr.image_x_origin); dump_int_field(data.hdr.image_y_origin); dump_int_field(data.hdr.image_width); dump_int_field(data.hdr.image_height); dump_int_field(data.hdr.image_bpp); dump_int_field(data.hdr.image_descriptor); data.image_width = grub_le_to_cpu16 (data.hdr.image_width); data.image_height = grub_le_to_cpu16 (data.hdr.image_height); /* Check that bitmap encoding is supported. */ switch (data.hdr.image_type) { case GRUB_TGA_IMAGE_TYPE_RLE_TRUECOLOR: case GRUB_TGA_IMAGE_TYPE_RLE_BLACK_AND_WHITE: case GRUB_TGA_IMAGE_TYPE_RLE_INDEXCOLOR: data.uses_rle = 1; break; case GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_TRUECOLOR: case GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_BLACK_AND_WHITE: case GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_INDEXCOLOR: data.uses_rle = 0; break; default: grub_file_close (data.file); return grub_error (GRUB_ERR_BAD_FILE_TYPE, "unsupported bitmap format (unknown encoding %d)", data.hdr.image_type); } data.bpp = data.hdr.image_bpp / 8; /* Check that bitmap depth is supported. */ switch (data.hdr.image_type) { case GRUB_TGA_IMAGE_TYPE_RLE_BLACK_AND_WHITE: case GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_BLACK_AND_WHITE: if (data.hdr.image_bpp != 8) { grub_file_close (data.file); return grub_error (GRUB_ERR_BAD_FILE_TYPE, "unsupported bitmap format (bpp=%d)", data.hdr.image_bpp); } grub_video_bitmap_create (bitmap, data.image_width, data.image_height, GRUB_VIDEO_BLIT_FORMAT_RGB_888); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (data.file); return grub_errno; } data.bitmap = *bitmap; /* Load bitmap data. */ tga_load_grayscale (&data); break; case GRUB_TGA_IMAGE_TYPE_RLE_INDEXCOLOR: case GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_INDEXCOLOR: if (data.hdr.image_bpp != 8 || data.hdr.color_map_bpp != 24 || data.hdr.color_map_first_index != 0) { grub_file_close (data.file); return grub_error (GRUB_ERR_BAD_FILE_TYPE, "unsupported bitmap format (bpp=%d)", data.hdr.image_bpp); } grub_video_bitmap_create (bitmap, data.image_width, data.image_height, GRUB_VIDEO_BLIT_FORMAT_RGB_888); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (data.file); return grub_errno; } data.bitmap = *bitmap; /* Load bitmap data. */ tga_load_palette (&data); tga_load_index_color (&data); break; case GRUB_TGA_IMAGE_TYPE_RLE_TRUECOLOR: case GRUB_TGA_IMAGE_TYPE_UNCOMPRESSED_TRUECOLOR: switch (data.hdr.image_bpp) { case 24: grub_video_bitmap_create (bitmap, data.image_width, data.image_height, GRUB_VIDEO_BLIT_FORMAT_RGB_888); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (data.file); return grub_errno; } data.bitmap = *bitmap; /* Load bitmap data. */ tga_load_truecolor_R8G8B8 (&data); break; case 32: grub_video_bitmap_create (bitmap, data.image_width, data.image_height, GRUB_VIDEO_BLIT_FORMAT_RGBA_8888); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (data.file); return grub_errno; } data.bitmap = *bitmap; /* Load bitmap data. */ tga_load_truecolor_R8G8B8A8 (&data); break; default: grub_file_close (data.file); return grub_error (GRUB_ERR_BAD_FILE_TYPE, "unsupported bitmap format (bpp=%d)", data.hdr.image_bpp); } } /* If there was a loading problem, destroy bitmap. */ if (grub_errno != GRUB_ERR_NONE) { grub_video_bitmap_destroy (*bitmap); *bitmap = 0; } grub_file_close (data.file); return grub_errno; } #if defined(TGA_DEBUG) static grub_err_t grub_cmd_tgatest (grub_command_t cmd_d __attribute__ ((unused)), int argc, char **args) { struct grub_video_bitmap *bitmap = 0; if (argc != 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); grub_video_reader_tga (&bitmap, args[0]); if (grub_errno != GRUB_ERR_NONE) return grub_errno; grub_video_bitmap_destroy (bitmap); return GRUB_ERR_NONE; } #endif static struct grub_video_bitmap_reader tga_reader = { .extension = ".tga", .reader = grub_video_reader_tga, .next = 0 }; GRUB_MOD_INIT(tga) { grub_video_bitmap_reader_register (&tga_reader); #if defined(TGA_DEBUG) cmd = grub_register_command ("tgatest", grub_cmd_tgatest, "FILE", "Tests loading of TGA bitmap."); #endif } GRUB_MOD_FINI(tga) { #if defined(TGA_DEBUG) grub_unregister_command (cmd); #endif grub_video_bitmap_reader_unregister (&tga_reader); }