Browse Source

Added support for ETC1 and PVR1 compressed textures contained in KTX, PKM, and PVR files.

Most desktop graphics drivers don't support those formats.

--HG--
branch : minor
Alex Szpakowski 10 years ago
parent
commit
dfdd52e499

+ 7 - 0
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
 )

+ 1 - 1
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.
 

+ 28 - 0
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 = "<group>"; };
 		FA636D8C171B72A70065623F /* wrap_RandomGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_RandomGenerator.cpp; sourceTree = "<group>"; };
 		FA636D8D171B72A70065623F /* wrap_RandomGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_RandomGenerator.h; sourceTree = "<group>"; };
+		FA6F55701A6E261800062204 /* KTXHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KTXHandler.cpp; sourceTree = "<group>"; };
+		FA6F55711A6E261800062204 /* KTXHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KTXHandler.h; sourceTree = "<group>"; };
+		FA6F55721A6E261800062204 /* PKMHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PKMHandler.cpp; sourceTree = "<group>"; };
+		FA6F55731A6E261800062204 /* PKMHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PKMHandler.h; sourceTree = "<group>"; };
+		FA6F55741A6E261800062204 /* PVRHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PVRHandler.cpp; sourceTree = "<group>"; };
+		FA6F55751A6E261800062204 /* PVRHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRHandler.h; sourceTree = "<group>"; };
+		FA6F557C1A6E28A200062204 /* CompressedFormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedFormatHandler.h; sourceTree = "<group>"; };
 		FA70573A194A3FC600670B30 /* DroppedFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DroppedFile.cpp; sourceTree = "<group>"; };
 		FA70573B194A3FC600670B30 /* DroppedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DroppedFile.h; sourceTree = "<group>"; };
 		FA70573E194A55CB00670B30 /* wrap_DroppedFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_DroppedFile.cpp; sourceTree = "<group>"; };
@@ -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 */,

+ 21 - 0
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

+ 22 - 0
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;
 	}

+ 6 - 1
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<CompressedData::Format, CompressedData::FORMAT_MAX_ENUM>::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::Format, CompressedData::FORMAT_MAX_ENUM> CompressedData::formats(CompressedData::formatEntries, sizeof(CompressedData::formatEntries));

+ 5 - 0
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
 	};
 

+ 21 - 38
src/modules/image/magpie/CompressedData.cpp

@@ -20,8 +20,6 @@
 
 #include "CompressedData.h"
 
-#include "ddsHandler.h"
-
 namespace love
 {
 namespace image
@@ -29,59 +27,44 @@ namespace image
 namespace magpie
 {
 
-CompressedData::CompressedData(love::filesystem::FileData *filedata)
+CompressedData::CompressedData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata)
 {
-	load(filedata);
-}
+	CompressedFormatHandler *parser = nullptr;
 
-CompressedData::~CompressedData()
-{
-	delete[] data;
-}
-
-void CompressedData::load(love::filesystem::FileData *filedata)
-{
-	// SubImage vector will be populated by a parser.
-	std::vector<SubImage> parsedimages;
-	Format texformat = FORMAT_UNKNOWN;
+	for (CompressedFormatHandler *handler : formats)
+	{
+		if (handler->canParse(filedata))
+		{
+			parser = handler;
+			break;
+		}
+	}
 
-	uint8 *newdata = 0;
-	size_t newdata_size = 0;
+	if (parser == nullptr)
+		throw love::Exception("Could not parse compressed data: Unknown format.");
 
-	if (ddsHandler::canParse(filedata))
-		newdata = ddsHandler::parse(filedata, parsedimages, newdata_size, texformat);
+	// DataImages SubImage vector will be populated by a parser.
+	data = parser->parse(filedata, dataImages, dataSize, format);
 
-	if (newdata == 0)
+	if (data == nullptr)
 		throw love::Exception("Could not parse compressed data.");
 
-	if (texformat == FORMAT_UNKNOWN)
+	if (format == FORMAT_UNKNOWN)
 	{
-		delete[] newdata;
+		delete[] data;
 		throw love::Exception("Could not parse compressed data: Unknown format.");
 	}
 
-	if (parsedimages.size() == 0 || newdata_size == 0)
+	if (dataImages.size() == 0 || dataSize == 0)
 	{
-		delete[] newdata;
+		delete[] data;
 		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)
+CompressedData::~CompressedData()
 {
-	if (ddsHandler::canParse(filedata))
-		return true;
-
-	return false;
+	delete[] data;
 }
 
 } // magpie

+ 5 - 7
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 <list>
+
 namespace love
 {
 namespace image
@@ -36,15 +40,9 @@ class CompressedData : public love::image::CompressedData
 {
 public:
 
-	CompressedData(love::filesystem::FileData *filedata);
+	CompressedData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata);
 	virtual ~CompressedData();
 
-	static bool isCompressed(love::filesystem::FileData *filedata);
-
-private:
-
-	void load(love::filesystem::FileData *filedata);
-
 }; // CompressedData
 
 } // magpie

