diff options
author | Stijn Buys <ingar@osirion.org> | 2008-11-11 19:11:57 +0000 |
---|---|---|
committer | Stijn Buys <ingar@osirion.org> | 2008-11-11 19:11:57 +0000 |
commit | 773c1bafe0f1d8b706e0f72e235f8466e7a9ccf5 (patch) | |
tree | ab4b3058f436a4e4c54618f132a9179ee40e330c /src/render/tgafile.cc | |
parent | 3082cb197fb6af7d069f9ad211ff6ea5657d924a (diff) |
cleanups
Diffstat (limited to 'src/render/tgafile.cc')
-rw-r--r-- | src/render/tgafile.cc | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/src/render/tgafile.cc b/src/render/tgafile.cc new file mode 100644 index 0000000..5a27648 --- /dev/null +++ b/src/render/tgafile.cc @@ -0,0 +1,367 @@ +/* + 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 <string.h> + +#include <fstream> +#include <sstream> +#include <string> + +#include "filesystem/filesystem.h" +#include "render/tgafile.h" +#include "sys/sys.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); + } + + 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->data()[i * channels] = (unsigned char) b; + image->data()[i * channels+1] = (unsigned char) g; + image->data()[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[3]; + + // 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) { + 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(); + } + + 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) { + memcpy(block_data, pixel_data, image.channels()); + block_length++; + compress = false; + } else { + if (!compress) { + + // uncompressed block and pixel_data differs from the last pixel + 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; + } + memcpy(block_data, pixel_data, image.channels()); + block_length++; + compress = true; + } + + } else { + + // compressed block and pixel data is 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(); + + con_print << "Wrote " << filename << std::endl; +} + +} |