Browse Source

Merge remote-tracking branch 'origin/12.0-development' into vulkan

niki 2 years ago
parent
commit
6d9f855bf4

+ 6 - 1
src/libraries/luahttps/src/windows/SChannelConnection.cpp

@@ -45,7 +45,8 @@ static void enqueue_prepend(std::vector<char> &buffer, char *data, size_t size)
 {
 {
 	size_t oldSize = buffer.size();
 	size_t oldSize = buffer.size();
 	buffer.resize(oldSize + size);
 	buffer.resize(oldSize + size);
-	memmove(&buffer[size], &buffer[0], oldSize);
+	if (oldSize > 0)
+		memmove(&buffer[size], &buffer[0], oldSize);
 	memcpy(&buffer[0], data, size);
 	memcpy(&buffer[0], data, size);
 }
 }
 
 
@@ -61,6 +62,10 @@ static size_t dequeue(std::vector<char> &buffer, char *data, size_t size)
 		memmove(&buffer[0], &buffer[size], remaining);
 		memmove(&buffer[0], &buffer[size], remaining);
 		buffer.resize(remaining);
 		buffer.resize(remaining);
 	}
 	}
+	else
+	{
+		buffer.resize(0);
+	}
 
 
 	return size;
 	return size;
 }
 }

+ 1 - 1
src/modules/graphics/metal/Buffer.mm

@@ -123,7 +123,7 @@ void *Buffer::map(MapType map, size_t offset, size_t size)
 		return nullptr;
 		return nullptr;
 
 
 	if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
 	if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
-		return  nullptr;
+		return nullptr;
 
 
 	Range r(offset, size);
 	Range r(offset, size);
 
 

+ 8 - 7
src/modules/graphics/metal/Graphics.mm

@@ -1520,14 +1520,17 @@ void Graphics::present(void *screenshotCallbackData)
 
 
 	deprecations.draw(this);
 	deprecations.draw(this);
 
 
+	// endPass calls useRenderEncoder, which makes sure activeDrawable is set
+	// when possible.
 	endPass();
 	endPass();
 
 
 	id<MTLBuffer> screenshotbuffer = nil;
 	id<MTLBuffer> screenshotbuffer = nil;
 
 
+	int w = activeDrawable.texture.width;
+	int h = activeDrawable.texture.height;
+
 	if (!pendingScreenshotCallbacks.empty())
 	if (!pendingScreenshotCallbacks.empty())
 	{
 	{
-		int w = activeDrawable.texture.width;
-		int h = activeDrawable.texture.height;
 		size_t size = w * h * 4;
 		size_t size = w * h * 4;
 
 
 		screenshotbuffer = [device newBufferWithLength:size options:MTLResourceStorageModeShared];
 		screenshotbuffer = [device newBufferWithLength:size options:MTLResourceStorageModeShared];
@@ -1540,7 +1543,7 @@ void Graphics::present(void *screenshotCallbackData)
 						 sourceSlice:0
 						 sourceSlice:0
 						 sourceLevel:0
 						 sourceLevel:0
 						sourceOrigin:MTLOriginMake(0, 0, 0)
 						sourceOrigin:MTLOriginMake(0, 0, 0)
-						  sourceSize:MTLSizeMake(w, h, 0)
+						  sourceSize:MTLSizeMake(w, h, 1)
 							toBuffer:screenshotbuffer
 							toBuffer:screenshotbuffer
 				   destinationOffset:0
 				   destinationOffset:0
 			  destinationBytesPerRow:w * 4
 			  destinationBytesPerRow:w * 4
@@ -1564,12 +1567,12 @@ void Graphics::present(void *screenshotCallbackData)
 
 
 	submitCommandBuffer(SUBMIT_DONE);
 	submitCommandBuffer(SUBMIT_DONE);
 
 
+	activeDrawable = nil;
+
 	if (!pendingScreenshotCallbacks.empty())
 	if (!pendingScreenshotCallbacks.empty())
 	{
 	{
 		[cmd waitUntilCompleted];
 		[cmd waitUntilCompleted];
 
 
-		int w = activeDrawable.texture.width;
-		int h = activeDrawable.texture.height;
 		size_t size = w * h * 4;
 		size_t size = w * h * 4;
 
 
 		auto imagemodule = Module::getInstance<love::image::Image>(M_IMAGE);
 		auto imagemodule = Module::getInstance<love::image::Image>(M_IMAGE);
@@ -1620,8 +1623,6 @@ void Graphics::present(void *screenshotCallbackData)
 	// This is set to NO when there are pending screen captures.
 	// This is set to NO when there are pending screen captures.
 	metalLayer.framebufferOnly = YES;
 	metalLayer.framebufferOnly = YES;
 
 
-	activeDrawable = nil;
-
 	// Reset the per-frame stat counts.
 	// Reset the per-frame stat counts.
 	drawCalls = 0;
 	drawCalls = 0;
 	shaderSwitches = 0;
 	shaderSwitches = 0;

+ 5 - 0
src/modules/image/FormatHandler.cpp

@@ -70,5 +70,10 @@ void FormatHandler::freeRawPixels(unsigned char *mem)
 	delete[] mem;
 	delete[] mem;
 }
 }
 
 
+void FormatHandler::freeEncodedImage(unsigned char *mem)
+{
+	delete[] mem;
+}
+
 } // image
 } // image
 } // love
 } // love

