mirror of
https://github.com/love2d/love.git
synced 2026-08-15 15:51:12 +02:00
2459752c06
Also updated the argument order of Canvas:newImageData to match: (sliceindex, mipmap, x, y, w, h).
387 lines
10 KiB
C++
387 lines
10 KiB
C++
/**
|
|
* Copyright (c) 2006-2018 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 "Image.h"
|
|
#include "Graphics.h"
|
|
|
|
// C++
|
|
#include <algorithm>
|
|
|
|
namespace love
|
|
{
|
|
namespace graphics
|
|
{
|
|
|
|
love::Type Image::type("Image", &Texture::type);
|
|
|
|
int Image::imageCount = 0;
|
|
|
|
Image::Image(const Slices &data, const Settings &settings, bool validatedata)
|
|
: Texture(data.getTextureType())
|
|
, settings(settings)
|
|
, data(data)
|
|
, mipmapsType(settings.mipmaps ? MIPMAPS_GENERATED : MIPMAPS_NONE)
|
|
, sRGB(isGammaCorrect() && !settings.linear)
|
|
, usingDefaultTexture(false)
|
|
{
|
|
if (validatedata && data.validate() == MIPMAPS_DATA)
|
|
mipmapsType = MIPMAPS_DATA;
|
|
|
|
++imageCount;
|
|
}
|
|
|
|
Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings)
|
|
: Image(Slices(textype), settings, false)
|
|
{
|
|
if (isPixelFormatCompressed(format))
|
|
throw love::Exception("This constructor is only supported for non-compressed pixel formats.");
|
|
|
|
if (textype == TEXTURE_2D_ARRAY)
|
|
layers = slices;
|
|
else if (textype == TEXTURE_VOLUME)
|
|
depth = slices;
|
|
|
|
init(format, width, height, settings);
|
|
}
|
|
|
|
Image::Image(const Slices &slices, const Settings &settings)
|
|
: Image(slices, settings, true)
|
|
{
|
|
if (texType == TEXTURE_2D_ARRAY)
|
|
this->layers = data.getSliceCount();
|
|
else if (texType == TEXTURE_VOLUME)
|
|
this->depth = data.getSliceCount();
|
|
|
|
love::image::ImageDataBase *slice = data.get(0, 0);
|
|
init(slice->getFormat(), slice->getWidth(), slice->getHeight(), settings);
|
|
}
|
|
|
|
Image::~Image()
|
|
{
|
|
--imageCount;
|
|
}
|
|
|
|
void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
|
|
{
|
|
pixelWidth = w;
|
|
pixelHeight = h;
|
|
|
|
width = (int) (pixelWidth / settings.dpiScale + 0.5);
|
|
height = (int) (pixelHeight / settings.dpiScale + 0.5);
|
|
|
|
format = fmt;
|
|
|
|
if (isCompressed() && mipmapsType == MIPMAPS_GENERATED)
|
|
mipmapsType = MIPMAPS_NONE;
|
|
|
|
mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getTotalMipmapCount(w, h, depth);
|
|
|
|
if (mipmapCount > 1)
|
|
filter.mipmap = defaultMipmapFilter;
|
|
|
|
initQuad();
|
|
}
|
|
|
|
void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y)
|
|
{
|
|
love::image::ImageData *id = dynamic_cast<love::image::ImageData *>(d);
|
|
|
|
love::thread::EmptyLock lock;
|
|
if (id != nullptr)
|
|
lock.setLock(id->getMutex());
|
|
|
|
Rect rect = {x, y, d->getWidth(), d->getHeight()};
|
|
uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect);
|
|
}
|
|
|
|
void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps)
|
|
{
|
|
// No effect if the texture hasn't been created yet.
|
|
if (getHandle() == 0 || usingDefaultTexture)
|
|
return;
|
|
|
|
if (d->getFormat() != getPixelFormat())
|
|
throw love::Exception("Pixel formats must match.");
|
|
|
|
if (mipmap < 0 || (mipmapsType != MIPMAPS_DATA && mipmap > 0) || mipmap >= getMipmapCount())
|
|
throw love::Exception("Invalid image mipmap index %d.", mipmap + 1);
|
|
|
|
if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6)
|
|
|| (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap))
|
|
|| (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount()))
|
|
{
|
|
throw love::Exception("Invalid image slice index %d.", slice + 1);
|
|
}
|
|
|
|
Rect rect = {x, y, d->getWidth(), d->getHeight()};
|
|
|
|
int mipw = getPixelWidth(mipmap);
|
|
int miph = getPixelHeight(mipmap);
|
|
|
|
if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0
|
|
|| (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph)
|
|
{
|
|
throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d Image.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
|
|
}
|
|
|
|
love::image::ImageDataBase *oldd = data.get(slice, mipmap);
|
|
|
|
if (oldd == nullptr)
|
|
throw love::Exception("Image does not store ImageData!");
|
|
|
|
Rect currect = {0, 0, oldd->getWidth(), oldd->getHeight()};
|
|
|
|
// We can only replace the internal Data (used when reloading due to setMode)
|
|
// if the dimensions match. We also don't currently support partial updates
|
|
// of compressed textures.
|
|
if (rect == currect)
|
|
data.set(slice, mipmap, d);
|
|
else if (isPixelFormatCompressed(d->getFormat()))
|
|
throw love::Exception("Compressed textures only support replacing the entire Image.");
|
|
|
|
Graphics::flushStreamDrawsGlobal();
|
|
|
|
uploadImageData(d, mipmap, slice, x, y);
|
|
|
|
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
|
|
generateMipmaps();
|
|
}
|
|
|
|
void Image::replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps)
|
|
{
|
|
Graphics::flushStreamDrawsGlobal();
|
|
|
|
uploadByteData(format, data, size, mipmap, slice, rect);
|
|
|
|
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
|
|
generateMipmaps();
|
|
}
|
|
|
|
bool Image::isCompressed() const
|
|
{
|
|
return isPixelFormatCompressed(format);
|
|
}
|
|
|
|
bool Image::isFormatLinear() const
|
|
{
|
|
return isGammaCorrect() && !sRGB;
|
|
}
|
|
|
|
Image::MipmapsType Image::getMipmapsType() const
|
|
{
|
|
return mipmapsType;
|
|
}
|
|
|
|
Image::Slices::Slices(TextureType textype)
|
|
: textureType(textype)
|
|
{
|
|
}
|
|
|
|
void Image::Slices::clear()
|
|
{
|
|
data.clear();
|
|
}
|
|
|
|
void Image::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d)
|
|
{
|
|
if (textureType == TEXTURE_VOLUME)
|
|
{
|
|
if (mipmap >= (int) data.size())
|
|
data.resize(mipmap + 1);
|
|
|
|
if (slice >= (int) data[mipmap].size())
|
|
data[mipmap].resize(slice + 1);
|
|
|
|
data[mipmap][slice].set(d);
|
|
}
|
|
else
|
|
{
|
|
if (slice >= (int) data.size())
|
|
data.resize(slice + 1);
|
|
|
|
if (mipmap >= (int) data[slice].size())
|
|
data[slice].resize(mipmap + 1);
|
|
|
|
data[slice][mipmap].set(d);
|
|
}
|
|
}
|
|
|
|
love::image::ImageDataBase *Image::Slices::get(int slice, int mipmap) const
|
|
{
|
|
if (slice < 0 || slice >= getSliceCount(mipmap))
|
|
return nullptr;
|
|
|
|
if (mipmap < 0 || mipmap >= getMipmapCount(slice))
|
|
return nullptr;
|
|
|
|
if (textureType == TEXTURE_VOLUME)
|
|
return data[mipmap][slice].get();
|
|
else
|
|
return data[slice][mipmap].get();
|
|
}
|
|
|
|
void Image::Slices::add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips)
|
|
{
|
|
int slicecount = addallslices ? cdata->getSliceCount() : 1;
|
|
int mipcount = addallmips ? cdata->getMipmapCount() : 1;
|
|
|
|
for (int mip = 0; mip < mipcount; mip++)
|
|
{
|
|
for (int slice = 0; slice < slicecount; slice++)
|
|
set(startslice + slice, startmip + mip, cdata->getSlice(slice, mip));
|
|
}
|
|
}
|
|
|
|
int Image::Slices::getSliceCount(int mip) const
|
|
{
|
|
if (textureType == TEXTURE_VOLUME)
|
|
{
|
|
if (mip < 0 || mip >= (int) data.size())
|
|
return 0;
|
|
|
|
return (int) data[mip].size();
|
|
}
|
|
else
|
|
return (int) data.size();
|
|
}
|
|
|
|
int Image::Slices::getMipmapCount(int slice) const
|
|
{
|
|
if (textureType == TEXTURE_VOLUME)
|
|
return (int) data.size();
|
|
else
|
|
{
|
|
if (slice < 0 || slice >= (int) data.size())
|
|
return 0;
|
|
|
|
return data[slice].size();
|
|
}
|
|
}
|
|
|
|
Image::MipmapsType Image::Slices::validate() const
|
|
{
|
|
int slicecount = getSliceCount();
|
|
int mipcount = getMipmapCount(0);
|
|
|
|
if (slicecount == 0 || mipcount == 0)
|
|
throw love::Exception("At least one ImageData or CompressedImageData is required!");
|
|
|
|
if (textureType == TEXTURE_CUBE && slicecount != 6)
|
|
throw love::Exception("Cube textures must have exactly 6 sides.");
|
|
|
|
image::ImageDataBase *firstdata = get(0, 0);
|
|
|
|
int w = firstdata->getWidth();
|
|
int h = firstdata->getHeight();
|
|
int depth = textureType == TEXTURE_VOLUME ? slicecount : 1;
|
|
PixelFormat format = firstdata->getFormat();
|
|
|
|
int expectedmips = Texture::getTotalMipmapCount(w, h, depth);
|
|
|
|
if (mipcount != expectedmips && mipcount != 1)
|
|
throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount);
|
|
|
|
if (textureType == TEXTURE_CUBE && w != h)
|
|
throw love::Exception("Cube images must have equal widths and heights for each cube face.");
|
|
|
|
int mipw = w;
|
|
int miph = h;
|
|
int mipslices = slicecount;
|
|
|
|
for (int mip = 0; mip < mipcount; mip++)
|
|
{
|
|
if (textureType == TEXTURE_VOLUME)
|
|
{
|
|
slicecount = getSliceCount(mip);
|
|
|
|
if (slicecount != mipslices)
|
|
throw love::Exception("Invalid number of image data layers in mipmap level %d (expected %d, got %d)", mip+1, mipslices, slicecount);
|
|
}
|
|
|
|
for (int slice = 0; slice < slicecount; slice++)
|
|
{
|
|
auto slicedata = get(slice, mip);
|
|
|
|
if (slicedata == nullptr)
|
|
throw love::Exception("Missing image data (slice %d, mipmap level %d)", slice+1, mip+1);
|
|
|
|
int realw = slicedata->getWidth();
|
|
int realh = slicedata->getHeight();
|
|
|
|
if (getMipmapCount(slice) != mipcount)
|
|
throw love::Exception("All Image layers must have the same mipmap count.");
|
|
|
|
if (mipw != realw)
|
|
throw love::Exception("Width of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, mipw, realw);
|
|
|
|
if (miph != realh)
|
|
throw love::Exception("Height of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, miph, realh);
|
|
|
|
if (format != slicedata->getFormat())
|
|
throw love::Exception("All Image slices and mipmaps must have the same pixel format.");
|
|
}
|
|
|
|
mipw = std::max(mipw / 2, 1);
|
|
miph = std::max(miph / 2, 1);
|
|
|
|
if (textureType == TEXTURE_VOLUME)
|
|
mipslices = std::max(mipslices / 2, 1);
|
|
}
|
|
|
|
if (mipcount > 1)
|
|
return MIPMAPS_DATA;
|
|
else
|
|
return MIPMAPS_NONE;
|
|
}
|
|
|
|
bool Image::getConstant(const char *in, SettingType &out)
|
|
{
|
|
return settingTypes.find(in, out);
|
|
}
|
|
|
|
bool Image::getConstant(SettingType in, const char *&out)
|
|
{
|
|
return settingTypes.find(in, out);
|
|
}
|
|
|
|
const char *Image::getConstant(SettingType in)
|
|
{
|
|
const char *name = nullptr;
|
|
getConstant(in, name);
|
|
return name;
|
|
}
|
|
|
|
std::vector<std::string> Image::getConstants(SettingType)
|
|
{
|
|
return settingTypes.getNames();
|
|
}
|
|
|
|
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM>::Entry Image::settingTypeEntries[] =
|
|
{
|
|
{ "mipmaps", SETTING_MIPMAPS },
|
|
{ "linear", SETTING_LINEAR },
|
|
{ "dpiscale", SETTING_DPI_SCALE },
|
|
};
|
|
|
|
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM> Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries));
|
|
|
|
} // graphics
|
|
} // love
|