+ 74 - 0
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<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format) = 0;
+
+}; // CompressedFormatHandler
+
+} // magpie
+} // image
+} // love
+
+#endif // LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H

+ 0 - 228
src/modules/image/magpie/DevilHandler.cpp

@@ -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 <IL/il.h>
-
-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

+ 21 - 2
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

+ 4 - 0
src/modules/image/magpie/Image.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "image/Image.h"
 #include "FormatHandler.h"
+#include "CompressedFormatHandler.h"
 
 // C++
 #include <list>
@@ -63,6 +64,9 @@ private:
 	// Image format handlers we can use for decoding and encoding ImageData.
 	std::list<FormatHandler *> formatHandlers;
 
+	// Compressed image format handers we can use for parsing CompressedData.
+	std::list<CompressedFormatHandler *> compressedFormatHandlers;
+
 }; // Image
 
 } // magpie

+ 241 - 0
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 <string.h>
+
+// C++
+#include <algorithm>
+
+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<CompressedData::SubImage> &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

+ 13 - 24
src/modules/image/magpie/DevilHandler.h → 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<CompressedData::SubImage> &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

+ 156 - 0
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<CompressedData::SubImage> &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
+

+ 53 - 0
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<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
+
+}; // PKMHandler
+
+} // magpie
+} // image
+} // love
+
+#endif // LOVE_IMAGE_MAGPIE_PKM_HANDLER_H

+ 385 - 0
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 <algorithm>
+
+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<CompressedData::SubImage> &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

+ 51 - 0
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<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
+
+}; // PVRHandler
+
+} // magpie
+} // image
+} // love
+
+#endif // LOVE_IMAGE_MAGPIE_PVR_HANDLER_H

+ 4 - 4
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<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format)
+uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &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<CompressedD
 	return data;
 }
 
-CompressedData::Format ddsHandler::convertFormat(dds::Format ddsformat)
+CompressedData::Format DDSHandler::convertFormat(dds::Format ddsformat)
 {
 	switch (ddsformat)
 	{

+ 7 - 24
src/modules/image/magpie/ddsHandler.h

@@ -22,9 +22,7 @@
 #define LOVE_IMAGE_MAGPIE_DDS_HANDLER_H
 
 // LOVE
-#include "common/EnumMap.h"
-#include "filesystem/FileData.h"
-#include "image/CompressedData.h"
+#include "CompressedFormatHandler.h"
 
 // dds parser
 #include "ddsparse/ddsparse.h"
@@ -42,36 +40,21 @@ namespace magpie
 /**
  * Interface between CompressedData and the ddsparse library.
  **/
-class ddsHandler
+class DDSHandler : public CompressedFormatHandler
 {
 public:
 
-	/**
-	 * Determines whether a particular FileData can be parsed as CompressedData
-	 * by this handler.
-	 * @param data The data to parse.
-	 **/
-	static bool canParse(const filesystem::FileData *data);
+	virtual ~DDSHandler() {}
 
-	/**
-	 * 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] images 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.
-	 **/
-	static uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
+	// Implements CompressedFormatHandler.
+	virtual bool canParse(const filesystem::FileData *data);
+	virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
 
 private:
 
 	static CompressedData::Format convertFormat(dds::Format ddsformat);
 
-}; // ddsHandler
+}; // DDSHandler
 
 } // magpie
 } // image