+ 6 - 0
src/modules/image/FormatHandler.h

@@ -43,6 +43,7 @@ public:
 	{
 	{
 		ENCODED_TGA,
 		ENCODED_TGA,
 		ENCODED_PNG,
 		ENCODED_PNG,
+		ENCODED_EXR,
 		ENCODED_MAX_ENUM
 		ENCODED_MAX_ENUM
 	};
 	};
 
 
@@ -116,6 +117,11 @@ public:
 	 **/
 	 **/
 	virtual void freeRawPixels(unsigned char *mem);
 	virtual void freeRawPixels(unsigned char *mem);
 
 
+	/**
+	 * Frees encoded image memory allocated by the format handler.
+	 **/
+	virtual void freeEncodedImage(unsigned char *mem);
+
 }; // FormatHandler
 }; // FormatHandler
 
 
 } // image
 } // image

+ 3 - 2
src/modules/image/ImageData.cpp

@@ -206,12 +206,12 @@ love::filesystem::FileData *ImageData::encode(FormatHandler::EncodedFormat encod
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
-		encoder->freeRawPixels(encodedimage.data);
+		encoder->freeEncodedImage(encodedimage.data);
 		throw;
 		throw;
 	}
 	}
 
 
 	memcpy(filedata->getData(), encodedimage.data, encodedimage.size);
 	memcpy(filedata->getData(), encodedimage.data, encodedimage.size);
-	encoder->freeRawPixels(encodedimage.data);
+	encoder->freeEncodedImage(encodedimage.data);
 
 
 	if (writefile)
 	if (writefile)
 	{
 	{
@@ -859,6 +859,7 @@ StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM>::Entry
 {
 {
 	{"tga", FormatHandler::ENCODED_TGA},
 	{"tga", FormatHandler::ENCODED_TGA},
 	{"png", FormatHandler::ENCODED_PNG},
 	{"png", FormatHandler::ENCODED_PNG},
+	{"exr", FormatHandler::ENCODED_EXR},
 };
 };
 
 
 StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM> ImageData::encodedFormats(ImageData::encodedFormatEntries, sizeof(ImageData::encodedFormatEntries));
 StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM> ImageData::encodedFormats(ImageData::encodedFormatEntries, sizeof(ImageData::encodedFormatEntries));

+ 185 - 7
src/modules/image/magpie/EXRHandler.cpp

@@ -47,8 +47,27 @@ bool EXRHandler::canDecode(Data *data)
 	return ParseEXRVersionFromMemory(&version, (const unsigned char *) data->getData(), data->getSize()) == TINYEXR_SUCCESS;
 	return ParseEXRVersionFromMemory(&version, (const unsigned char *) data->getData(), data->getSize()) == TINYEXR_SUCCESS;
 }
 }
 
 
