diff --git a/CMakeLists.txt b/CMakeLists.txt index 917ecb4e9..654d590ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -348,6 +348,7 @@ set(LOVE_SRC_MODULE_IMAGE_ROOT set(LOVE_SRC_MODULE_IMAGE_MAGPIE src/modules/image/magpie/CompressedData.cpp src/modules/image/magpie/CompressedData.h + src/modules/image/magpie/CompressedFormatHandler.h src/modules/image/magpie/ddsHandler.cpp src/modules/image/magpie/ddsHandler.h src/modules/image/magpie/FormatHandler.cpp @@ -358,8 +359,14 @@ set(LOVE_SRC_MODULE_IMAGE_MAGPIE src/modules/image/magpie/ImageData.h src/modules/image/magpie/JPEGHandler.cpp src/modules/image/magpie/JPEGHandler.h + src/modules/image/magpie/KTXHandler.cpp + src/modules/image/magpie/KTXHandler.h + src/modules/image/magpie/PKMHandler.cpp + src/modules/image/magpie/PKMHandler.h src/modules/image/magpie/PNGHandler.cpp src/modules/image/magpie/PNGHandler.h + src/modules/image/magpie/PVRHandler.cpp + src/modules/image/magpie/PVRHandler.h src/modules/image/magpie/STBHandler.cpp src/modules/image/magpie/STBHandler.h ) diff --git a/changes.txt b/changes.txt index 2f0b4debb..5bd035e62 100644 --- a/changes.txt +++ b/changes.txt @@ -10,7 +10,7 @@ Released: N/A * Added love.graphics.getSupported (replaces love.graphics.isSupported.) * Added love.graphics.getSystemLimits (replaces love.graphics.getSystemLimit.) * Added love.graphics.stencil and love.graphics.setStencilTest (replaces love.graphics.setStencil.) - * Added optional ClearType argument to love.graphics.clear. Current enums are "all" (the default) and "stencil". + * Added optional type argument to love.graphics.clear. Current type enums are "all" (the default) and "stencil". * Added optional x/y/width/height arguments to Image:refresh. * Added BMFont bitmap font file support to love.graphics.newFont and love.font. diff --git a/platform/macosx/love-framework.xcodeproj/project.pbxproj b/platform/macosx/love-framework.xcodeproj/project.pbxproj index 4277ec650..256b8fc80 100644 --- a/platform/macosx/love-framework.xcodeproj/project.pbxproj +++ b/platform/macosx/love-framework.xcodeproj/project.pbxproj @@ -272,6 +272,13 @@ FA636D8B171B70920065623F /* RandomGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = FA636D89171B70920065623F /* RandomGenerator.h */; }; FA636D8E171B72A70065623F /* wrap_RandomGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA636D8C171B72A70065623F /* wrap_RandomGenerator.cpp */; }; FA636D8F171B72A70065623F /* wrap_RandomGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = FA636D8D171B72A70065623F /* wrap_RandomGenerator.h */; }; + FA6F55761A6E261800062204 /* KTXHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6F55701A6E261800062204 /* KTXHandler.cpp */; }; + FA6F55771A6E261800062204 /* KTXHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6F55711A6E261800062204 /* KTXHandler.h */; }; + FA6F55781A6E261800062204 /* PKMHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6F55721A6E261800062204 /* PKMHandler.cpp */; }; + FA6F55791A6E261800062204 /* PKMHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6F55731A6E261800062204 /* PKMHandler.h */; }; + FA6F557A1A6E261800062204 /* PVRHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6F55741A6E261800062204 /* PVRHandler.cpp */; }; + FA6F557B1A6E261800062204 /* PVRHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6F55751A6E261800062204 /* PVRHandler.h */; }; + FA6F557D1A6E28A200062204 /* CompressedFormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6F557C1A6E28A200062204 /* CompressedFormatHandler.h */; }; FA70573C194A3FC600670B30 /* DroppedFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA70573A194A3FC600670B30 /* DroppedFile.cpp */; }; FA70573D194A3FC600670B30 /* DroppedFile.h in Headers */ = {isa = PBXBuildFile; fileRef = FA70573B194A3FC600670B30 /* DroppedFile.h */; }; FA705740194A55CB00670B30 /* wrap_DroppedFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA70573E194A55CB00670B30 /* wrap_DroppedFile.cpp */; }; @@ -849,6 +856,13 @@ FA636D89171B70920065623F /* RandomGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomGenerator.h; sourceTree = ""; }; FA636D8C171B72A70065623F /* wrap_RandomGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_RandomGenerator.cpp; sourceTree = ""; }; FA636D8D171B72A70065623F /* wrap_RandomGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_RandomGenerator.h; sourceTree = ""; }; + FA6F55701A6E261800062204 /* KTXHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KTXHandler.cpp; sourceTree = ""; }; + FA6F55711A6E261800062204 /* KTXHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KTXHandler.h; sourceTree = ""; }; + FA6F55721A6E261800062204 /* PKMHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PKMHandler.cpp; sourceTree = ""; }; + FA6F55731A6E261800062204 /* PKMHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PKMHandler.h; sourceTree = ""; }; + FA6F55741A6E261800062204 /* PVRHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PVRHandler.cpp; sourceTree = ""; }; + FA6F55751A6E261800062204 /* PVRHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRHandler.h; sourceTree = ""; }; + FA6F557C1A6E28A200062204 /* CompressedFormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedFormatHandler.h; sourceTree = ""; }; FA70573A194A3FC600670B30 /* DroppedFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DroppedFile.cpp; sourceTree = ""; }; FA70573B194A3FC600670B30 /* DroppedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DroppedFile.h; sourceTree = ""; }; FA70573E194A55CB00670B30 /* wrap_DroppedFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_DroppedFile.cpp; sourceTree = ""; }; @@ -1124,6 +1138,7 @@ children = ( FAEC808C1711E76C0057279A /* CompressedData.cpp */, FAEC808D1711E76C0057279A /* CompressedData.h */, + FA6F557C1A6E28A200062204 /* CompressedFormatHandler.h */, FAE010DE170DE25E006F29D0 /* ddsHandler.cpp */, FAE010DF170DE25E006F29D0 /* ddsHandler.h */, FADD58DC18C30367005FC3BF /* FormatHandler.cpp */, @@ -1134,8 +1149,14 @@ FAEC80891710FEA60057279A /* ImageData.h */, FA48E43418F10D00007CF0BD /* JPEGHandler.cpp */, FA48E43518F10D00007CF0BD /* JPEGHandler.h */, + FA6F55701A6E261800062204 /* KTXHandler.cpp */, + FA6F55711A6E261800062204 /* KTXHandler.h */, + FA6F55721A6E261800062204 /* PKMHandler.cpp */, + FA6F55731A6E261800062204 /* PKMHandler.h */, FA4BBCE018A5DD7E0027707D /* PNGHandler.cpp */, FA4BBCE118A5DD7E0027707D /* PNGHandler.h */, + FA6F55741A6E261800062204 /* PVRHandler.cpp */, + FA6F55751A6E261800062204 /* PVRHandler.h */, FADE620719368D5C00C25B97 /* STBHandler.cpp */, FADE620819368D5C00C25B97 /* STBHandler.h */, ); @@ -2052,6 +2073,7 @@ FAF6705018184FF800DBDEEA /* wuff_convert.h in Headers */, FAF272AB16E3D44400CC193A /* wrap_Channel.h in Headers */, FA0A4BF9182E260200E1E4D2 /* wrap_MotorJoint.h in Headers */, + FA6F557D1A6E28A200062204 /* CompressedFormatHandler.h in Headers */, FAF272AD16E3D44400CC193A /* wrap_LuaThread.h in Headers */, FAF272AF16E3D44400CC193A /* wrap_ThreadModule.h in Headers */, FAF272B616E3D46400CC193A /* Thread.h in Headers */, @@ -2072,11 +2094,13 @@ FAEC808F1711E76C0057279A /* CompressedData.h in Headers */, FA0F0D5B19A2CFEB0010A75B /* glad.hpp in Headers */, FA636D8B171B70920065623F /* RandomGenerator.h in Headers */, + FA6F557B1A6E261800062204 /* PVRHandler.h in Headers */, FA636D8F171B72A70065623F /* wrap_RandomGenerator.h in Headers */, FAC86E641724552C00EED715 /* wrap_Quad.h in Headers */, FAC86E6C1724555D00EED715 /* Quad.h in Headers */, FA03546D1731F3A700284828 /* simplexnoise1234.h in Headers */, FA3D9E0E16E68DE600CA6630 /* Cursor.h in Headers */, + FA6F55771A6E261800062204 /* KTXHandler.h in Headers */, FA3D9E1216E68EAE00CA6630 /* Cursor.h in Headers */, FA3D9E1616E6D41100CA6630 /* wrap_Cursor.h in Headers */, FA9FC0B1173D6E3E005027FF /* wrap_Window.h in Headers */, @@ -2104,6 +2128,7 @@ FA5FDC861788D548002F0ED2 /* win32.h in Headers */, FA5FDC8F1788D548002F0ED2 /* lua-enet.h in Headers */, FA7175AA178E8418001FE7FE /* lua.h in Headers */, + FA6F55791A6E261800062204 /* PKMHandler.h in Headers */, FA34E7CE174B513F00E04D3F /* System.h in Headers */, FA34E7D3174B515D00E04D3F /* System.h in Headers */, FAF670571818501300DBDEEA /* WaveDecoder.h in Headers */, @@ -2182,6 +2207,7 @@ FA08F5B516C7530100F007B5 /* Source.cpp in Sources */, FAF6705118184FF800DBDEEA /* wuff_internal.c in Sources */, FA08F5B616C7530A00F007B5 /* Audio.cpp in Sources */, + FA6F55761A6E261800062204 /* KTXHandler.cpp in Sources */, FA08F5B716C7530A00F007B5 /* Pool.cpp in Sources */, FA08F5B816C7530A00F007B5 /* Source.cpp in Sources */, FA08F5B916C7532A00F007B5 /* b64.cpp in Sources */, @@ -2198,6 +2224,7 @@ FA08F5C216C7532A00F007B5 /* runtime.cpp in Sources */, FA08F5C316C7532A00F007B5 /* utf8.cpp in Sources */, FA0F0D5A19A2CFEB0010A75B /* glad.cpp in Sources */, + FA6F557A1A6E261800062204 /* PVRHandler.cpp in Sources */, FA08F5C416C7532A00F007B5 /* Variant.cpp in Sources */, FA08F5C516C7532A00F007B5 /* Vector.cpp in Sources */, FA08F5C616C7532A00F007B5 /* wrap_Data.cpp in Sources */, @@ -2340,6 +2367,7 @@ FA08F64F16C7546400F007B5 /* RevoluteJoint.cpp in Sources */, FA08F65016C7546400F007B5 /* RopeJoint.cpp in Sources */, FA08F65116C7546400F007B5 /* Shape.cpp in Sources */, + FA6F55781A6E261800062204 /* PKMHandler.cpp in Sources */, FA08F65216C7547300F007B5 /* WeldJoint.cpp in Sources */, FAF6704C18184FF800DBDEEA /* wuff.c in Sources */, FA08F65316C7547300F007B5 /* WheelJoint.cpp in Sources */, diff --git a/src/common/int.h b/src/common/int.h index bf442c478..13a6a17ec 100644 --- a/src/common/int.h +++ b/src/common/int.h @@ -47,6 +47,27 @@ typedef uint32_t uint32; typedef int64_t int64; typedef uint64_t uint64; +static inline uint16 swap16(uint16 x) +{ + return (x >> 8) | (x << 8); +} + +static inline uint32 swap32(uint32 x) +{ + return ((x & 0x000000FF) << 24) | + ((x & 0x0000FF00) << 8) | + ((x & 0x00FF0000) >> 8) | + ((x & 0xFF000000) >> 24); +} + +static inline uint64 swap64(uint64 x) +{ + return ((x << 56) & 0xFF00000000000000ULL) | ((x << 40) & 0x00FF000000000000ULL) | + ((x << 24) & 0x0000FF0000000000ULL) | ((x << 8) & 0x000000FF00000000ULL) | + ((x >> 8) & 0x00000000FF000000ULL) | ((x >> 24) & 0x0000000000FF0000ULL) | + ((x >> 40) & 0x000000000000FF00ULL) | ((x >> 56) & 0x00000000000000FFULL); +} + } // love #endif // LOVE_INT_H diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index 48d9690a5..0e928ec20 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -507,6 +507,20 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const return GL_COMPRESSED_RGBA_BPTC_UNORM; case image::CompressedData::FORMAT_BC7SRGB: return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + case image::CompressedData::FORMAT_ETC1: + // The ETC2 format can load ETC1 textures. + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility) + return GL_COMPRESSED_RGB8_ETC2; + else + return GL_ETC1_RGB8_OES; + case image::CompressedData::FORMAT_PVR1_RGB2: + return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + case image::CompressedData::FORMAT_PVR1_RGB4: + return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + case image::CompressedData::FORMAT_PVR1_RGBA2: + return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + case image::CompressedData::FORMAT_PVR1_RGBA4: + return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; default: if (flags.sRGB) return GL_SRGB8_ALPHA8; @@ -540,6 +554,14 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format) case image::CompressedData::FORMAT_BC7: case image::CompressedData::FORMAT_BC7SRGB: return GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc; + case image::CompressedData::FORMAT_ETC1: + // ETC2 support guarantees ETC1 support as well. + return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture; + case image::CompressedData::FORMAT_PVR1_RGB2: + case image::CompressedData::FORMAT_PVR1_RGB4: + case image::CompressedData::FORMAT_PVR1_RGBA2: + case image::CompressedData::FORMAT_PVR1_RGBA4: + return GLAD_IMG_texture_compression_pvrtc; default: return false; } diff --git a/src/modules/image/CompressedData.cpp b/src/modules/image/CompressedData.cpp index 71e1647e8..227072643 100644 --- a/src/modules/image/CompressedData.cpp +++ b/src/modules/image/CompressedData.cpp @@ -27,7 +27,7 @@ namespace image CompressedData::CompressedData() : format(FORMAT_UNKNOWN) - , data(0) + , data(nullptr) , dataSize(0) { } @@ -114,6 +114,11 @@ StringMap::Entry Compre {"bc6hs", CompressedData::FORMAT_BC6Hs}, {"bc7", CompressedData::FORMAT_BC7}, {"bc7srgb", CompressedData::FORMAT_BC7SRGB}, + {"etc1", CompressedData::FORMAT_ETC1}, + {"pvr1rgb2", CompressedData::FORMAT_PVR1_RGB2}, + {"pvr1rgb4", CompressedData::FORMAT_PVR1_RGB4}, + {"pvr1rgba2", CompressedData::FORMAT_PVR1_RGBA2}, + {"pvr1rgba4", CompressedData::FORMAT_PVR1_RGBA4}, }; StringMap CompressedData::formats(CompressedData::formatEntries, sizeof(CompressedData::formatEntries)); diff --git a/src/modules/image/CompressedData.h b/src/modules/image/CompressedData.h index 9ddb4e211..41196fac6 100644 --- a/src/modules/image/CompressedData.h +++ b/src/modules/image/CompressedData.h @@ -58,6 +58,11 @@ public: FORMAT_BC6Hs, FORMAT_BC7, FORMAT_BC7SRGB, + FORMAT_ETC1, + FORMAT_PVR1_RGB2, + FORMAT_PVR1_RGB4, + FORMAT_PVR1_RGBA2, + FORMAT_PVR1_RGBA4, FORMAT_MAX_ENUM }; diff --git a/src/modules/image/magpie/CompressedData.cpp b/src/modules/image/magpie/CompressedData.cpp index 07af19ef9..bf238f5c2 100644 --- a/src/modules/image/magpie/CompressedData.cpp +++ b/src/modules/image/magpie/CompressedData.cpp @@ -20,8 +20,6 @@ #include "CompressedData.h" -#include "ddsHandler.h" - namespace love { namespace image @@ -29,9 +27,39 @@ namespace image namespace magpie { -CompressedData::CompressedData(love::filesystem::FileData *filedata) +CompressedData::CompressedData(std::list formats, love::filesystem::FileData *filedata) { - load(filedata); + CompressedFormatHandler *parser = nullptr; + + for (CompressedFormatHandler *handler : formats) + { + if (handler->canParse(filedata)) + { + parser = handler; + break; + } + } + + if (parser == nullptr) + throw love::Exception("Could not parse compressed data: Unknown format."); + + // DataImages SubImage vector will be populated by a parser. + data = parser->parse(filedata, dataImages, dataSize, format); + + if (data == nullptr) + throw love::Exception("Could not parse compressed data."); + + if (format == FORMAT_UNKNOWN) + { + delete[] data; + throw love::Exception("Could not parse compressed data: Unknown format."); + } + + if (dataImages.size() == 0 || dataSize == 0) + { + delete[] data; + throw love::Exception("Could not parse compressed data: No valid data?"); + } } CompressedData::~CompressedData() @@ -39,51 +67,6 @@ CompressedData::~CompressedData() delete[] data; } -void CompressedData::load(love::filesystem::FileData *filedata) -{ - // SubImage vector will be populated by a parser. - std::vector parsedimages; - Format texformat = FORMAT_UNKNOWN; - - uint8 *newdata = 0; - size_t newdata_size = 0; - - if (ddsHandler::canParse(filedata)) - newdata = ddsHandler::parse(filedata, parsedimages, newdata_size, texformat); - - if (newdata == 0) - throw love::Exception("Could not parse compressed data."); - - if (texformat == FORMAT_UNKNOWN) - { - delete[] newdata; - throw love::Exception("Could not parse compressed data: Unknown format."); - } - - if (parsedimages.size() == 0 || newdata_size == 0) - { - delete[] newdata; - throw love::Exception("Could not parse compressed data: No valid data?"); - } - - // Make sure to clean up any previously loaded data. - delete[] data; - - data = newdata; - dataSize = newdata_size; - - dataImages = parsedimages; - format = texformat; -} - -bool CompressedData::isCompressed(love::filesystem::FileData *filedata) -{ - if (ddsHandler::canParse(filedata)) - return true; - - return false; -} - } // magpie } // image } // love diff --git a/src/modules/image/magpie/CompressedData.h b/src/modules/image/magpie/CompressedData.h index da66ee9dc..aba40febc 100644 --- a/src/modules/image/magpie/CompressedData.h +++ b/src/modules/image/magpie/CompressedData.h @@ -22,9 +22,13 @@ #define LOVE_IMAGE_MAGPIE_COMPRESSED_DATA_H // LOVE +#include "CompressedFormatHandler.h" #include "filesystem/FileData.h" #include "image/CompressedData.h" +// C++ +#include + namespace love { namespace image @@ -36,15 +40,9 @@ class CompressedData : public love::image::CompressedData { public: - CompressedData(love::filesystem::FileData *filedata); + CompressedData(std::list formats, love::filesystem::FileData *filedata); virtual ~CompressedData(); - static bool isCompressed(love::filesystem::FileData *filedata); - -private: - - void load(love::filesystem::FileData *filedata); - }; // CompressedData } // magpie diff --git a/src/modules/image/magpie/CompressedFormatHandler.h b/src/modules/image/magpie/CompressedFormatHandler.h new file mode 100644 index 000000000..490da2c03 --- /dev/null +++ b/src/modules/image/magpie/CompressedFormatHandler.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * 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. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H +#define LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H + +// LOVE +#include "filesystem/FileData.h" +#include "image/CompressedData.h" +#include "common/Object.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +/** + * Base class for all CompressedData parser library interfaces. + * We inherit from love::Object to take advantage of reference counting... + **/ +class CompressedFormatHandler : public love::Object +{ +public: + + CompressedFormatHandler() {} + virtual ~CompressedFormatHandler() {} + + /** + * Determines whether a particular FileData can be parsed as CompressedData + * by this handler. + * @param data The data to parse. + **/ + virtual bool canParse(const filesystem::FileData *data) = 0; + + /** + * Parses compressed image filedata into a list of sub-images and returns + * a single block of memory containing all the images. + * + * @param[in] filedata The data to parse. + * @param[out] image The list of sub-images generated. Byte data is a pointer + * to the returned data. + * @param[out] dataSize The total size in bytes of the returned data. + * @param[out] format The format of the Compressed Data. + * + * @return The single block of memory containing the parsed images. + **/ + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) = 0; + +}; // CompressedFormatHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H diff --git a/src/modules/image/magpie/DevilHandler.cpp b/src/modules/image/magpie/DevilHandler.cpp deleted file mode 100644 index 12bc98d56..000000000 --- a/src/modules/image/magpie/DevilHandler.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Copyright (c) 2006-2015 LOVE Development Team - * - * 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 "DevilHandler.h" - -// LOVE -#include "common/Exception.h" -#include "common/math.h" - -// DevIL -#include - -namespace love -{ -namespace image -{ -namespace magpie -{ - -static inline void ilxClearErrors() -{ - while (ilGetError() != IL_NO_ERROR); -} - -DevilHandler::DevilHandler() - : mutex(nullptr) -{ - // There should only ever be one DevilHandler object (owned by the Image - // module), so we can use the global initialization function here. - ilInit(); - ilEnable(IL_ORIGIN_SET); - ilOriginFunc(IL_ORIGIN_UPPER_LEFT); -} - -DevilHandler::~DevilHandler() -{ - ilShutDown(); - - if (mutex) - delete mutex; -} - -bool DevilHandler::canDecode(love::filesystem::FileData * /*data*/) -{ - // DevIL can decode a lot of formats... - return true; -} - -bool DevilHandler::canEncode(ImageData::Format format) -{ - switch (format) - { - case ImageData::FORMAT_BMP: - case ImageData::FORMAT_TGA: - case ImageData::FORMAT_JPG: - case ImageData::FORMAT_PNG: - return true; - default: - return false; - } - - return false; -} - -DevilHandler::DecodedImage DevilHandler::decode(love::filesystem::FileData *data) -{ - if (!mutex) - mutex = love::thread::newMutex(); - - love::thread::Lock lock(mutex); - - ILuint image = ilGenImage(); - ilBindImage(image); - - DecodedImage img; - - try - { - bool success = ilLoadL(IL_TYPE_UNKNOWN, (void *)data->getData(), (ILuint) data->getSize()) == IL_TRUE; - - if (!success) - throw love::Exception("Could not decode image!"); - - img.width = ilGetInteger(IL_IMAGE_WIDTH); - img.height = ilGetInteger(IL_IMAGE_HEIGHT); - - // Make sure the image is in RGBA format. - ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE); - - // This should always be four. - int bpp = ilGetInteger(IL_IMAGE_BPP); - if (bpp != sizeof(pixel)) - throw love::Exception("Could not convert image!"); - - img.size = (size_t) ilGetInteger(IL_IMAGE_SIZE_OF_DATA); - - try - { - img.data = new ILubyte[img.size]; - } - catch (std::bad_alloc &) - { - throw love::Exception("Out of memory."); - } - - memcpy(img.data, ilGetData(), img.size); - } - catch (std::exception &e) - { - // catches love and std exceptions - ilDeleteImage(image); - throw love::Exception("%s", e.what()); - } - - ilDeleteImage(image); - - return img; -} - -DevilHandler::EncodedImage DevilHandler::encode(const DecodedImage &img, ImageData::Format format) -{ - if (!mutex) - mutex = love::thread::newMutex(); - - love::thread::Lock lock(mutex); - - ILuint tempimage = ilGenImage(); - ilBindImage(tempimage); - ilxClearErrors(); - - EncodedImage encodedimage; - - try - { - bool success = ilTexImage(img.width, img.height, 1, sizeof(pixel), IL_RGBA, IL_UNSIGNED_BYTE, img.data) == IL_TRUE; - - ILenum err = ilGetError(); - ilxClearErrors(); - - if (!success) - { - if (err != IL_NO_ERROR) - { - switch (err) - { - case IL_ILLEGAL_OPERATION: - throw love::Exception("Illegal operation"); - case IL_INVALID_PARAM: - throw love::Exception("Invalid parameters"); - case IL_OUT_OF_MEMORY: - throw love::Exception("Out of memory"); - default: - throw love::Exception("Unknown error (%d)", (int) err); - } - } - - throw love::Exception("Could not create image for the encoding!"); - } - - ilRegisterOrigin(IL_ORIGIN_UPPER_LEFT); - - ILuint ilFormat; - switch (format) - { - case ImageData::FORMAT_BMP: - ilFormat = IL_BMP; - break; - case ImageData::FORMAT_TGA: - ilFormat = IL_TGA; - break; - case ImageData::FORMAT_JPG: - ilFormat = IL_JPG; - break; - case ImageData::FORMAT_PNG: - default: // PNG is the default format - ilFormat = IL_PNG; - break; - } - - encodedimage.size = ilSaveL(ilFormat, NULL, 0); - if (!encodedimage.size) - throw love::Exception("Could not encode image!"); - - try - { - encodedimage.data = new ILubyte[encodedimage.size]; - } - catch(std::bad_alloc &) - { - throw love::Exception("Out of memory"); - } - - ilSaveL(ilFormat, encodedimage.data, encodedimage.size); - } - catch (std::exception &e) - { - // Catches love and std exceptions. - ilDeleteImage(tempimage); - delete[] encodedimage.data; - encodedimage.data = 0; - throw love::Exception("%s", e.what()); - } - - ilDeleteImage(tempimage); - - return encodedimage; -} - -} // magpie -} // image -} // love diff --git a/src/modules/image/magpie/Image.cpp b/src/modules/image/magpie/Image.cpp index c85a71f62..6c99299f7 100644 --- a/src/modules/image/magpie/Image.cpp +++ b/src/modules/image/magpie/Image.cpp @@ -27,6 +27,11 @@ #include "PNGHandler.h" #include "STBHandler.h" +#include "ddsHandler.h" +#include "PVRHandler.h" +#include "KTXHandler.h" +#include "PKMHandler.h" + namespace love { namespace image @@ -39,6 +44,11 @@ Image::Image() formatHandlers.push_back(new PNGHandler); formatHandlers.push_back(new JPEGHandler); formatHandlers.push_back(new STBHandler); + + compressedFormatHandlers.push_back(new DDSHandler); + compressedFormatHandlers.push_back(new PVRHandler); + compressedFormatHandlers.push_back(new KTXHandler); + compressedFormatHandlers.push_back(new PKMHandler); } Image::~Image() @@ -47,6 +57,9 @@ Image::~Image() // release them instead of deleting them completely here. for (FormatHandler *handler : formatHandlers) handler->release(); + + for (CompressedFormatHandler *handler : compressedFormatHandlers) + handler->release(); } const char *Image::getName() const @@ -71,12 +84,18 @@ love::image::ImageData *Image::newImageData(int width, int height, void *data, b love::image::CompressedData *Image::newCompressedData(love::filesystem::FileData *data) { - return new CompressedData(data); + return new CompressedData(compressedFormatHandlers, data); } bool Image::isCompressed(love::filesystem::FileData *data) { - return CompressedData::isCompressed(data); + for (CompressedFormatHandler *handler : compressedFormatHandlers) + { + if (handler->canParse(data)) + return true; + } + + return false; } } // magpie diff --git a/src/modules/image/magpie/Image.h b/src/modules/image/magpie/Image.h index 50809bdc3..4597e46b9 100644 --- a/src/modules/image/magpie/Image.h +++ b/src/modules/image/magpie/Image.h @@ -24,6 +24,7 @@ // LOVE #include "image/Image.h" #include "FormatHandler.h" +#include "CompressedFormatHandler.h" // C++ #include @@ -63,6 +64,9 @@ private: // Image format handlers we can use for decoding and encoding ImageData. std::list formatHandlers; + // Compressed image format handers we can use for parsing CompressedData. + std::list compressedFormatHandlers; + }; // Image } // magpie diff --git a/src/modules/image/magpie/KTXHandler.cpp b/src/modules/image/magpie/KTXHandler.cpp new file mode 100644 index 000000000..fcaf61e6e --- /dev/null +++ b/src/modules/image/magpie/KTXHandler.cpp @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * 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. + **/ + +// LOVE +#include "KTXHandler.h" +#include "common/int.h" + +// C +#include + +// C++ +#include + +namespace love +{ +namespace image +{ +namespace magpie +{ + +namespace +{ + +#define KTX_IDENTIFIER_REF {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A} +#define KTX_ENDIAN_REF (0x04030201) +#define KTX_ENDIAN_REF_REV (0x01020304) +#define KTX_HEADER_SIZE (64) + +struct KTXHeader +{ + uint8 identifier[12]; + uint32 endianness; + uint32 glType; + uint32 glTypeSize; + uint32 glFormat; + uint32 glInternalFormat; + uint32 glBaseInternalFormat; + uint32 pixelWidth; + uint32 pixelHeight; + uint32 pixelDepth; + uint32 numberOfArrayElements; + uint32 numberOfFaces; + uint32 numberOfMipmapLevels; + uint32 bytesOfKeyValueData; +}; + +static_assert(sizeof(KTXHeader) == KTX_HEADER_SIZE, "Real size of KTX header doesn't match struct size!"); + +enum KTXGLInternalFormat +{ + KTX_GL_ETC1_RGB8_OES = 0x8D64, + + // LOVE doesn't support EAC or ETC2 yet, but it won't be hard to add. + KTX_GL_COMPRESSED_R11_EAC = 0x9270, + KTX_GL_COMPRESSED_SIGNED_R11_EAC = 0x9271, + KTX_GL_COMPRESSED_RG11_EAC = 0x9272, + KTX_GL_COMPRESSED_SIGNED_RG11_EAC = 0x9273, + KTX_GL_COMPRESSED_RGB8_ETC2 = 0x9274, + KTX_GL_COMPRESSED_SRGB8_ETC2 = 0x9275, + KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276, + KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277, + KTX_GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279, + + // I don't know if any KTX file contains PVR data, but why not support it. + KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00, + KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01, + KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02, + KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03, + + // Same with DXT1/3/5. + KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0, + KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2, + KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3 +}; + +CompressedData::Format convertFormat(uint32 glformat) +{ + switch (glformat) + { + case KTX_GL_ETC1_RGB8_OES: + return CompressedData::FORMAT_ETC1; + case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGB4; + case KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGB2; + case KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGBA4; + case KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGBA2; + case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return CompressedData::FORMAT_DXT1; + case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + return CompressedData::FORMAT_DXT3; + case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return CompressedData::FORMAT_DXT5; + default: + return CompressedData::FORMAT_UNKNOWN; + } +} + +} // Anonymous namespace. + +bool KTXHandler::canParse(const filesystem::FileData *data) +{ + if (data->getSize() < sizeof(KTXHeader)) + return false; + + KTXHeader *header = (KTXHeader *) data->getData(); + uint8 ktxidentifier[12] = KTX_IDENTIFIER_REF; + + if (memcmp(header->identifier, ktxidentifier, 12) != 0) + return false; + + if (header->endianness != KTX_ENDIAN_REF && header->endianness != KTX_ENDIAN_REF_REV) + return false; + + return true; +} + +uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +{ + if (!canParse(filedata)) + throw love::Exception("Could not decode compressed data (not a KTX file?)"); + + KTXHeader header = *(KTXHeader *) filedata->getData(); + + if (header.endianness == KTX_ENDIAN_REF_REV) + { + uint32 *headerArray = (uint32 *) &header.glType; + for (int i = 0; i < 12; i++) + headerArray[i] = swap32(headerArray[i]); + } + + header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u); + + CompressedData::Format cformat = convertFormat(header.glInternalFormat); + + if (cformat == CompressedData::FORMAT_UNKNOWN) + throw love::Exception("Unsupported image format in KTX file."); + + if (header.numberOfArrayElements > 0) + throw love::Exception("Texture arrays in KTX files are not supported."); + + if (header.pixelDepth > 1) + throw love::Exception("3D textures in KTX files are not supported."); + + if (header.numberOfFaces > 1) + throw love::Exception("Cubemap textures in KTX files are not supported."); + + size_t fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData; + const uint8 *filebytes = (uint8 *) filedata->getData(); + size_t totalsize = 0; + + // Calculate the total size needed to hold the data in memory. + for (int i = 0; i < (int) header.numberOfMipmapLevels; i++) + { + if (fileoffset + sizeof(uint32) > filedata->getSize()) + throw love::Exception("Could not parse KTX file: unexpected EOF."); + + uint32 mipsize = *(uint32 *) (filebytes + fileoffset); + + if (header.endianness == KTX_ENDIAN_REF_REV) + mipsize = swap32(mipsize); + + fileoffset += sizeof(uint32); + + // All mipsize fields are at a file offset that's a multiple of 4, so + // there might be some padding after the actual data in this mip level. + uint32 mipsizepadded = (mipsize + 3) & ~uint32(3); + + totalsize += mipsizepadded; + fileoffset += mipsizepadded; + } + + uint8 *data = nullptr; + try + { + data = new uint8[totalsize]; + } + catch (std::bad_alloc &) + { + throw love::Exception("Out of memory."); + } + + // Reset the file offset to the start of the file's image data. + fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData; + size_t dataoffset = 0; + + // Copy each mipmap level of the image from the file to our block of memory. + for (int i = 0; i < (int) header.numberOfMipmapLevels; i++) + { + uint32 mipsize = *(uint32 *) (filebytes + fileoffset); + + if (header.endianness == KTX_ENDIAN_REF_REV) + mipsize = swap32(mipsize); + + fileoffset += sizeof(uint32); + + uint32 mipsizepadded = (mipsize + 3) & ~uint32(3); + + CompressedData::SubImage mip; + mip.width = (int) std::max(header.pixelWidth >> i, 1u); + mip.height = (int) std::max(header.pixelHeight >> i, 1u); + mip.size = mipsize; + + memcpy(data + dataoffset, filebytes + fileoffset, mipsize); + mip.data = data + dataoffset; + + fileoffset += mipsizepadded; + dataoffset += mipsizepadded; + + images.push_back(mip); + } + + dataSize = totalsize; + format = cformat; + + return data; +} + +} // magpie +} // image +} // love diff --git a/src/modules/image/magpie/DevilHandler.h b/src/modules/image/magpie/KTXHandler.h similarity index 57% rename from src/modules/image/magpie/DevilHandler.h rename to src/modules/image/magpie/KTXHandler.h index ed56ab665..4ac823e73 100644 --- a/src/modules/image/magpie/DevilHandler.h +++ b/src/modules/image/magpie/KTXHandler.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2015 LOVE Development Team + * Copyright (c) 2006-2014 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -18,13 +18,11 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#ifndef LOVE_IMAGE_MAGPIE_DEVIL_HANDLER_H -#define LOVE_IMAGE_MAGPIE_DEVIL_HANDLER_H +#ifndef LOVE_IMAGE_MAGPIE_KTX_HANDLER_H +#define LOVE_IMAGE_MAGPIE_KTX_HANDLER_H -// LOVE -#include "filesystem/FileData.h" -#include "FormatHandler.h" -#include "thread/threads.h" +#include "common/config.h" +#include "CompressedFormatHandler.h" namespace love { @@ -34,31 +32,22 @@ namespace magpie { /** - * Interface between ImageData and DevIL. + * Handles KTX files with compressed image data inside. **/ -class DevilHandler : public FormatHandler +class KTXHandler : public CompressedFormatHandler { public: - // Implements FormatHandler. + virtual ~KTXHandler() {} - DevilHandler(); - virtual ~DevilHandler(); + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); - virtual bool canDecode(love::filesystem::FileData *data); - virtual bool canEncode(ImageData::Format format); - - virtual DecodedImage decode(love::filesystem::FileData *data); - virtual EncodedImage encode(const DecodedImage &img, ImageData::Format format); - -private: - - Mutex *mutex; - -}; // DevilHandler +}; // KTXHandler } // magpie } // image } // love -#endif // LOVE_IMAGE_MAGPIE_DEVIL_HANDLER_H +#endif // LOVE_IMAGE_MAGPIE_KTX_HANDLER_H diff --git a/src/modules/image/magpie/PKMHandler.cpp b/src/modules/image/magpie/PKMHandler.cpp new file mode 100644 index 000000000..26ae19728 --- /dev/null +++ b/src/modules/image/magpie/PKMHandler.cpp @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * 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. + **/ + +// LOVE +#include "PKMHandler.h" +#include "common/int.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +namespace +{ + +// Big endian to host (and vice versa.) +inline uint16 swap16big(uint16 x) +{ +#ifdef LOVE_BIG_ENDIAN + return x; +#else + return swap16(x); +#endif +} + +static const uint8 pkmIdentifier[] = {'P','K','M',' '}; + +struct PKMHeader +{ + uint8 identifier[4]; + uint8 version[2]; + uint16 textureFormatBig; + uint16 extendedWidthBig; + uint16 extendedHeightBig; + uint16 widthBig; + uint16 heightBig; +}; + +enum PKMTextureFormat +{ + ETC1_RGB_NO_MIPMAPS = 0, + ETC2PACKAGE_RGB_NO_MIPMAPS, + ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD, + ETC2PACKAGE_RGBA_NO_MIPMAPS, + ETC2PACKAGE_RGBA1_NO_MIPMAPS, + ETC2PACKAGE_R_NO_MIPMAPS, + ETC2PACKAGE_RG_NO_MIPMAPS, + ETC2PACKAGE_R_SIGNED_NO_MIPMAPS, + ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS +}; + +CompressedData::Format convertFormat(uint16 texformat) +{ + switch (texformat) + { + case ETC1_RGB_NO_MIPMAPS: + return CompressedData::FORMAT_ETC1; + default: + return CompressedData::FORMAT_UNKNOWN; + } +} + +} // Anonymous namespace. + +bool PKMHandler::canParse(const filesystem::FileData *data) +{ + if (data->getSize() <= sizeof(PKMHeader)) + return false; + + const PKMHeader *header = (const PKMHeader *) data->getData(); + + if (memcmp(header->identifier, pkmIdentifier, 4) != 0) + return false; + + // At the time of this writing, only v1.0 and v2.0 exist. + if ((header->version[0] != '2' && header->version[0] != '1') || header->version[1] != '0') + return false; + + return true; +} + +uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +{ + if (!canParse(filedata)) + throw love::Exception("Could not decode compressed data (not a PKM file?)"); + + PKMHeader header = *(const PKMHeader *) filedata->getData(); + + header.textureFormatBig = swap16big(header.textureFormatBig); + header.extendedWidthBig = swap16big(header.extendedWidthBig); + header.extendedHeightBig = swap16big(header.extendedHeightBig); + header.widthBig = swap16big(header.widthBig); + header.heightBig = swap16big(header.heightBig); + + CompressedData::Format cformat = convertFormat(header.textureFormatBig); + + if (cformat == CompressedData::FORMAT_UNKNOWN) + throw love::Exception("Could not parse PKM file: unsupported texture format."); + + // The rest of the file after the header is all texture data. + size_t totalsize = filedata->getSize() - sizeof(PKMHeader); + uint8 *data = nullptr; + + try + { + data = new uint8[totalsize]; + } + catch (std::bad_alloc &) + { + throw love::Exception("Out of memory."); + } + + // PKM files only store a single mipmap level. + memcpy(data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize); + + CompressedData::SubImage mip; + + // TODO: verify whether glCompressedTexImage works properly with the unpadded + // width and height values (extended == padded.) + mip.width = header.widthBig; + mip.height = header.heightBig; + + mip.size = totalsize; + mip.data = data; + + images.push_back(mip); + + dataSize = totalsize; + format = cformat; + + return data; +} + +} // magpie +} // image +} // love + diff --git a/src/modules/image/magpie/PKMHandler.h b/src/modules/image/magpie/PKMHandler.h new file mode 100644 index 000000000..1df3ffdad --- /dev/null +++ b/src/modules/image/magpie/PKMHandler.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * 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. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_PKM_HANDLER_H +#define LOVE_IMAGE_MAGPIE_PKM_HANDLER_H + +#include "common/config.h" +#include "CompressedFormatHandler.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +/** + * Handles PKM files with compressed ETC data inside. + **/ +class PKMHandler : public CompressedFormatHandler +{ +public: + + virtual ~PKMHandler() {} + + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + +}; // PKMHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_PKM_HANDLER_H diff --git a/src/modules/image/magpie/PVRHandler.cpp b/src/modules/image/magpie/PVRHandler.cpp new file mode 100644 index 000000000..2e12c3505 --- /dev/null +++ b/src/modules/image/magpie/PVRHandler.cpp @@ -0,0 +1,385 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * 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. + **/ + +// LOVE +#include "PVRHandler.h" +#include "common/int.h" + +// C++ +#include + +namespace love +{ +namespace image +{ +namespace magpie +{ + +namespace +{ + +// 'P' 'V' 'R' 3 +static const uint32 PVRTEX3_IDENT = 0x03525650; +static const uint32 PVRTEX3_IDENT_REV = 0x50565203; + +#pragma pack(push, 4) +struct PVRTexHeaderV3 +{ + uint32 version; /// Version of the file header, used to identify it. + uint32 flags; /// Various format flags. + uint64 pixelFormat; /// The pixel format, 8cc value storing the 4 channel identifiers and their respective sizes. + uint32 colorSpace; /// The Color Space of the texture, currently either linear RGB or sRGB. + uint32 channelType; /// Variable type that the channel is stored in. Supports signed/unsigned int/short/byte or float for now. + uint32 height; /// Height of the texture. + uint32 width; /// Width of the texture. + uint32 depth; /// Depth of the texture. (Z-slices) + uint32 numSurfaces; /// Number of members in a Texture Array. + uint32 numFaces; /// Number of faces in a Cube Map. Maybe be a value other than 6. + uint32 numMipmaps; /// Number of MIP Maps in the texture - NB: Includes top level. + uint32 metaDataSize; /// Size of the accompanying meta data. +}; +#pragma pack(pop) + +enum PVRV3PixelFormat +{ + ePVRTPF_PVRTCI_2bpp_RGB = 0x00, + ePVRTPF_PVRTCI_2bpp_RGBA, + ePVRTPF_PVRTCI_4bpp_RGB, + ePVRTPF_PVRTCI_4bpp_RGBA, + ePVRTPF_PVRTCII_2bpp, + ePVRTPF_PVRTCII_4bpp, + ePVRTPF_ETC1 = 0x06, + ePVRTPF_DXT1, + ePVRTPF_DXT2, + ePVRTPF_DXT3, + ePVRTPF_DXT4, + ePVRTPF_DXT5, + ePVRTF_UNKNOWN_FORMAT = 0x7F +}; + +// 'P' 'V' 'R' '!' +static const uint32 PVRTEX2_IDENT = 0x21525650; +static const uint32 PVRTEX2_IDENT_REV = 0x50565221; + +struct PVRTexHeaderV2 +{ + uint32 headerSize; + uint32 height; + uint32 width; + uint32 numMipmaps; + uint32 flags; + uint32 dataSize; + uint32 bpp; + uint32 bitmaskRed; + uint32 bitmaskGreen; + uint32 bitmaskBlue; + uint32 bitmaskAlpha; + uint32 pvrTag; + uint32 numSurfaces; +}; + +// The legacy V2 pixel types we support. +enum PVRPixelTypeV2 +{ + PixelTypePVRTC2 = 0x18, + PixelTypePVRTC4, + PixelTypePVRTCII2 = 0x1C, + PixelTypePVRTCII4, + PixelTypeDXT1 = 0x20, + PixelTypeDXT3 = 0x22, + PixelTypeDXT5 = 0x24, + PixelTypeETC1 = 0x36 +}; + +// Convert a V2 header to V3. +void ConvertPVRHeader(PVRTexHeaderV2 header2, PVRTexHeaderV3 *header3) +{ + // If the header's endianness doesn't match our own, we swap everything. + if (header2.pvrTag == PVRTEX2_IDENT_REV) + { + // All of the struct's members are uint32 values, so we can do this. + uint32 *headerArray = (uint32 *) &header2; + for (size_t i = 0; i < sizeof(PVRTexHeaderV2) / sizeof(uint32); i++) + headerArray[i] = swap32(headerArray[i]); + } + + memset(header3, 0, sizeof(PVRTexHeaderV3)); + + header3->version = PVRTEX3_IDENT; + header3->height = header2.height; + header3->width = header2.width; + header3->depth = 1; + header3->numSurfaces = header2.numSurfaces; + header3->numFaces = 1; + header3->numMipmaps = header2.numMipmaps; + header3->metaDataSize = 0; + + switch ((PVRPixelTypeV2) (header2.flags & 0xFF)) + { + case PixelTypePVRTC2: + header3->pixelFormat = ePVRTPF_PVRTCI_2bpp_RGBA; + break; + case PixelTypePVRTC4: + header3->pixelFormat = ePVRTPF_PVRTCI_4bpp_RGBA; + break; + case PixelTypePVRTCII2: + header3->pixelFormat = ePVRTPF_PVRTCII_2bpp; + break; + case PixelTypePVRTCII4: + header3->pixelFormat = ePVRTPF_PVRTCII_4bpp; + break; + case PixelTypeDXT1: + header3->pixelFormat = ePVRTPF_DXT1; + break; + case PixelTypeDXT3: + header3->pixelFormat = ePVRTPF_DXT3; + break; + case PixelTypeDXT5: + header3->pixelFormat = ePVRTPF_DXT5; + break; + case PixelTypeETC1: + header3->pixelFormat = ePVRTPF_ETC1; + break; + default: + header3->pixelFormat = ePVRTF_UNKNOWN_FORMAT; + break; + } +} + +CompressedData::Format convertFormat(PVRV3PixelFormat format) +{ + switch (format) + { + case ePVRTPF_PVRTCI_2bpp_RGB: + return CompressedData::FORMAT_PVR1_RGB2; + case ePVRTPF_PVRTCI_2bpp_RGBA: + return CompressedData::FORMAT_PVR1_RGBA2; + case ePVRTPF_PVRTCI_4bpp_RGB: + return CompressedData::FORMAT_PVR1_RGB4; + case ePVRTPF_PVRTCI_4bpp_RGBA: + return CompressedData::FORMAT_PVR1_RGBA4; + case ePVRTPF_ETC1: + return CompressedData::FORMAT_ETC1; + case ePVRTPF_DXT1: + return CompressedData::FORMAT_DXT1; + case ePVRTPF_DXT3: + return CompressedData::FORMAT_DXT3; + case ePVRTPF_DXT5: + return CompressedData::FORMAT_DXT5; + default: + return CompressedData::FORMAT_UNKNOWN; + } +} + +int getBitsPerPixel(uint64 pixelformat) +{ + // Uncompressed formats have their bits per pixel stored in the high bits. + if ((pixelformat & 0xFFFFFFFF) != pixelformat) + { + const uint8 *charformat = (const uint8 *) &pixelformat; + return charformat[4] + charformat[5] + charformat[6] + charformat[7]; + } + + switch (pixelformat) + { + case ePVRTPF_PVRTCI_2bpp_RGB: + case ePVRTPF_PVRTCI_2bpp_RGBA: + case ePVRTPF_PVRTCII_2bpp: + return 2; + case ePVRTPF_PVRTCI_4bpp_RGB: + case ePVRTPF_PVRTCI_4bpp_RGBA: + case ePVRTPF_PVRTCII_4bpp: + case ePVRTPF_ETC1: + case ePVRTPF_DXT1: + return 4; + case ePVRTPF_DXT2: + case ePVRTPF_DXT3: + case ePVRTPF_DXT4: + case ePVRTPF_DXT5: + return 8; + default: + return 0; + } +} + +void getFormatMinDimensions(uint64 pixelformat, int &minX, int &minY) +{ + switch (pixelformat) + { + case ePVRTPF_PVRTCI_2bpp_RGB: + case ePVRTPF_PVRTCI_2bpp_RGBA: + minX = 16; + minY = 8; + break; + case ePVRTPF_PVRTCI_4bpp_RGB: + case ePVRTPF_PVRTCI_4bpp_RGBA: + minX = minY = 8; + break; + case ePVRTPF_PVRTCII_2bpp: + minX = 8; + minY = 4; + break; + case ePVRTPF_PVRTCII_4bpp: + minX = minY = 4; + break; + case ePVRTPF_DXT1: + case ePVRTPF_DXT2: + case ePVRTPF_DXT3: + case ePVRTPF_DXT4: + case ePVRTPF_DXT5: + case ePVRTPF_ETC1: + minX = minY = 4; + break; + default: // We don't handle all possible formats, but that's fine. + minX = minY = 1; + break; + } +} + +size_t getMipLevelSize(const PVRTexHeaderV3 &header, int miplevel) +{ + int smallestwidth = 1; + int smallestheight = 1; + getFormatMinDimensions(header.pixelFormat, smallestwidth, smallestheight); + + int width = std::max((int) header.width >> miplevel, 1); + int height = std::max((int) header.height >> miplevel, 1); + int depth = std::max((int) header.depth >> miplevel, 1); + + // Pad the dimensions. + width += (-width) % smallestwidth; + height += (-height) % smallestheight; + + return getBitsPerPixel(header.pixelFormat) * width * height * depth / 8; +} + +} // Anonymous namespace. + + +bool PVRHandler::canParse(const filesystem::FileData *data) +{ + if (data->getSize() < sizeof(PVRTexHeaderV2) || data->getSize() < sizeof(PVRTexHeaderV3)) + return false; + + PVRTexHeaderV3 *header3 = (PVRTexHeaderV3 *) data->getData(); + + // Magic number (FourCC identifier.) + if (header3->version == PVRTEX3_IDENT || header3->version == PVRTEX3_IDENT_REV) + return true; + + // Maybe it has a V2 header. + PVRTexHeaderV2 *header2 = (PVRTexHeaderV2 *) data->getData(); + + // FourCC identifier. + if (header2->pvrTag == PVRTEX2_IDENT || header2->pvrTag == PVRTEX2_IDENT_REV) + return true; + + return false; +} + +uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +{ + if (!canParse(filedata)) + throw love::Exception("Could not decode compressed data (not a PVR file?)"); + + PVRTexHeaderV3 header3 = *(PVRTexHeaderV3 *) filedata->getData(); + + // If the header isn't the V3 format, assume it's V2 and convert. + if (header3.version != PVRTEX3_IDENT && header3.version != PVRTEX3_IDENT_REV) + ConvertPVRHeader(*(PVRTexHeaderV2 *) filedata->getData(), &header3); + + // If the header's endianness doesn't match our own, then we swap everything. + if (header3.version == PVRTEX3_IDENT_REV) + { + header3.version = PVRTEX3_IDENT; + header3.flags = swap32(header3.flags); + header3.pixelFormat = swap64(header3.pixelFormat); + header3.colorSpace = swap32(header3.colorSpace); + header3.channelType = swap32(header3.channelType); + header3.height = swap32(header3.height); + header3.width = swap32(header3.width); + header3.depth = swap32(header3.depth); + header3.numFaces = swap32(header3.numFaces); + header3.numMipmaps = swap32(header3.numMipmaps); + header3.metaDataSize = swap32(header3.metaDataSize); + } + + if (header3.depth > 1) + throw love::Exception("Image depths greater than 1 in PVR files are unsupported."); + + CompressedData::Format cformat = convertFormat((PVRV3PixelFormat) header3.pixelFormat); + + if (cformat == CompressedData::FORMAT_UNKNOWN) + throw love::Exception("Could not parse PVR file: unsupported image format."); + + size_t totalsize = 0; + uint8 *data = nullptr; + + // Ignore faces and surfaces except the first ones (for now.) + for (int i = 0; i < (int) header3.numMipmaps; i++) + totalsize += getMipLevelSize(header3, i); + + size_t fileoffset = sizeof(PVRTexHeaderV3) + header3.metaDataSize; + + // Make sure the file actually holds this much data... + if (filedata->getSize() < fileoffset + totalsize) + throw love::Exception("Could not parse PVR file: invalid size calculation."); + + try + { + data = new uint8[totalsize]; + } + catch (std::bad_alloc &) + { + throw love::Exception("Out of memory."); + } + + size_t curoffset = 0; + const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset; + + for (int i = 0; i < (int) header3.numMipmaps; i++) + { + size_t mipsize = getMipLevelSize(header3, i); + + if (curoffset + mipsize > totalsize) + break; // Just in case. + + CompressedData::SubImage mip; + mip.width = std::max((int) header3.width >> i, 1); + mip.height = std::max((int) header3.height >> i, 1); + mip.size = mipsize; + + memcpy(data + curoffset, filebytes + curoffset, mipsize); + mip.data = data + curoffset; + + curoffset += mipsize; + + images.push_back(mip); + } + + dataSize = totalsize; + format = cformat; + + return data; +} + +} // magpie +} // image +} // love diff --git a/src/modules/image/magpie/PVRHandler.h b/src/modules/image/magpie/PVRHandler.h new file mode 100644 index 000000000..f35cc9aec --- /dev/null +++ b/src/modules/image/magpie/PVRHandler.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * 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. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_PVR_HANDLER_H +#define LOVE_IMAGE_MAGPIE_PVR_HANDLER_H + +// LOVE +#include "common/config.h" +#include "CompressedFormatHandler.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +class PVRHandler : public CompressedFormatHandler +{ +public: + + virtual ~PVRHandler() {} + + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + +}; // PVRHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_PVR_HANDLER_H diff --git a/src/modules/image/magpie/ddsHandler.cpp b/src/modules/image/magpie/ddsHandler.cpp index 6b02867a8..b117bcea1 100644 --- a/src/modules/image/magpie/ddsHandler.cpp +++ b/src/modules/image/magpie/ddsHandler.cpp @@ -27,19 +27,19 @@ namespace image namespace magpie { -bool ddsHandler::canParse(const filesystem::FileData *data) +bool DDSHandler::canParse(const filesystem::FileData *data) { return dds::isCompressedDDS(data->getData(), data->getSize()); } -uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) { if (!dds::isDDS(filedata->getData(), filedata->getSize())) throw love::Exception("Could not decode compressed data (not a DDS file?)"); CompressedData::Format texformat = CompressedData::FORMAT_UNKNOWN; - uint8 *data = 0; + uint8 *data = nullptr; dataSize = 0; images.clear(); @@ -99,7 +99,7 @@ uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); private: static CompressedData::Format convertFormat(dds::Format ddsformat); -}; // ddsHandler +}; // DDSHandler } // magpie } // image