BLP image loader

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Ovan
Posts: 70
Joined: Thu Dec 18, 2008 12:41 am
Contact:

BLP image loader

Post by Ovan »

This is a complet BLP image loader (used by warcraft 3, warcraft 3 the frozen throne, and wow, matching the revision 0, 1, 2)
this loader accept all feature of the format, but only the first mipmap data is loaded due to engine architecture,
but the mipmap level can be regenerated when swithing from image to texture with irrlicht driver feature

for compilation I suggest to add the jpeglib folder from the irrlicht source folder to match the header information

Code: Select all

 
#ifndef __C_BLP_IMAGE_LOADER_H_INCLUDED__
#define __C_BLP_IMAGE_LOADER_H_INCLUDED__
 
/**
 * Copyright (C) <2014>
 * Ovan/Magun contact on irrlicht-fr.org or ovan@sleek-think.ovh
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
**/
 
#include "IrrCompileConfig.h"
#define _IRR_COMPILE_WITH_BLP_LOADER_
 
// force plugin compilation
// guard efined if loader included to the engine
#ifdef _IRR_COMPILE_WITH_BLP_LOADER_
 
#include <IImageLoader.h>
 
namespace irr
{
    namespace video
    {
        struct sharedheader;
        class IVideoDriver;
        class CImageLoaderBLP : public IImageLoader
        {
            protected:
                enum Compression : int
                {
                    JPG = 0,
                    BLP = 1,
                    RAW = 2,
                    DXT = 3
                };
            public:
                CImageLoaderBLP(IVideoDriver*);
                virtual ~CImageLoaderBLP();
 
                virtual bool isALoadableFileExtension(const io::path& filename) const;
                virtual bool isALoadableFileFormat(io::IReadFile* file) const;
                virtual IImage* loadImage(io::IReadFile* file) const;
            protected:
                IImage* decompressBLP(const sharedheader&, char *, size_t) const;
                IImage* decompressDXT(const sharedheader&, char *, size_t) const;
                IImage* decompressRaw(const sharedheader&, char *, size_t) const;
                IImage* decompressJpg(const sharedheader&, char *, size_t) const;
            private:
                IVideoDriver *driver;
        };
 
        IImageLoader* createImageLoaderBLP();
    }
}
 
#endif
#endif
 

Code: Select all

 
#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
 
use loader

Code: Select all

 driver->addExternalImageLoader(new irr::video::CImageLoaderBLP(driver));
driver->getTexture("*.blp");
some attention for the Raw and DXTC compression, it's untested (I don't have file to test)

you can found resource at http://www.hiveworkshop.com/forums/icons.php & http://www.hiveworkshop.com/forums/skins.php

original post:
http://irrlicht-fr.org/viewtopic.php?pid=11575#p11575
Post Reply