-bool EXRHandler::canEncode(PixelFormat /*rawFormat*/, EncodedFormat /*encodedFormat*/)
+bool EXRHandler::canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat)
 {
 {
+	if (encodedFormat != ENCODED_EXR)
+		return false;
+
+	switch (rawFormat)
+	{
+	case PIXELFORMAT_R16_FLOAT:
+	case PIXELFORMAT_R32_FLOAT:
+	case PIXELFORMAT_R32_UINT:
+	case PIXELFORMAT_RG16_FLOAT:
+	case PIXELFORMAT_RG32_FLOAT:
+	case PIXELFORMAT_RG32_UINT:
+	case PIXELFORMAT_RGBA16_FLOAT:
+	case PIXELFORMAT_RGBA32_FLOAT:
+	case PIXELFORMAT_RGBA32_UINT:
+		return true;
+	default:
+		return false;
+	}
+
 	return false;
 	return false;
 }
 }
 
 
@@ -76,7 +95,7 @@ static void getEXRChannels(const EXRHeader &header, const EXRImage &image, T *rg
 }
 }
 
 
 template <typename T>
 template <typename T>
-static T *loadEXRChannels(int width, int height, T *rgba[4], T one)
+static T *readEXRChannels(int width, int height, T *rgba[4], T one)
 {
 {
 	T *data = nullptr;
 	T *data = nullptr;
 
 
@@ -105,6 +124,20 @@ static T *loadEXRChannels(int width, int height, T *rgba[4], T one)
 	return data;
 	return data;
 }
 }
 
 
+template <typename T>
+static void writeEXRChannels(int width, int height, int components, const T *pixels, T *rgba[4])
+{
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			size_t offset = y * width + x;
+			for (int c = 0; c < components; c++)
+				rgba[c][offset] = pixels[offset * components + c];
+		}
+	}
+}
+
 FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 {
 {
 	const char *err = "unknown error";
 	const char *err = "unknown error";
@@ -131,7 +164,10 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 			throw love::Exception("Could not parse EXR image header: %s", err);
 			throw love::Exception("Could not parse EXR image header: %s", err);
 
 
 		if (LoadEXRImageFromMemory(&exrImage, &exrHeader, mem, memsize, &err) != TINYEXR_SUCCESS)
 		if (LoadEXRImageFromMemory(&exrImage, &exrHeader, mem, memsize, &err) != TINYEXR_SUCCESS)
+		{
+			FreeEXRHeader(&exrHeader);
 			throw love::Exception("Could not decode EXR image: %s", err);
 			throw love::Exception("Could not decode EXR image: %s", err);
+		}
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
@@ -145,6 +181,7 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 	{
 	{
 		if (pixelType != exrHeader.pixel_types[i])
 		if (pixelType != exrHeader.pixel_types[i])
 		{
 		{
+			FreeEXRHeader(&exrHeader);
 			FreeEXRImage(&exrImage);
 			FreeEXRImage(&exrImage);
 			throw love::Exception("Could not decode EXR image: all channels must have the same data type.");
 			throw love::Exception("Could not decode EXR image: all channels must have the same data type.");
 		}
 		}
@@ -153,7 +190,25 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 	img.width  = exrImage.width;
 	img.width  = exrImage.width;
 	img.height = exrImage.height;
 	img.height = exrImage.height;
 
 
-	if (pixelType == TINYEXR_PIXELTYPE_HALF)
+	if (pixelType == TINYEXR_PIXELTYPE_UINT)
+	{
+		img.format = PIXELFORMAT_RGBA32_UINT;
+
+		uint32 *rgba[4] = {nullptr};
+		getEXRChannels(exrHeader, exrImage, rgba);
+
+		try
+		{
+			img.data = (unsigned char *) readEXRChannels(img.width, img.height, rgba, 1u);
+		}
+		catch (love::Exception &)
+		{
+			FreeEXRHeader(&exrHeader);
+			FreeEXRImage(&exrImage);
+			throw;
+		}
+	}
+	else if (pixelType == TINYEXR_PIXELTYPE_HALF)
 	{
 	{
 		img.format = PIXELFORMAT_RGBA16_FLOAT;
 		img.format = PIXELFORMAT_RGBA16_FLOAT;
 
 
@@ -162,10 +217,11 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 
 
 		try
 		try
 		{
 		{
-			img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, float32to16(1.0f));
+			img.data = (unsigned char *) readEXRChannels(img.width, img.height, rgba, float32to16(1.0f));
 		}
 		}
 		catch (love::Exception &)
 		catch (love::Exception &)
 		{
 		{
+			FreeEXRHeader(&exrHeader);
 			FreeEXRImage(&exrImage);
 			FreeEXRImage(&exrImage);
 			throw;
 			throw;
 		}
 		}
@@ -179,30 +235,145 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data)
 
 
 		try
 		try
 		{
 		{
-			img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, 1.0f);
+			img.data = (unsigned char *) readEXRChannels(img.width, img.height, rgba, 1.0f);
 		}
 		}
 		catch (love::Exception &)
 		catch (love::Exception &)
 		{
 		{
+			FreeEXRHeader(&exrHeader);
 			FreeEXRImage(&exrImage);
 			FreeEXRImage(&exrImage);
 			throw;
 			throw;
 		}
 		}
 	}
 	}
 	else
 	else
 	{
 	{
+		FreeEXRHeader(&exrHeader);
 		FreeEXRImage(&exrImage);
 		FreeEXRImage(&exrImage);
 		throw love::Exception("Could not decode EXR image: unknown pixel format.");
 		throw love::Exception("Could not decode EXR image: unknown pixel format.");
 	}
 	}
 
 
 	img.size = getPixelFormatSliceSize(img.format, img.width, img.height);
 	img.size = getPixelFormatSliceSize(img.format, img.width, img.height);
 
 
+	FreeEXRHeader(&exrHeader);
 	FreeEXRImage(&exrImage);
 	FreeEXRImage(&exrImage);
 
 
 	return img;
 	return img;
 }
 }
 
 
-FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage & /*img*/, EncodedFormat /*encodedFormat*/)
+FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage &img, EncodedFormat encodedFormat)
 {
 {
-	throw love::Exception("Invalid format.");
+	if (!canEncode(img.format, encodedFormat))
+	{
+		if (encodedFormat != ENCODED_EXR)
+			throw love::Exception("EXR encoder cannot encode to non-EXR format.");
+		else
+			throw love::Exception("EXR encoder cannot encode the given pixel format.");
+	}
+
+	const auto &formatinfo = getPixelFormatInfo(img.format);
+
+	EXRHeader exrHeader;
+	InitEXRHeader(&exrHeader);
+
+	exrHeader.num_channels = formatinfo.components;
+
+	// TODO: this could be configurable.
+	exrHeader.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
+
+	// TinyEXR expects malloc here because FreeEXRHeader uses free().
+	exrHeader.channels = (EXRChannelInfo *) malloc(sizeof(EXRChannelInfo) * exrHeader.num_channels);
+	exrHeader.pixel_types = (int *) malloc(sizeof(int) * exrHeader.num_channels);
+	exrHeader.requested_pixel_types = (int *) malloc(sizeof(int) * exrHeader.num_channels);
+
+	int pixeltype = -1;
+	if (formatinfo.dataType == PIXELFORMATTYPE_UINT)
+	{
+		pixeltype = TINYEXR_PIXELTYPE_UINT;
+	}
+	else if (formatinfo.dataType == PIXELFORMATTYPE_SFLOAT)
+	{
+		if (formatinfo.blockSize / formatinfo.components == 2)
+			pixeltype = TINYEXR_PIXELTYPE_HALF;
+		else if (formatinfo.blockSize / formatinfo.components == 4)
+			pixeltype = TINYEXR_PIXELTYPE_FLOAT;
+	}
+
+	if (pixeltype == -1)
+	{
+		FreeEXRHeader(&exrHeader);
+		throw love::Exception("Cannot convert the given pixel format to an EXR pixel type.");
+	}
+
+	for (int i = 0; i < exrHeader.num_channels; i++)
+	{
+		exrHeader.channels[i] = EXRChannelInfo();
+
+		const char names[] = {'R', 'G', 'B', 'A'};
+		exrHeader.channels[i].name[0] = names[i];
+
+		exrHeader.pixel_types[i] = pixeltype;
+		exrHeader.requested_pixel_types[i] = pixeltype;
+	}
+
+	EXRImage exrImage;
+	InitEXRImage(&exrImage);
+
+	exrImage.width = img.width;
+	exrImage.height = img.height;
+	exrImage.num_channels = exrHeader.num_channels;
+
+	exrImage.images = (unsigned char **) malloc(sizeof(unsigned char *) * exrImage.num_channels);
+
+	for (int i = 0; i < exrImage.num_channels; i++)
+	{
+		size_t componentsize = pixeltype == TINYEXR_PIXELTYPE_HALF ? 2 : 4;
+
+		exrImage.images[i] = (unsigned char *) malloc(img.width * img.height * componentsize);
+
+		if (exrImage.images[i] == nullptr)
+		{
+			FreeEXRHeader(&exrHeader);
+			FreeEXRImage(&exrImage);
+			throw love::Exception("Out of memory.");
+		}
+	}
+
+	if (pixeltype == TINYEXR_PIXELTYPE_UINT)
+	{
+		writeEXRChannels(img.width, img.height, formatinfo.components, (const uint32 *) img.data, (uint32 **) exrImage.images);
+	}
+	else if (pixeltype == TINYEXR_PIXELTYPE_HALF)
+	{
+		writeEXRChannels(img.width, img.height, formatinfo.components, (const float16 *) img.data, (float16 **) exrImage.images);
+	}
+	else if (pixeltype == TINYEXR_PIXELTYPE_FLOAT)
+	{
+		writeEXRChannels(img.width, img.height, formatinfo.components, (const float *) img.data, (float **) exrImage.images);
+	}
+
+	EncodedImage encimg;
+
+	const char *err = nullptr;
+	encimg.size = SaveEXRImageToMemory(&exrImage, &exrHeader, &encimg.data, &err);
+
+	FreeEXRHeader(&exrHeader);
+	FreeEXRImage(&exrImage);
+
+	std::string errstring;
+	if (err != nullptr)
+	{
+		errstring = err;
+		FreeEXRErrorMessage(err);
+	}
+
+	if (encimg.size == 0)
+	{
+		if (!errstring.empty())
+			throw love::Exception("Could not encode EXR image: %s", errstring.c_str());
+		else
+			throw love::Exception("Could not encode EXR image");
+	}
+
+	return encimg;
 }
 }
 
 
 void EXRHandler::freeRawPixels(unsigned char *mem)
 void EXRHandler::freeRawPixels(unsigned char *mem)
