/* render/tgafile.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ /* Documentation and examples on the TGA file format: http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf http://www.fileformat.info/format/tga/egff.htm http://www.morrowland.com/apron/tut_gl.php Notes TGA image type Colormap RLE 0 No image data included in file No No 1 Colormapped image data Yes No 2 Truecolor image data No No 3 Monochrome image data No No 9 Colormapped image data Yes Yes 10 Truecolor image data No Yes 11 Monochrome image data No Yes TGA multi-byte integer values have LSB first */ #include #include #include #include #include "sys/sys.h" #include "filesystem/filesystem.h" #include "render/tgafile.h" const unsigned char TGA_NONE = 0; const unsigned char TGA_TRUECOLOR = 2; const unsigned char TGA_TRUECOLOR_RLE = 10; namespace render { Image *TGA::load(const char *filename) { Image *image = 0; if (!filename) return 0; filesystem::File *tga_file = filesystem::open(filename); if (!tga_file) { //con_warn << "Could not open " << filename << std::endl; return 0; } // TGA header unsigned char header[18]; memset(header, 0, sizeof(header)); if (!tga_file->read(header, 18)) { con_warn << "Error reading " << filename << std::endl; filesystem::close(tga_file); return 0; } // byte 0 - image ID field length unsigned int tga_idlength = header[0]; // byte 1 - color map type unsigned int tga_colormap = header[1]; // byte 2 - image type unsigned int tga_type = header[2]; // byte 3+4 - color map first entry index //unsigned int tga_colormap_first = header[3] + (header[4] << 8 ); // byte 5+6 - color map length (in bits) unsigned int tga_color_map_length = header[5] + (header[6] << 8); // byte 7 - color map entry length unsigned int tga_colormap_entry = header[7]; // byte 8+9 - image x origin // byte 10+11 - image y origin // byte 12+13 - image width (LSB first) unsigned int tga_width = header[12] + (header[13] << 8); // byte 14+15 - image height (LSB first) unsigned int tga_height = header[14] + (header[15] << 8); // byte 16 - image color depth (in bits) unsigned int tga_depth = header[16]; // byte 17 - image descriptor byte unsigned int tga_descriptor = header[17]; // read the image id if there is one if (tga_idlength) tga_file->skip(tga_idlength); // read color map data (even for non-color mapped images) if (tga_colormap) { if (tga_colormap > 1) con_warn << filename << ": invalid color map type!" << std::endl; tga_file->skip(tga_color_map_length*tga_colormap_entry); } // FIXME channels should be a sane value unsigned int index = 0; unsigned int channels = tga_depth / 8; switch (tga_type) { case TGA_NONE: con_warn << "Error reading " << filename << ": no image data!" << std::endl; filesystem::close(tga_file); return 0; break; case TGA_TRUECOLOR: if ((tga_depth == 24) || (tga_depth == 32)) { image = new Image(tga_width, tga_height, channels); for (size_t i = 0; i < tga_width * tga_height; i++) { tga_file->read((void *)(*image)[i*(size_t)channels], channels); } image->swap_channels(); } else if (tga_depth == 16) { channels = 3; image = new Image(tga_width, tga_height, channels); for (size_t i = 0; i < tga_width * tga_height; i++) { // unpack one pixel unsigned char pixel_data[2]; tga_file->read((void *)pixel_data, 2); unsigned int unpacked = pixel_data[0] + pixel_data[1] * 0xff; unsigned int b = (unpacked & 0x1f) << 3; unsigned int g = ((unpacked >> 5) & 0x1f) << 3; unsigned int r = ((unpacked >> 10) & 0x1f) << 3; // store it image->ptr()[i * channels] = (unsigned char) b; image->ptr()[i * channels+1] = (unsigned char) g; image->ptr()[i * channels+2] = (unsigned char) r; } } else { con_warn << "Error reading " << filename << ": unsupported image depth '" << tga_depth << "'!" << std::endl; filesystem::close(tga_file); return 0; } break; case TGA_TRUECOLOR_RLE: image = new Image(tga_width, tga_height, channels); while (index < tga_width * tga_height) { unsigned char rle = 0; unsigned char pixel_data[4]; // read RLE packet byte tga_file->read(&rle, 1); if (rle < 128) { rle++; // rle contains the number of pixels-1 tga_file->read((void *)(*image)[index*channels], rle*channels); index += rle; } else { rle -= 127; // rle contains 128 + the number of identical pixels-1 tga_file->read(pixel_data, channels); while (rle > 0) { memcpy((void *)(*image)[index*channels], (void *)pixel_data, channels); index++; rle--; } } } image->swap_channels(); break; default: con_warn << "Error reading " << filename << ": unsupported TGA type '" << (int) tga_type << "'!" << std::endl; filesystem::close(tga_file); return 0; } filesystem::close(tga_file); if ((tga_descriptor & 0x20) == 0x0) { // origin at bottom left image->flip_vertical(); } if ((tga_descriptor & 0x10) == 0x10) { con_warn << filename << ": descriptor bit 4 (left-right) set!" << std::endl; } con_debug << " " << filename << " " << image->width() << "x" << image->height() << "x" << image->bpp() << "bpp" << std::endl; return image; } void TGA::save(const char *filename, Image & image) { if (!filename) return; std::ofstream ofs(filename, std::ios_base::out | std::ios_base::binary); if (!ofs.is_open()) { con_warn << "Could not write " << filename << std::endl; return; } // write TGA header unsigned char header[18]; memset(header, 0, sizeof(header)); // byte 0 - image ID field length = 0 (no image ID field present) // byte 1 - color map type = 0 (no palette present) // byte 2 - image type = 10 (truecolor RLE encoded) header[2] = TGA_TRUECOLOR_RLE; // byte 3-11 - palette data (not used) // byte 12+13 - image width header[12] = (image.width() & 0xff); header[13] = ((image.width() >> 8) & 0xff); // byte 14+15 - image height header[14] = (image.height() & 0xff); header[15] = ((image.height() >> 8) & 0xff); // byte 16 - image color depth = 24 (RGB) or 32 (RGBA) header[16] = image.channels() * 8; // byte 17 - image descriptor byte = 0x20 (origin at bottom left) header[17] = 0x20; // write header ofs.write((char *)header, sizeof(header)); // write image data // TGA has the R and B channels switched unsigned char pixel_data[image.channels()]; unsigned char block_data[image.channels()*128]; unsigned char rle_packet; bool compress = false; size_t block_length = 0; for (int y = image.height() - 1; y >= 0; y--) { for (size_t x = 0; x < image.width(); x++) { size_t index = y * image.width() * image.channels() + x * image.channels(); pixel_data[0] = *image[index+2]; pixel_data[1] = *image[index+1]; pixel_data[2] = *image[index]; if (image.channels() == 4) pixel_data[3] = *image[index+3]; if (block_length == 0) { // if the block is empty, copy the first pixel memcpy(block_data, pixel_data, image.channels()); block_length++; // by default, the block is not compressed compress = false; } else { // if the current block is not compressed if (!compress) { // if the previous pixel differs from the pixel data if (memcmp(&block_data[(block_length-1)*image.channels()], pixel_data, image.channels()) != 0) { // append pixel memcpy(&block_data[block_length*image.channels()], pixel_data, image.channels()); block_length++; } else { // uncompressed block and pixel data is identical if (block_length > 1) { // write the uncompressed block rle_packet = block_length - 2; ofs.write((char *)&rle_packet, 1); ofs.write((char *)block_data, (block_length - 1) * image.channels()); block_length = 1; } // create a commpressed block memcpy(block_data, pixel_data, image.channels()); block_length++; compress = true; } // if the current block is compressed } else { // if the previous pixel and the pixel data are identical if (memcmp(block_data, pixel_data, image.channels()) == 0) { block_length++; } else { // compressed block and pixel data differs if (block_length > 1) { // write the compressed block rle_packet = block_length + 127; ofs.write((char *)&rle_packet, 1); ofs.write((char *)block_data, image.channels()); block_length = 0; } memcpy(&block_data[block_length * image.channels()], pixel_data, image.channels()); block_length++; compress = false; } } } if (block_length == 128) { rle_packet = block_length - 1; if (!compress) { ofs.write((char *)&rle_packet, 1); ofs.write((char *)block_data, 128 * image.channels()); } else { rle_packet += 128; ofs.write((char *)&rle_packet, 1); ofs.write((char *)block_data, image.channels()); } block_length = 0; compress = false; } } } // write remaining bytes if (block_length) { rle_packet = block_length - 1; if (!compress) { ofs.write((char *)&rle_packet, 1); ofs.write((char *)block_data, block_length * image.channels()); } else { rle_packet += 128; ofs.write((char *)&rle_packet, 1); ofs.write((char *)block_data, image.channels()); } } // write footer (optional, but the specification recommends it) char footer[26]; memset(footer, 0, sizeof(footer)); strncpy(&footer[8] , "TRUEVISION-XFILE", 16); footer[24] = '.'; footer[25] = 0; ofs.write(footer, sizeof(footer)); // close file ofs.close(); } }