#include "CImageLoaderBLP.h"
#ifdef _IRR_COMPILE_WITH_BLP_LOADER_
#include <IVideoDriver.h>
#include <IReadFile.h>
#include <IImage.h>
#include <irrString.h>
#include <iostream>
#include "jpeglib.h"
namespace irr
{
namespace video
{
struct sharedheader
{
u32 compression;
u32 flags;
u32 width;
u32 height;
u32 alpha_depth;
u32 mipmap_offsets[16];
u32 mipmap_lengths[16];
u32 palette[265];
};
struct blpheader
{
u32 compression;
u32 flags;
u32 width;
u32 height;
u32 alpha_depth;
u32 pictureSubType;
u32 mipmap_offsets[16];
u32 mipmap_lengths[16];
};
struct blp2header
{
u32 type; // 0 = JPG, 1 = BLP / DXTC / Uncompressed
u8 compression; // 1 = BLP, 2 = DXTC, 3 = Uncompressed
u8 alpha_depth; // 0, 1, 4, or 8
u8 alpha_type; // 0, 1, 7, or 8
u8 has_mips; // 0 = no mips, 1 = has mips
u32 width; // Image width in pixels, usually a power of 2
u32 height; // Image height in pixels, usually a power of 2
u32 mipmap_offsets[16]; // The file offsets of each mipmap, 0 for unused
u32 mipmap_lengths[16]; // The length of each mipmap data block
};
CImageLoaderBLP::CImageLoaderBLP(irr::video::IVideoDriver *d) : driver(d)
{
#ifdef _DEBUG
setDebugName("CImageLoaderBLP");
#endif
}
//! destructor
CImageLoaderBLP::~CImageLoaderBLP()
{
}
bool CImageLoaderBLP::isALoadableFileExtension(const io::path& filename) const
{
return core::hasFileExtension(filename, "blp");
}
bool CImageLoaderBLP::isALoadableFileFormat(io::IReadFile* file) const
{
if(!file)
return false;
char buf[4];
file->read(buf, 4);
return buf[0] == 'B' && buf[1] == 'L' && buf[2] == 'P' &&
(buf[3] == '2' || buf[3] == '1' || buf[3] == '0');
}
IImage* CImageLoaderBLP::loadImage(io::IReadFile* file) const
{
if(!file)
return 0;
char blpid[4];
file->read(blpid, 4);
if(blpid[0] != 'B' || blpid[1] != 'L' || blpid[2] != 'P' ||
!(blpid[3] == '2' || blpid[3] == '1' || blpid[3] == '0'))
return 0;
sharedheader shd;
if(blpid[3] < '2')
{
std::cout << "war3 revision" << std::endl;
blpheader header;
file->read(&header, sizeof(blpheader));
shd.compression = header.compression;
shd.flags = header.flags;
shd.width = header.width;
shd.height = header.height;
shd.alpha_depth = header.alpha_depth;
for(int i = 0; i<16; ++i)
{
shd.mipmap_offsets[i] = header.mipmap_offsets[i];
shd.mipmap_lengths[i] = header.mipmap_lengths[i];
}
}
else
{
std::cout << "wow revision" << std::endl;
blp2header header;
file->read(&header, sizeof(blp2header));
shd.flags = header.alpha_type;
shd.width = header.width;
shd.height = header.height;
shd.alpha_depth = header.alpha_depth;
for(int i = 0; i<16; ++i)
{
shd.mipmap_offsets[i] = header.mipmap_offsets[i];
shd.mipmap_lengths[i] = header.mipmap_lengths[i];
}
if(header.type == 0)
shd.compression = JPG;
else
{
switch(header.compression)
{
case 1: shd.compression = BLP; break;
case 2: shd.compression = DXT; break;
default: shd.compression = RAW; break;
}
shd.flags = header.alpha_depth;
}
}
std::cout << "compression: " << std::to_string(shd.compression) << std::endl;
// only the first mipmap is loader, other can be generated by the engine
switch(shd.compression)
{
case JPG:
{
u32 jpegHeaderSize = 0;
file->read(&jpegHeaderSize, 4);
char *data = new char[shd.mipmap_lengths[0] + jpegHeaderSize];
file->read(data, jpegHeaderSize);
file->seek(shd.mipmap_offsets[0]);
file->read(data+jpegHeaderSize, shd.mipmap_lengths[0]);
return decompressJpg(shd, data, shd.mipmap_lengths[0] + jpegHeaderSize);
}
break;
case BLP:
{
file->read(shd.palette, 256*sizeof(u32));
for(int i = 0; i<256; ++i)
{
// from rgba
SColor c = shd.palette[i];
shd.palette[i] = SColor(
255-c.getRed(),
c.getGreen(),
c.getBlue(),
c.getAlpha()
).color;
}
file->seek(shd.mipmap_offsets[0]);
char *data = new char[shd.mipmap_lengths[0]];
file->read(data, shd.mipmap_lengths[0]);
return decompressBLP(shd, data, shd.mipmap_lengths[0]);
}
break;
case DXT:
{
file->seek(shd.mipmap_offsets[0]);
char *data = new char[shd.mipmap_lengths[0]];
file->read(data, shd.mipmap_lengths[0]);
return decompressDXT(shd, data, shd.mipmap_lengths[0]);
}
break;
default:
{
file->seek(shd.mipmap_offsets[0]);
char *data = new char[shd.mipmap_lengths[0]];
file->read(data, shd.mipmap_lengths[0]);
return decompressRaw(shd, data, shd.mipmap_lengths[0]);
}
break;
}
return 0;
}
IImage* CImageLoaderBLP::decompressBLP(const sharedheader &header, char *data, size_t size) const
{
IImage *tmp = driver->createImage(
ECF_A8R8G8B8,
core::dimension2du(header.width, header.height)
);
for(int height = 0; height < header.height; ++height)
{
for(int width = 0; width < header.width; ++width)
{
unsigned char index = (unsigned char)data[width + header.width*height];
tmp->setPixel(width, height, SColor(header.palette[index]));
}
}
if(header.alpha_depth == 3 || header.alpha_depth == 4)
{
char *alpha = data + header.width*header.height;
for(int height = 0; height < header.height; ++height)
{
for(int width = 0; width < header.width; ++width)
{
SColor c(tmp->getPixel(width, height));
c.setAlpha(alpha[width + header.width*height]);
tmp->setPixel(width, height, c);
}
}
}
delete [] data;
return tmp;
}
IImage* CImageLoaderBLP::decompressDXT(const sharedheader &header, char *data, size_t size) const
{
ECOLOR_FORMAT dxt;
// untested section (no file found)
// the doc tell that the header.palette is used to compute DXTC
// but I suppose that irrlicht DXTC does not require it
if(header.flags == 0)
dxt = ECF_DXT1;
if(header.flags == 1)
dxt = ECF_DXT3;
if(header.flags == 7)
dxt = ECF_DXT5;
return driver->createImageFromData(
dxt,
core::dimension2du(header.width, header.height),
data, true, true
);
}
IImage* CImageLoaderBLP::decompressRaw(const sharedheader &header, char *data, size_t size) const
{
ECOLOR_FORMAT dxt;
if(header.alpha_depth == 0)
dxt = ECF_R8G8B8;
else dxt = ECF_A8R8G8B8;
// untested section (no file found)
IImage *tmp = driver->createImageFromData(
ECF_A8R8G8B8,
core::dimension2du(header.width, header.height),
data, true, true
);
return tmp;
}
IImage* CImageLoaderBLP::decompressJpg(const sharedheader &header, char *buffer, size_t size) const
{
struct jpeg_error_mgr jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, (unsigned char*)buffer, size);
if(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK ||
!jpeg_start_decompress(&cinfo))
{
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return 0;
}
IImage *tmp = driver->createImage(
ECF_A8R8G8B8,
core::dimension2du(cinfo.image_width, cinfo.image_height)
);
const JDIMENSION requiredScanlines = cinfo.output_height;
const JDIMENSION scanlineSize = cinfo.output_width * cinfo.output_components;
JSAMPARRAY scanlines = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, scanlineSize, requiredScanlines);
while(cinfo.output_scanline < cinfo.output_height)
{
const JDIMENSION currentScanline = cinfo.output_scanline;
const JDIMENSION dimension = jpeg_read_scanlines(&cinfo, scanlines, requiredScanlines);
for(int height = 0; height < dimension; ++height)
{
int width = 0;
for(int component = 0; component < scanlineSize; component += cinfo.output_components)
{
u32 argb = ((u32)scanlines[height][component]) |
((u32)scanlines[height][component + 1] << 8) |
((u32)scanlines[height][component + 2] << 16);
if(cinfo.output_components == 4)
argb |= ((u32)(scanlines[height][component + 3]) << 24);
tmp->setPixel(width, height + currentScanline, argb);
++width;
}
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return tmp;
}
}
}
#endif