@@ -210,6 +381,13 @@ void EXRHandler::freeRawPixels(unsigned char *mem)
 	delete[] mem;
 	delete[] mem;
 }
 }
 
 
+void EXRHandler::freeEncodedImage(unsigned char *mem)
+{
+	// SaveEXRImageToMemory uses malloc.
+	if (mem != nullptr)
+		::free(mem);
+}
+
 } // magpie
 } // magpie
 } // image
 } // image
 } // love
 } // love

+ 8 - 5
src/modules/image/magpie/EXRHandler.h

@@ -36,15 +36,18 @@ class EXRHandler : public FormatHandler
 {
 {
 public:
 public:
 
 
+	virtual ~EXRHandler() {}
+
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 
 
-	virtual bool canDecode(Data *data);
-	virtual bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat);
+	bool canDecode(Data *data) override;
+	bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat) override;
 
 
-	virtual DecodedImage decode(Data *data);
-	virtual EncodedImage encode(const DecodedImage &img, EncodedFormat format);
+	DecodedImage decode(Data *data) override;
+	EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
 
 
-	virtual void freeRawPixels(unsigned char *mem);
+	void freeRawPixels(unsigned char *mem) override;
+	void freeEncodedImage(unsigned char *mem) override;
 
 
 }; // EXRHandler
 }; // EXRHandler
 
 

