From 3d993058e5078fbdfd92d479281ad93bb40a4bc6 Mon Sep 17 00:00:00 2001 From: Stijn Buys Date: Wed, 26 Mar 2008 23:24:26 +0000 Subject: improved TGA handling --- src/client/video.cc | 56 +------ src/render/Makefile.am | 4 +- src/render/gl.h | 1 + src/render/image.cc | 64 ++++++++ src/render/image.h | 56 +++++++ src/render/render.cc | 32 +++- src/render/tga.cc | 400 ++++++++++++++++++++++++++++++------------------- src/render/tga.h | 35 ++--- 8 files changed, 424 insertions(+), 224 deletions(-) create mode 100644 src/render/image.cc create mode 100644 src/render/image.h (limited to 'src') diff --git a/src/client/video.cc b/src/client/video.cc index e41f774..cd25d19 100644 --- a/src/client/video.cc +++ b/src/client/video.cc @@ -10,6 +10,7 @@ #include "client/video.h" #include "client/view.h" #include "render/render.h" +#include "render/tga.h" #include "core/core.h" #include "filesystem/filesystem.h" #include "sys/sys.h" @@ -181,59 +182,12 @@ void screenshot() } } while (!available); - std::ofstream ofs(filename.c_str()); + render::Image image((unsigned int)video::width, (unsigned int)video::height, 3); - if (!ofs.is_open()) { - con_warn << "Could not write " << shortname << std::endl; - return; - } + glReadPixels(0, 0, (GLsizei) video::width, (GLsizei) video::height, + GL_RGB, GL_UNSIGNED_BYTE, (void *) image.data()); - // TGA header - // TODO: RL-encoding, image ID - - // note: see http://www.fileformat.info/format/tga/egff.htm - unsigned char header[18]; - memset(header, 0, sizeof(header)); - - // byte 0 - image ID field lenght = 0 (no image ID field present) - // byte 1 - color map type = 0 (no palette present) - // byte 2 - image type = 2 (truecolor without RLE) - header[2] = 2; - // byte 3-11 - palette data (not used) - // byte 12+13 - image width - header[12] = (video::width & 0xff); - header[13] = ((video::width >> 8) & 0xff); - // byte 14+15 - image height - header[14] = (video::height & 0xff); - header[15] = ((video::height >> 8) & 0xff); - // byte 16 - image color depth = 24 (RGB) - header[16] = 24; - // byte 17 - image descriptor byte - header[17] = 0; - - // allocate buffer to hold the RGB data - unsigned char *rgb_data = (unsigned char *) malloc(video::width * video::height * 3); - - // read OpenGL pixels into the buffer - //glReadBuffer(GL_BACK); - glReadPixels(0, 0, (GLsizei) video::width, (GLsizei) video::height, GL_RGB, GL_UNSIGNED_BYTE, (void *) rgb_data); - - // either OpenGL actually returns BGR, or TGA wants BGR - for (size_t i = 0; i < (size_t) (video::width * video::height); i++) { - unsigned char tmp = rgb_data[i*3]; - rgb_data[i*3] = rgb_data[i*3+2]; - rgb_data[i*3+2] = tmp; - } - - ofs.write((char *)header, sizeof(header)); - ofs.write((char *)rgb_data, video::width * video::height * 3 ); - - // free the buffer - free(rgb_data); - - // close file - ofs.close(); - con_print << "Wrote " << shortname << std::endl; + render::TGA::save(filename.c_str(), image); } diff --git a/src/render/Makefile.am b/src/render/Makefile.am index 87f3ba3..75da9c7 100644 --- a/src/render/Makefile.am +++ b/src/render/Makefile.am @@ -3,5 +3,5 @@ METASOURCES = AUTO noinst_LTLIBRARIES = librender.la librender_la_LDFLAGS = -avoid-version -no-undefined @GL_LIBS@ librender_la_LIBADD = $(top_builddir)/src/math/libmath.la -librender_la_SOURCES = draw.cc gl.cc render.cc text.cc tga.cc -noinst_HEADERS = draw.h gl.h render.h text.h tga.h +librender_la_SOURCES = draw.cc gl.cc image.cc render.cc text.cc tga.cc +noinst_HEADERS = draw.h gl.h image.h render.h text.h tga.h diff --git a/src/render/gl.h b/src/render/gl.h index 9153435..69eb412 100644 --- a/src/render/gl.h +++ b/src/render/gl.h @@ -8,6 +8,7 @@ #define __INCLUDED_RENDER_GL_H__ #include +#include #include "math/vector3f.h" #include "math/color.h" diff --git a/src/render/image.cc b/src/render/image.cc new file mode 100644 index 0000000..7ce8113 --- /dev/null +++ b/src/render/image.cc @@ -0,0 +1,64 @@ +/* + render/image.cc + This file is part of the Osirion project and is distributed under + the terms of the GNU General Public License version 2 +*/ + +#include +#include + +#include "render/image.h" + +namespace render +{ + +Image::Image(unsigned int width, unsigned int height, unsigned int channels) +{ + image_width = width; + image_height = height; + image_channels = channels; + + image_data = (unsigned char *) malloc(size()); + clear(); +} + +Image::~Image() +{ + free(image_data); +} + +void Image::clear() +{ + memset(image_data, 0, size()); +} + +void Image::swap_channels() +{ + for (size_t y =0; y < image_height; y++) { + for (size_t x = 0; x < image_width; x++) { + size_t offset = y * image_width * image_channels + x * image_channels; + unsigned char tmp = image_data[offset]; + image_data[offset] = image_data[offset + 2]; + image_data[offset + 2] = tmp; + } + } +} + +void Image::flip() +{ + unsigned char line[image_width*image_channels]; + for (size_t y=0; y < image_height /2; y++) { + memcpy(line, + &image_data[y*image_width*image_channels], + image_width*image_channels); + memcpy(&image_data[y*image_width*image_channels], + &image_data[(image_height-1-y)*image_width*image_channels], + image_width*image_channels); + memcpy(&image_data[(image_height-1-y)*image_width*image_channels], + line, + image_width*image_channels); + } +} + +} + diff --git a/src/render/image.h b/src/render/image.h new file mode 100644 index 0000000..0c1069b --- /dev/null +++ b/src/render/image.h @@ -0,0 +1,56 @@ +/* + render/image.h + This file is part of the Osirion project and is distributed under + the terms of the GNU General Public License version 2 +*/ + +#ifndef __INCLUDED_RENDER_IMAGE_H__ +#define __INCLUDED_RENDER_IMAGE_H__ + +namespace render { + +/// RGB (24bpp) or RGBA (32bpp) image data +class Image { +public: + Image(unsigned int width, unsigned int height, unsigned int channels); + ~Image(); + + // pointer to the image data + inline unsigned char *data() { return image_data; } + + // index into the image data + inline unsigned char *operator[](size_t index) { return &image_data[index]; } + + // width of the image in pixels + inline unsigned int width() const { return image_width; } + + // height of the image in pixels + inline unsigned int height() const { return image_height; } + + // size of the image data in bytes + inline size_t size() const { return ((size_t) image_width * (size_t) image_height * (size_t) image_channels); } + + // number of channels 3 (RGB) or 4 (RGBA) + inline unsigned int channels() const { return image_channels; } + + // set image data to zero + void clear(); + + // swap the red and blue channel values of the image + void swap_channels(); + + // flip upside-down + void flip(); + +private: + unsigned char *image_data; + + unsigned int image_width; + unsigned int image_height; + unsigned int image_channels; +}; + +} + +#endif // __INCLUDED_RENDER_IMAGE_H__ + diff --git a/src/render/render.cc b/src/render/render.cc index 15a6d5c..3a9ebd3 100644 --- a/src/render/render.cc +++ b/src/render/render.cc @@ -10,6 +10,8 @@ #include #include "render/render.h" +#include "render/tga.h" +#include "render/gl.h" #include "core/core.h" #include "filesystem/filesystem.h" #include "sys/sys.h" @@ -22,6 +24,32 @@ core::Cvar *r_drawradius = 0; core::Cvar *r_drawstats = 0; core::Cvar *r_wireframe = 0; +bool texture(char * const filename, size_t id) +{ + Image *image = TGA::load(filename); + if (!image) + return false; + + // FIXME - I don't get this part... yet + + glGenTextures(1, &textures[id]); + glBindTexture(GL_TEXTURE_2D, textures[id]); + + int texture_type; + if (image->channels() == 4) + texture_type = GL_RGBA; + else + texture_type = GL_RGB; + + gluBuild2DMipmaps(GL_TEXTURE_2D, image->channels(), + image->width(), image->height(), texture_type, GL_UNSIGNED_BYTE, image->data()); + + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); + + return true; +} + void init() { con_print << "Initializing renderer..." << std::endl; @@ -32,11 +60,11 @@ void init() con_print << "Loading textures..." << std::endl; - if (!TGA::texture(textures, "bitmaps/loader.tga", 0)) { + if (!texture( "bitmaps/loader.tga", 0)) { con_error << "Essential file bitmaps/loader.tga missing" << std::endl; core::application()->shutdown(); } - if (!TGA::texture(textures, "bitmaps/conchars.tga", 1)) { + if (!texture("bitmaps/conchars.tga", 1)) { con_error << "Essential file bitmaps/conchars.tga missing" << std::endl; core::application()->shutdown(); } diff --git a/src/render/tga.cc b/src/render/tga.cc index 0eb2ef6..c7f4fc1 100644 --- a/src/render/tga.cc +++ b/src/render/tga.cc @@ -1,185 +1,285 @@ /* - Ronny Andr�Reierstad - http://www.morrowland.com - http://www.morrowland.com/apron/tut_gl.php - apron@morrowland.com + render/tga.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 "filesystem/filesystem.h" #include "render/tga.h" #include "sys/sys.h" -#include "GL/gl.h" -#include + +const unsigned char TGA_NONE = 0; +const unsigned char TGA_TRUECOLOR = 2; +const unsigned char TGA_TRUECOLOR_RLE = 10; namespace render { -///////////////////////////////////////////////////////////////////////////////////////////////// -// TGA TEXTURE LOADER -///////////////////////////////////////////////////////////////////////////////////////////////// -bool TGA::texture(GLuint textureArray[], const char *filename, int ID) +Image *TGA::load(const char *filename) { + Image *image = 0; + if (!filename) - return false; - - image *pBitMap = load(filename); - if (!pBitMap) { - //con_warn << "Could not load " << filename << std::endl; - return false; + return 0; + + filesystem::File *tga_file = filesystem::open(filename); + if (!tga_file) { + con_warn << "Could not open " << filename << std::endl; + return 0; } - - glGenTextures(1, &textureArray[ID]); - glBindTexture(GL_TEXTURE_2D, textureArray[ID]); - int textureType = GL_RGB; - if (pBitMap->channels == 4) textureType = GL_RGBA; - gluBuild2DMipmaps(GL_TEXTURE_2D, pBitMap->channels, pBitMap->size_x, pBitMap->size_y, textureType, GL_UNSIGNED_BYTE, pBitMap->data); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); - - if (pBitMap) { - if (pBitMap->data) { - free(pBitMap->data); - } - free(pBitMap); + + // 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; } - return true; -} + // byte 0 - image ID field lenght + unsigned int tga_idlength = header[0]; + // byte 1 - color map type + unsigned int tga_colormap = header[1]; -TGA::image *TGA::load(const char *filename) -{ - image *pImgData = NULL; - //FILE *pFile = NULL; - GLushort width = 0; - GLushort height = 0; - GLubyte length = 0; - GLubyte imgType = 0; - GLubyte bits = 0; - GLushort channels = 0; - GLushort stride = 0; - - filesystem::File *f = filesystem::open(filename); - if (!f) - return 0; - - pImgData = (image*)malloc(sizeof(image)); - pImgData->data = 0; - - f->read((void *)&length, sizeof(GLubyte)); - f->skip(1); - f->read((void *)&imgType, sizeof(GLubyte)); - f->skip(9); - f->read((void *)&width, sizeof(GLushort)); - f->read((void *)&height, sizeof(GLushort)); - f->read((void *)&bits, sizeof(GLubyte)); - f->skip(length +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]; - con_debug << " " << filename << " " << width << "x" << height << "x" << (int) bits << "bpp" << std::endl; + con_debug << " " << filename << " " + << tga_width << "x" << tga_height << "x" << tga_depth << "bpp" << std::endl; - if (imgType != TGA_RLE) { - // Check for 24 or 32 Bit - if (bits == 24 || bits == 32) { + // 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; - channels = bits / 8; - stride = channels * width; - pImgData->data = new unsigned char[stride * height]; - - for (GLushort y = 0; y < height; y++) { - unsigned char *pLine = &(pImgData->data[stride * y]); - - f->read((void *)pLine, stride); - - for (GLushort i = 0; i < stride; i += channels) { - int temp = pLine[i]; - pLine[i] = pLine[i + 2]; - pLine[i + 2] = temp; - } + 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); } - } - - // Check for 16 Bit - else if (bits == 16) { - unsigned short pixels = 0; - int r=0, g=0, b=0; + + image->swap_channels(); + + } else if (tga_depth == 16) { channels = 3; - stride = channels * width; - pImgData->data = new unsigned char[stride * height]; - - for (int i = 0; i < width*height; i++) { - f->read((void *)&pixels, sizeof(GLushort)); - - b = (pixels & 0x1f) << 3; - g = ((pixels >> 5) & 0x1f) << 3; - r = ((pixels >> 10) & 0x1f) << 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; - pImgData->data[i * 3 + 0] = r; - pImgData->data[i * 3 + 1] = g; - pImgData->data[i * 3 + 2] = b; + // 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 - return NULL; - } else { - GLubyte rleID = 0; - int colorsRead = 0; - channels = bits / 8; - stride = channels * width; - - pImgData->data = new unsigned char[stride * height]; - GLubyte *pColors = new GLubyte [channels]; - - int i = 0; - while (i < width*height) { + } else { + con_warn << "Error reading " << filename + << ": unsupported image depth '" << tga_depth << "'!" << std::endl; + filesystem::close(tga_file); + return 0; + } - f->read((void *)&rleID, sizeof(GLubyte)); - + 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[channels]; + + // read RLE packet byte + tga_file->read(&rle, 1); - if (rleID < 128) { - rleID++; - - while (rleID) { - f->read((void *)pColors, sizeof(GLubyte) * channels); - - pImgData->data[colorsRead + 0] = pColors[2]; - pImgData->data[colorsRead + 1] = pColors[1]; - pImgData->data[colorsRead + 2] = pColors[0]; - - if (bits == 32) pImgData->data[colorsRead + 3] = pColors[3]; - - i++; - rleID--; - colorsRead += channels; - } + if (rle < 128) { + rle++; // rle contains the number of pixels-1 + tga_file->read((void *)(*image)[index*channels], rle*channels); + index += rle; + } else { - rleID -= 127; - - f->read((void *)pColors, sizeof(GLubyte) * channels); - - while (rleID) { - pImgData->data[colorsRead + 0] = pColors[2]; - pImgData->data[colorsRead + 1] = pColors[1]; - pImgData->data[colorsRead + 2] = pColors[0]; - - if (bits == 32) pImgData->data[colorsRead + 3] = pColors[3]; - - i++; - rleID--; - colorsRead += channels; + 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--; } } } - delete pColors; - } - - filesystem::close(f); - - - pImgData->channels = channels; - pImgData->size_x = width; - pImgData->size_y = height; + + image->swap_channels(); + + break; - return pImgData; + 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; + } + + return image; +} + +void TGA::save(const char *filename, Image & image) +{ + if (!filename) + return; + + std::ofstream ofs(filename); + + 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 lenght = 0 (no image ID field present) + // byte 1 - color map type = 0 (no palette present) + // byte 2 - image type = 2 (truecolor without RLE) + header[2] = TGA_TRUECOLOR; + // 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 buf[image.channels()]; + for (int y = image.height()-1; y >= 0; y--) { + for (size_t x = 0; x < image.width(); x++) { + buf[0] = *image[y*image.width()*image.channels()+x*image.channels()+2]; + buf[1] = *image[y*image.width()*image.channels()+x*image.channels()+1]; + buf[2] = *image[y*image.width()*image.channels()+x*image.channels()]; + + if (image.channels() == 4) + buf[3] = *image[y*image.width()*image.channels()+x*image.channels()+3]; + + ofs.write((char *)buf, 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; } } diff --git a/src/render/tga.h b/src/render/tga.h index d4449a8..0da65fb 100644 --- a/src/render/tga.h +++ b/src/render/tga.h @@ -1,36 +1,33 @@ /* - www.morrowland.com - apron@morrowland.com + render/tga.h + This file is part of the Osirion project and is distributed under + the terms of the GNU General Public License version 2 */ + #ifndef _INCLUDED_RENDER_TGA_H__ #define _INCLUDED_RENDER_TGA_H__ -#include "GL/gl.h" - -#define TGA_RGB 2 -#define TGA_A 3 -#define TGA_RLE 10 +#include "render/image.h" namespace render { class TGA { + public: - typedef struct { - int channels; - int size_x; - int size_y; - unsigned char *data; - } image; - - - static bool texture(GLuint textureArray[], const char *filename, int textureID); - -protected: - static image *load(const char *filename); + /// load a TGA image from disk + /** @param filename short path to the filename to be loaded + */ + static Image *load(const char * filename); + + /// write an image to a TGA file + /** @param filename short path to the file to write the image data to + */ + static void save(const char *filename, Image & image); }; } #endif //_INCLUDED_RENDER_TGA_H__ + -- cgit v1.2.3