+ 6 - 0
src/modules/image/magpie/PNGHandler.cpp

@@ -264,6 +264,12 @@ void PNGHandler::freeRawPixels(unsigned char *mem)
 		::free(mem);
 		::free(mem);
 }
 }
 
 
+void PNGHandler::freeEncodedImage(unsigned char *mem)
+{
+	if (mem)
+		::free(mem);
+}
+
 } // magpie
 } // magpie
 } // image
 } // image
 } // love
 } // love

+ 8 - 5
src/modules/image/magpie/PNGHandler.h

@@ -37,15 +37,18 @@ class PNGHandler : public FormatHandler
 {
 {
 public:
 public:
 
 
+	virtual ~PNGHandler() {}
+
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 
 
-	virtual bool canDecode(Data *data);
-	virtual bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat);
+	bool canDecode(Data *data) override;
+	bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat) override;
 
 
-	virtual DecodedImage decode(Data *data);
-	virtual EncodedImage encode(const DecodedImage &img, EncodedFormat format);
+	DecodedImage decode(Data *data) override;
+	EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
 
 
-	virtual void freeRawPixels(unsigned char *mem);
+	void freeRawPixels(unsigned char *mem) override;
+	void freeEncodedImage(unsigned char *mem) override;
 
 
 }; // PNGHandler
 }; // PNGHandler
 
 

+ 12 - 8
src/modules/image/magpie/STBHandler.cpp

@@ -123,15 +123,14 @@ FormatHandler::EncodedImage STBHandler::encode(const DecodedImage &img, EncodedF
 
 
 	encimg.size = (img.width * img.height * bpp) + headerlen;
 	encimg.size = (img.width * img.height * bpp) + headerlen;
 
 
-	// We need to use malloc because we use stb_image_free (which uses free())
-	// as our custom free() function, which is called by the ImageData after
-	// encode() is complete.
-	// stb_image's source code is compiled with this source, so calling malloc()
-	// directly is fine.
-	encimg.data = (unsigned char *) malloc(encimg.size);
-
-	if (encimg.data == nullptr)
+	try
+	{
+		encimg.data = new unsigned char[encimg.size];
+	}
+	catch (std::exception &)
+	{
 		throw love::Exception("Out of memory.");
 		throw love::Exception("Out of memory.");
+	}
 
 
 	// here's the header for the Targa file format.
 	// here's the header for the Targa file format.
 	encimg.data[0]  = 0; // ID field size
 	encimg.data[0]  = 0; // ID field size
@@ -176,6 +175,11 @@ void STBHandler::freeRawPixels(unsigned char *mem)
 	stbi_image_free(mem);
 	stbi_image_free(mem);
 }
 }
 
 
+void STBHandler::freeEncodedImage(unsigned char *mem)
+{
+	delete[] mem;
+}
+
 } // magpie
 } // magpie
 } // image
 } // image
 } // love
 } // love

+ 3 - 0
src/modules/image/magpie/STBHandler.h

@@ -40,6 +40,8 @@ class STBHandler final : public FormatHandler
 {
 {
 public:
 public:
 
 
+	virtual ~STBHandler() {}
+
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 
 
 	bool canDecode(Data *data) override;
 	bool canDecode(Data *data) override;
@@ -49,6 +51,7 @@ public:
 	EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
 	EncodedImage encode(const DecodedImage &img, EncodedFormat format) override;
 
 
 	void freeRawPixels(unsigned char *mem) override;
 	void freeRawPixels(unsigned char *mem) override;
+	void freeEncodedImage(unsigned char *mem) override;
 
 
 }; // STBHandler
 }; // STBHandler