Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,356 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/BlitSurface.h>
#include <Image/Surface.h>
#include <Jolt/Core/Color.h>
#include <Jolt/Core/Profiler.h>
//////////////////////////////////////////////////////////////////////////////////////////
// BlitSettings
//////////////////////////////////////////////////////////////////////////////////////////
const BlitSettings BlitSettings::sDefault;
BlitSettings::BlitSettings() :
mConvertRGBToAlpha(false),
mConvertAlphaToRGB(false),
mConvertToGrayScale(false),
mInvertAlpha(false),
mColorKeyAlpha(false),
mColorKeyStart(240, 0, 240),
mColorKeyEnd(255, 15, 255)
{
}
bool BlitSettings::operator == (const BlitSettings &inRHS) const
{
return mConvertRGBToAlpha == inRHS.mConvertRGBToAlpha
&& mConvertAlphaToRGB == inRHS.mConvertAlphaToRGB
&& mConvertToGrayScale == inRHS.mConvertToGrayScale
&& mInvertAlpha == inRHS.mInvertAlpha
&& mColorKeyAlpha == inRHS.mColorKeyAlpha
&& mColorKeyStart == inRHS.mColorKeyStart
&& mColorKeyEnd == inRHS.mColorKeyEnd
&& mZoomSettings == inRHS.mZoomSettings;
}
//////////////////////////////////////////////////////////////////////////////////////////
// Converting from one format to another
//////////////////////////////////////////////////////////////////////////////////////////
// The macro COL(s) converts color s to another color given the mapping tables
#define CMP(s, c) map[256 * c + ((s & src_mask[c]) >> src_shift[c])]
#define COL(s) (CMP(s, 0) + CMP(s, 1) + CMP(s, 2) + CMP(s, 3))
static void sComputeTranslationTable(const FormatDescription & inSrcDesc, const FormatDescription & inDstDesc, uint32 *outMask, uint32 *outShift, uint32 *outMap)
{
JPH_PROFILE("sComputeTranslationTable");
// Compute translation tables for each color component
uint32 written_mask = 0;
for (int c = 0; c < 4; ++c)
{
outMask[c] = inSrcDesc.GetComponentMask(c);
outShift[c] = CountTrailingZeros(outMask[c]);
uint32 src_shifted_mask = outMask[c] >> outShift[c];
uint32 dst_mask = inDstDesc.GetComponentMask(c);
uint32 dst_shift = CountTrailingZeros(dst_mask);
uint32 dst_shifted_mask = dst_mask >> dst_shift;
if ((written_mask & dst_mask) != 0)
{
dst_mask = 0;
dst_shift = 0;
dst_shifted_mask = 0;
}
else
written_mask |= dst_mask;
float scale = src_shifted_mask > 0? float(dst_shifted_mask) / src_shifted_mask : 0.0f;
uint32 entry = 0;
if (src_shifted_mask != 0)
for (; entry <= src_shifted_mask; ++entry)
outMap[256 * c + entry] = uint32(round(scale * entry)) << dst_shift;
for (; entry < 256; ++entry)
outMap[256 * c + entry] = dst_mask;
}
}
static bool sConvertImageDifferentTypes(RefConst<Surface> inSrc, Ref<Surface> ioDst)
{
JPH_PROFILE("sConvertImageDifferentTypes");
// Get image properties
int sbpp = inSrc->GetBytesPerPixel();
int dbpp = ioDst->GetBytesPerPixel();
int width = inSrc->GetWidth();
int height = inSrc->GetHeight();
JPH_ASSERT(width == ioDst->GetWidth());
JPH_ASSERT(height == ioDst->GetHeight());
// Compute conversion map
uint32 src_mask[4];
uint32 src_shift[4];
uint32 map[4 * 256];
sComputeTranslationTable(inSrc->GetFormatDescription(), ioDst->GetFormatDescription(), src_mask, src_shift, map);
inSrc->Lock(ESurfaceLockMode::Read);
ioDst->Lock(ESurfaceLockMode::Write);
// Convert the image
for (int y = 0; y < height; ++y)
{
const uint8 *s = inSrc->GetScanLine(y);
const uint8 *s_end = inSrc->GetScanLine(y) + width * sbpp;
uint8 *d = ioDst->GetScanLine(y);
while (s < s_end)
{
uint32 src = 0;
memcpy(&src, s, sbpp);
uint32 dst = COL(src);
memcpy(d, &dst, dbpp);
s += sbpp;
d += dbpp;
}
}
inSrc->UnLock();
ioDst->UnLock();
return true;
}
static bool sConvertImageSameTypes(RefConst<Surface> inSrc, Ref<Surface> ioDst)
{
JPH_PROFILE("sConvertImageSameTypes");
// Get image properties
int dbpp = ioDst->GetBytesPerPixel();
int width = inSrc->GetWidth();
int height = inSrc->GetHeight();
JPH_ASSERT(inSrc->GetFormat() == ioDst->GetFormat());
JPH_ASSERT(dbpp == inSrc->GetBytesPerPixel());
JPH_ASSERT(width == ioDst->GetWidth());
JPH_ASSERT(height == ioDst->GetHeight());
inSrc->Lock(ESurfaceLockMode::Read);
ioDst->Lock(ESurfaceLockMode::Write);
// Copy the image line by line to compensate for stride
for (int y = 0; y < height; ++y)
memcpy(ioDst->GetScanLine(y), inSrc->GetScanLine(y), width * dbpp);
inSrc->UnLock();
ioDst->UnLock();
return true;
}
static bool sConvertImage(RefConst<Surface> inSrc, Ref<Surface> ioDst)
{
JPH_PROFILE("sConvertImage");
if (inSrc->GetFormat() == ioDst->GetFormat())
return sConvertImageSameTypes(inSrc, ioDst);
else
return sConvertImageDifferentTypes(inSrc, ioDst);
}
//////////////////////////////////////////////////////////////////////////////////////////
// Special color conversions
//////////////////////////////////////////////////////////////////////////////////////////
static void sConvertRGBToAlpha(Ref<Surface> ioSurface)
{
JPH_PROFILE("sConvertRGBToAlpha");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Convert RGB values to alpha values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
c->a = c->GetIntensity();
++c;
}
}
}
static void sConvertAlphaToRGB(Ref<Surface> ioSurface)
{
JPH_PROFILE("sConvertAlphaToRGB");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Convert alpha values to RGB values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
c->r = c->g = c->b = c->a;
++c;
}
}
}
static void sConvertToGrayScale(Ref<Surface> ioSurface)
{
JPH_PROFILE("sConvertToGrayScale");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Convert RGB values to grayscale values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
uint8 intensity = c->GetIntensity();
c->r = intensity;
c->g = intensity;
c->b = intensity;
++c;
}
}
}
static void sInvertAlpha(Ref<Surface> ioSurface)
{
JPH_PROFILE("sInvertAlpha");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Invert all alpha values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
c->a = uint8(255 - c->a);
++c;
}
}
}
static void sColorKeyAlpha(Ref<Surface> ioSurface, ColorArg inStart, ColorArg inEnd)
{
JPH_PROFILE("sColorKeyAlpha");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Set alpha values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
if (c->r >= inStart.r && c->r <= inEnd.r && c->g >= inStart.g && c->g <= inEnd.g && c->b >= inStart.b && c->b <= inEnd.b)
c->a = 0;
else
c->a = 255;
++c;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// BlitSurface
//////////////////////////////////////////////////////////////////////////////////////////
bool BlitSurface(RefConst<Surface> inSrc, Ref<Surface> ioDst, const BlitSettings &inBlitSettings)
{
JPH_PROFILE("BlitSurface");
// Do extra conversion options
RefConst<Surface> src = inSrc;
if (inBlitSettings.mConvertRGBToAlpha || inBlitSettings.mConvertAlphaToRGB || inBlitSettings.mConvertToGrayScale || inBlitSettings.mInvertAlpha || inBlitSettings.mColorKeyAlpha)
{
// Do them on A8R8G8B8 format so the conversion routines are simple
Ref<Surface> tmp = new SoftwareSurface(inSrc->GetWidth(), inSrc->GetHeight(), ESurfaceFormat::A8R8G8B8);
sConvertImage(inSrc, tmp);
src = tmp;
// Perform all optional conversions
tmp->Lock(ESurfaceLockMode::ReadWrite);
if (inBlitSettings.mConvertRGBToAlpha)
sConvertRGBToAlpha(tmp);
if (inBlitSettings.mConvertAlphaToRGB)
sConvertAlphaToRGB(tmp);
if (inBlitSettings.mConvertToGrayScale)
sConvertToGrayScale(tmp);
if (inBlitSettings.mInvertAlpha)
sInvertAlpha(tmp);
if (inBlitSettings.mColorKeyAlpha)
sColorKeyAlpha(tmp, inBlitSettings.mColorKeyStart, inBlitSettings.mColorKeyEnd);
tmp->UnLock();
}
if (src->GetWidth() != ioDst->GetWidth() || src->GetHeight() != ioDst->GetHeight())
{
// Zoom the image if the destination size is not equal to the source size
if (!ZoomImage(src, ioDst, inBlitSettings.mZoomSettings))
return false;
}
else
{
// Convert the image if the sizes are equal
if (!sConvertImage(src, ioDst))
return false;
}
return true;
}

View File

@@ -0,0 +1,37 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Image/ZoomImage.h>
#include <Jolt/Core/Color.h>
/// Settings for blitting one surface to another with possibly different formats and dimensions. The blit
/// routine can use filtering or blurring on the fly. Also it can perform some other
/// basic operations like converting an image to grayscale or alpha only surfaces.
class BlitSettings
{
public:
/// Constructor
BlitSettings();
/// Comparison operators
bool operator == (const BlitSettings &inRHS) const;
/// Default settings
static const BlitSettings sDefault;
/// Special operations that can be applied during the blit
bool mConvertRGBToAlpha; ///< Convert RGB values to alpha values (RGB values remain untouched)
bool mConvertAlphaToRGB; ///< Convert alpha values to grayscale RGB values (Alpha values remain untouched)
bool mConvertToGrayScale; ///< Convert RGB values to grayscale values (Alpha values remain untouched)
bool mInvertAlpha; ///< Invert alpha values
bool mColorKeyAlpha; ///< If true, colors in the range mColorKeyStart..mColorKeyEnd will get an alpha of 0, other colors will get an alpha of 255
Color mColorKeyStart;
Color mColorKeyEnd;
ZoomSettings mZoomSettings; ///< Settings for resizing the image
};
/// Copies an image from inSrc to inDst, converting it on the fly as defined by inBlitSettings
bool BlitSurface(RefConst<Surface> inSrc, Ref<Surface> ioDst, const BlitSettings &inBlitSettings = BlitSettings::sDefault);

View File

@@ -0,0 +1,199 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/LoadBMP.h>
#include <Image/BlitSurface.h>
#include <Image/Surface.h>
#pragma pack (1)
struct BitmapFileHeader
{
char mTypeB;
char mTypeM;
uint32 mSize;
uint16 mReserved1;
uint16 mReserved2;
uint32 mOffBits;
};
struct BitmapInfoHeader
{
uint32 mSize;
uint32 mWidth;
uint32 mHeight;
uint16 mPlanes;
uint16 mBitCount;
uint32 mCompression;
uint32 mSizeImage;
uint32 mXPelsPerMeter;
uint32 mYPelsPerMeter;
uint32 mClrUsed;
uint32 mClrImportant;
};
#pragma pack ()
Ref<Surface> LoadBMP(istream &inStream)
{
bool loaded = true;
// Read bitmap info
BitmapFileHeader bfh;
BitmapInfoHeader bih;
inStream.read((char *)&bfh, sizeof(bfh));
if (inStream.fail())
return nullptr;
inStream.read((char *)&bih, sizeof(bih));
if (inStream.fail())
return nullptr;
// Get properties
int bpp = (bih.mBitCount + 7) >> 3;
int scan_width = (bih.mWidth * bpp + 3) & (~3);
// Check if it is a bitmap
if (bfh.mTypeB != 'B' || bfh.mTypeM != 'M')
{
Trace("Not a BMP");
return nullptr;
}
// Check if bitmap is bottom-up
if (bih.mHeight <= 0)
{
Trace("Not bottom-up");
return nullptr;
}
// Check if it is not compressed
if (bih.mCompression != 0)
{
Trace("Is compressed");
return nullptr;
}
Ref<Surface> surface;
if (bih.mBitCount == 8)
{
// Load palette
uint32 *palette = new uint32 [256];
int pal_bytes = 4 * (bih.mClrUsed != 0? bih.mClrUsed : 256);
inStream.read((char *)palette, pal_bytes);
loaded = loaded && !inStream.fail();
// Seek to image data
inStream.seekg(bfh.mOffBits);
// Convert pixel data to a surface
surface = new SoftwareSurface(bih.mWidth, bih.mHeight, ESurfaceFormat::X8R8G8B8);
surface->Lock(ESurfaceLockMode::Write);
uint8 *scan_line = new uint8 [scan_width];
for (int y = bih.mHeight - 1; y >= 0; --y)
{
// Load one scan line
inStream.read((char *)scan_line, scan_width);
loaded = loaded && !inStream.fail();
// Copy one scan line
uint8 *in_pixel = scan_line;
uint32 *out_pixel = (uint32 *)surface->GetScanLine(y);
for (uint x = 0; x < bih.mWidth; ++x, ++in_pixel, ++out_pixel)
*out_pixel = palette[*in_pixel];
}
surface->UnLock();
// Release temporaries
delete [] palette;
delete [] scan_line;
}
else
{
// Determine pixel format
ESurfaceFormat format;
switch (bih.mBitCount)
{
case 16: format = ESurfaceFormat::X1R5G5B5; break;
case 24: format = ESurfaceFormat::R8G8B8; break;
default: Trace("Has invalid format"); return nullptr;
}
// Seek to image data
inStream.seekg(bfh.mOffBits);
// Convert pixel data to a surface
surface = new SoftwareSurface(bih.mWidth, bih.mHeight, format, scan_width);
surface->Lock(ESurfaceLockMode::Write);
for (int y = bih.mHeight - 1; y >= 0; --y)
{
inStream.read((char *)surface->GetScanLine(y), scan_width);
loaded = loaded && !inStream.fail();
}
surface->UnLock();
}
return loaded? surface : Ref<Surface>(nullptr);
}
bool SaveBMP(RefConst<Surface> inSurface, ostream &inStream)
{
bool stored = true;
// Convert surface if required
const Surface *src = inSurface;
Ref<Surface> tmp_src;
if (inSurface->GetFormat() != ESurfaceFormat::R8G8B8)
{
tmp_src = new SoftwareSurface(inSurface->GetWidth(), inSurface->GetHeight(), ESurfaceFormat::R8G8B8);
BlitSurface(inSurface, tmp_src);
src = tmp_src.GetPtr();
}
// Lock the surface
src->Lock(ESurfaceLockMode::Read);
JPH_ASSERT(src->GetStride() % 4 == 0);
BitmapFileHeader bfh;
BitmapInfoHeader bih;
// Fill in headers
bfh.mTypeB = 'B';
bfh.mTypeM = 'M';
bfh.mSize = sizeof(bfh) + sizeof(bih) + src->GetHeight() * src->GetStride();
bfh.mReserved1 = 0;
bfh.mReserved2 = 0;
bfh.mOffBits = sizeof(bfh) + sizeof(bih);
bih.mSize = sizeof(bih);
bih.mWidth = src->GetWidth();
bih.mHeight = src->GetHeight();
bih.mPlanes = 1;
bih.mBitCount = 24;
bih.mCompression = 0;
bih.mSizeImage = src->GetHeight() * src->GetStride();
bih.mXPelsPerMeter = 300;
bih.mYPelsPerMeter = 300;
bih.mClrUsed = 0;
bih.mClrImportant = 0;
// Write headers
inStream.write((char *)&bfh, sizeof(bfh));
stored = stored && !inStream.fail();
inStream.write((char *)&bih, sizeof(bih));
stored = stored && !inStream.fail();
// Write image data
for (int y = src->GetHeight() - 1; y >= 0; --y)
{
inStream.write((const char *)src->GetScanLine(y), src->GetStride());
stored = stored && !inStream.fail();
}
src->UnLock();
return stored;
}

View File

@@ -0,0 +1,15 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class Surface;
/// Load a windows BMP file
Ref<Surface> LoadBMP(istream &inStream);
/// Write a windows BMP file
bool SaveBMP(RefConst<Surface> inSurface, ostream &inStream);

View File

@@ -0,0 +1,130 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/LoadTGA.h>
#include <Image/Surface.h>
#pragma pack (1)
struct TGAHeader
{
uint8 mIDLength;
uint8 mColorMapType;
uint8 mImageType;
uint16 mColorMapFirstEntryIndex;
uint16 mColorMapLength;
uint8 mColorMapEntrySize;
uint16 mXOrigin;
uint16 mYOrigin;
uint16 mWidth;
uint16 mHeight;
uint8 mPixelDepth;
uint8 mImageDescriptor;
};
#pragma pack ()
Ref<Surface> LoadTGA(istream &inStream)
{
bool loaded = true;
// Read header
TGAHeader header;
inStream.read((char *)&header, sizeof(header));
if (inStream.fail())
return nullptr;
// Get properties
int bytes_per_pixel = (header.mPixelDepth + 7) >> 3;
int scan_width = bytes_per_pixel * header.mWidth;
// Check type
if (header.mImageType < 1 || header.mImageType > 2)
{
Trace("Not a readable TGA");
return nullptr;
}
// Check compression
if ((header.mImageType == 1 && header.mColorMapType != 1) || (header.mImageType == 2 && header.mColorMapType != 0))
{
Trace("Not an uncompressed TGA");
return nullptr;
}
Ref<Surface> surface;
if (header.mPixelDepth == 8)
{
// Determine pixel format
ESurfaceFormat format;
int pixel_size;
switch (header.mColorMapEntrySize)
{
case 15: format = ESurfaceFormat::X1R5G5B5; pixel_size = 2; break;
case 16: format = ESurfaceFormat::X1R5G5B5; pixel_size = 2; break;
case 24: format = ESurfaceFormat::R8G8B8; pixel_size = 3; break;
case 32: format = ESurfaceFormat::A8R8G8B8; pixel_size = 4; break;
default: Trace("Has invalid format"); return nullptr;
}
// Seek to beginning of palette
inStream.seekg(sizeof(TGAHeader) + header.mIDLength);
// Load palette
int pal_bytes = pixel_size * header.mColorMapLength;
uint8 *palette = new uint8 [pal_bytes];
inStream.read((char *)palette, pal_bytes);
loaded = loaded && !inStream.fail();
// Convert pixel data to a surface
surface = new SoftwareSurface(header.mWidth, header.mHeight, format);
surface->Lock(ESurfaceLockMode::Write);
uint8 *scan_line = new uint8 [scan_width];
for (int y = header.mHeight - 1; y >= 0; --y)
{
// Load one scan line
inStream.read((char *)scan_line, scan_width);
loaded = loaded && !inStream.fail();
// Copy one scan line
uint8 *in_pixel = scan_line;
uint8 *out_pixel = (uint8 *)surface->GetScanLine(y);
for (int x = 0; x < header.mWidth; ++x, ++in_pixel, out_pixel += pixel_size)
memcpy(out_pixel, palette + (*in_pixel - header.mColorMapFirstEntryIndex) * pixel_size, pixel_size);
}
surface->UnLock();
// Release temporaries
delete [] palette;
delete [] scan_line;
}
else
{
// Determine pixel format
ESurfaceFormat format;
switch (header.mPixelDepth)
{
case 15: format = ESurfaceFormat::X1R5G5B5; break;
case 16: format = ESurfaceFormat::X1R5G5B5; break;
case 24: format = ESurfaceFormat::R8G8B8; break;
case 32: format = ESurfaceFormat::A8R8G8B8; break;
default: Trace("Invalid format"); return nullptr;
}
// Convert pixel data to a surface
surface = new SoftwareSurface(header.mWidth, header.mHeight, format, scan_width);
surface->Lock(ESurfaceLockMode::Write);
for (int y = header.mHeight - 1; y >= 0; --y)
{
inStream.read((char *)surface->GetScanLine(y), scan_width);
loaded = loaded && !inStream.fail();
}
surface->UnLock();
}
return loaded? surface : Ref<Surface>(nullptr);
}

View File

@@ -0,0 +1,12 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class Surface;
/// Image routines, loads a Targa (TGA) file.
Ref<Surface> LoadTGA(istream &inStream);

View File

@@ -0,0 +1,220 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/Surface.h>
//////////////////////////////////////////////////////////////////////////////////////////
// FormatDescription
//
// Description of a surface format
//////////////////////////////////////////////////////////////////////////////////////////
// Format descriptions
static FormatDescription sFormats[] =
{
// Description BPP #CMP Closest 8 Bit Closest Alpha Red Mask Green Mask Blue Mask Alpha Mask
FormatDescription("A4L4", 8, 2, ESurfaceFormat::A8L8, ESurfaceFormat::A4L4, 0x0000000f, 0x0000000f, 0x0000000f, 0x000000f0),
FormatDescription("L8", 8, 1, ESurfaceFormat::L8, ESurfaceFormat::A8L8, 0x000000ff, 0x000000ff, 0x000000ff, 0x00000000),
FormatDescription("A8", 8, 1, ESurfaceFormat::A8, ESurfaceFormat::A8, 0x00000000, 0x00000000, 0x00000000, 0x000000ff),
FormatDescription("A8L8", 16, 2, ESurfaceFormat::A8L8, ESurfaceFormat::A8L8, 0x000000ff, 0x000000ff, 0x000000ff, 0x0000ff00),
FormatDescription("R5G6B5", 16, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A1R5G5B5, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000),
FormatDescription("X1R5G5B5", 16, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A1R5G5B5, 0x00007c00, 0x000003e0, 0x0000001f, 0x00000000),
FormatDescription("X4R4G4B4", 16, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A4R4G4B4, 0x00000f00, 0x000000f0, 0x0000000f, 0x00000000),
FormatDescription("A1R5G5B5", 16, 4, ESurfaceFormat::A8R8G8B8, ESurfaceFormat::A1R5G5B5, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000),
FormatDescription("A4R4G4B4", 16, 4, ESurfaceFormat::A8R8G8B8, ESurfaceFormat::A4R4G4B4, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000),
FormatDescription("R8G8B8", 24, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A8R8G8B8, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000),
FormatDescription("B8G8R8", 24, 3, ESurfaceFormat::B8G8R8, ESurfaceFormat::A8B8G8R8, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000),
FormatDescription("X8R8G8B8", 32, 3, ESurfaceFormat::X8R8G8B8, ESurfaceFormat::A8R8G8B8, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000),
FormatDescription("X8B8G8R8", 32, 3, ESurfaceFormat::X8B8G8R8, ESurfaceFormat::A8B8G8R8, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000),
FormatDescription("A8R8G8B8", 32, 4, ESurfaceFormat::A8R8G8B8, ESurfaceFormat::A8R8G8B8, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000),
FormatDescription("A8B8G8R8", 32, 4, ESurfaceFormat::A8B8G8R8, ESurfaceFormat::A8B8G8R8, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000),
FormatDescription("Invalid", 0, 0, ESurfaceFormat::Invalid, ESurfaceFormat::Invalid, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
};
FormatDescription::FormatDescription(const char *inFormatName, int inBitsPerPixel, int inNumberOfComponents, ESurfaceFormat inClosest8BitFormat, ESurfaceFormat inClosestAlphaFormat, uint32 inRedMask, uint32 inGreenMask, uint32 inBlueMask, uint32 inAlphaMask) :
mFormatName(inFormatName),
mBitsPerPixel(inBitsPerPixel),
mNumberOfComponents(inNumberOfComponents),
mClosest8BitFormat(inClosest8BitFormat),
mClosestAlphaFormat(inClosestAlphaFormat),
mRedMask(inRedMask),
mGreenMask(inGreenMask),
mBlueMask(inBlueMask),
mAlphaMask(inAlphaMask)
{
}
uint32 FormatDescription::Encode(ColorArg inColor) const
{
uint32 col = 0;
uint32 written_mask = 0;
// Loop through all components
for (int c = 0; c < 4; ++c)
{
// Check that we have not yet written this part of the color yet
uint32 mask = GetComponentMask(c);
if ((written_mask & mask) != 0) continue;
written_mask |= mask;
// Or in this component
col |= int(round((1.0f / 255.0f) * mask * inColor(c))) & mask;
}
return col;
}
const Color FormatDescription::Decode(uint32 inColor) const
{
Color col(0, 0, 0, 0);
// Loop through all components
for (int c = 0; c < 4; ++c)
{
uint32 mask = GetComponentMask(c);
if (mask != 0)
{
uint32 shift = CountTrailingZeros(mask);
uint32 shifted_color = (inColor & mask) >> shift;
uint32 shifted_mask = mask >> shift;
col(c) = uint8((255 * shifted_color + 127) / shifted_mask);
}
else
col(c) = 255;
}
return col;
}
const FormatDescription &GetFormatDescription(ESurfaceFormat inFormat)
{
if (inFormat <= ESurfaceFormat::Invalid)
return sFormats[uint(inFormat)];
return sFormats[uint(ESurfaceFormat::Invalid)];
}
//////////////////////////////////////////////////////////////////////////////////////////
// Surface
//
// Class that contains an image in arbitrary format
//////////////////////////////////////////////////////////////////////////////////////////
Surface::Surface(int inWidth, int inHeight, ESurfaceFormat inFormat) :
mFormat(inFormat),
mWidth(inWidth),
mHeight(inHeight),
mLength(0),
mLockMode(ESurfaceLockMode::None),
mStride(0),
mData(nullptr)
{
}
Surface::~Surface()
{
JPH_ASSERT(!IsLocked());
JPH_ASSERT(mData == nullptr);
JPH_ASSERT(mStride == 0);
JPH_ASSERT(mLength == 0);
}
void Surface::Lock(ESurfaceLockMode inMode) const
{
// Check if this resource can be locked
JPH_ASSERT(!IsLocked());
JPH_ASSERT((uint(inMode) & uint(ESurfaceLockMode::ReadWrite)) != 0);
// Store mode
mLockMode = inMode;
// Lock the buffer
HardwareLock();
// Check that data and stride were filled in
JPH_ASSERT(mData != nullptr);
JPH_ASSERT(mStride > 0);
JPH_ASSERT(mLength > 0);
}
void Surface::UnLock() const
{
// Check if this resource was locked
JPH_ASSERT(IsLocked());
// Unlock the hardware resource
HardwareUnLock();
// Reset members, so we are sure they will be set next time
mLockMode = ESurfaceLockMode::None;
mStride = 0;
mLength = 0;
mData = nullptr;
}
void Surface::Clear(ColorArg inColor)
{
Lock(ESurfaceLockMode::Write);
// Get image properties
int bpp = GetBytesPerPixel();
int width = GetWidth();
int height = GetHeight();
// Determine clear color
uint32 col = GetFormatDescription().Encode(inColor);
// Clear the image
for (int y = 0; y < height; ++y)
{
uint8 *d = GetScanLine(y);
uint8 *d_end = GetScanLine(y) + width * bpp;
while (d < d_end)
{
memcpy(d, &col, bpp);
d += bpp;
}
}
UnLock();
}
//////////////////////////////////////////////////////////////////////////////////////////
// SoftwareSurface
//
// Class that contains an image in arbitrary format
//////////////////////////////////////////////////////////////////////////////////////////
SoftwareSurface::SoftwareSurface(int inWidth, int inHeight, ESurfaceFormat inFormat, int inStride) :
Surface(inWidth, inHeight, inFormat)
{
// Determine stride and length
mPixelStride = inStride == 0? ((mWidth * GetBytesPerPixel() + 3) & ~3) : inStride;
mPixelLength = mPixelStride * inHeight;
// Allocate pixel data
JPH_ASSERT(mPixelLength > 0);
mPixelData = new uint8 [mPixelLength];
}
SoftwareSurface::~SoftwareSurface()
{
delete [] mPixelData;
}
void SoftwareSurface::HardwareLock() const
{
// Get pointer to data
mData = mPixelData;
mStride = mPixelStride;
mLength = mPixelLength;
}
void SoftwareSurface::HardwareUnLock() const
{
}

View File

@@ -0,0 +1,181 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
#include <Jolt/Core/Color.h>
#include <Jolt/Core/StringTools.h>
/// Possible lock modes of a Surface
enum class ESurfaceLockMode : uint
{
None = 0 << 0, ///< Not locked, cannot be used as a parameter
Read = 1 << 0,
Write = 2 << 0,
ReadWrite = Read | Write,
};
/// Possible surface formats, most significant bit (MSB) first
enum class ESurfaceFormat : uint
{
A4L4, ///< 4 bit alpha, 4 bit luminance (grayscale)
L8, ///< 8 bit luminance (grayscale)
A8, ///< 8 bit alpha
A8L8, ///< 8 bit luminance and 8 bit alpha
R5G6B5, ///< 16 bit RGB
X1R5G5B5, ///< 16 bit RGB
X4R4G4B4, ///< 16 bit RGB
A1R5G5B5, ///< 16 bit RGBA
A4R4G4B4, ///< 16 bit RGBA
R8G8B8, ///< 24 bit RGB
B8G8R8, ///< 24 bit BGR
X8R8G8B8, ///< 32 bit RGB
X8B8G8R8, ///< 32 bit RGB
A8R8G8B8, ///< 32 bit RGBA
A8B8G8R8, ///< 32 bit BGRA
Invalid, ///< Invalid value
Count = Invalid, ///< Number of pixel formats
};
/// Description of a surface format
class FormatDescription
{
public:
/// Constructor
FormatDescription(const char *inFormatName, int inBitsPerPixel, int inNumberOfComponents, ESurfaceFormat inClosest8BitFormat, ESurfaceFormat inClosestAlphaFormat, uint32 inRedMask, uint32 inGreenMask, uint32 inBlueMask, uint32 inAlphaMask);
/// General properties
const string_view & GetFormatName() const { return mFormatName; }
int GetBytesPerPixel() const { return (mBitsPerPixel + 7) >> 3; }
int GetNumberOfComponents() const { return mNumberOfComponents; }
ESurfaceFormat GetClosest8BitFormat() const { return mClosest8BitFormat; }
ESurfaceFormat GetClosestAlphaFormat() const { return mClosestAlphaFormat; }
/// Bitcounts for the various components of the image
int GetBitsPerPixel() const { return mBitsPerPixel; }
int GetRedBitsPerPixel() const { return CountBits(mRedMask); }
int GetGreenBitsPerPixel() const { return CountBits(mGreenMask); }
int GetBlueBitsPerPixel() const { return CountBits(mBlueMask); }
int GetAlphaBitsPerPixel() const { return CountBits(mAlphaMask); }
int GetComponentBitCount(int inComponent) const { return CountBits(GetComponentMask(inComponent)); }
/// Bitmasks indicating the various components of the image
uint32 GetRedMask() const { return mRedMask; }
uint32 GetGreenMask() const { return mGreenMask; }
uint32 GetBlueMask() const { return mBlueMask; }
uint32 GetAlphaMask() const { return mAlphaMask; }
uint32 GetComponentMask(int inComponent) const { return *(&mRedMask + inComponent); }
/// Convert a single color
uint32 Encode(ColorArg inColor) const;
const Color Decode(uint32 inColor) const;
private:
string_view mFormatName; ///< User displayable String describing the format
int mBitsPerPixel; ///< Number of bits per pixel
int mNumberOfComponents; ///< Number of color components per pixel
ESurfaceFormat mClosest8BitFormat; ///< Closest matching format that has 8 bit color components
ESurfaceFormat mClosestAlphaFormat; ///< Closest matching format that has an alpha channel
uint32 mRedMask; ///< Bitmasks indicating which bits are used by which color components
uint32 mGreenMask;
uint32 mBlueMask;
uint32 mAlphaMask;
};
/// Get the description for a specific surface format
const FormatDescription & GetFormatDescription(ESurfaceFormat inFormat);
/// Class that contains an image in arbitrary format
class Surface : public RefTarget<Surface>
{
public:
/// Constructor
Surface(int inWidth, int inHeight, ESurfaceFormat inFormat);
virtual ~Surface();
/// Type of the image data
const FormatDescription & GetFormatDescription() const { return ::GetFormatDescription(mFormat); }
const string_view & GetFormatName() const { return GetFormatDescription().GetFormatName(); }
int GetBytesPerPixel() const { return GetFormatDescription().GetBytesPerPixel(); }
int GetNumberOfComponents() const { return GetFormatDescription().GetNumberOfComponents(); }
ESurfaceFormat GetClosest8BitFormat() const { return GetFormatDescription().GetClosest8BitFormat(); }
int GetBitsPerPixel() const { return GetFormatDescription().GetBitsPerPixel(); }
int GetRedBitsPerPixel() const { return GetFormatDescription().GetRedBitsPerPixel(); }
int GetGreenBitsPerPixel() const { return GetFormatDescription().GetGreenBitsPerPixel(); }
int GetBlueBitsPerPixel() const { return GetFormatDescription().GetBlueBitsPerPixel(); }
int GetAlphaBitsPerPixel() const { return GetFormatDescription().GetAlphaBitsPerPixel(); }
int GetComponentBitCount(int inComponent) const { return GetFormatDescription().GetComponentBitCount(inComponent); }
uint32 GetRedMask() const { return GetFormatDescription().GetRedMask(); }
uint32 GetGreenMask() const { return GetFormatDescription().GetGreenMask(); }
uint32 GetBlueMask() const { return GetFormatDescription().GetBlueMask(); }
uint32 GetAlphaMask() const { return GetFormatDescription().GetAlphaMask(); }
uint32 GetComponentMask(int inComponent) const { return GetFormatDescription().GetComponentMask(inComponent); }
/// Get properties of this surface
inline ESurfaceFormat GetFormat() const { return mFormat; }
inline int GetWidth() const { return mWidth; }
inline int GetHeight() const { return mHeight; }
/// Sets the image to a specific color
void Clear(ColorArg inColor = Color::sBlack);
/// Locking functions
void Lock(ESurfaceLockMode inMode) const;
void UnLock() const;
/// Current lock state
inline ESurfaceLockMode GetLockMode() const { return mLockMode; }
inline bool IsLocked() const { return mLockMode != ESurfaceLockMode::None; }
inline bool IsLockedForRead() const { return (uint(mLockMode) & uint(ESurfaceLockMode::Read)) != 0; }
inline bool IsLockedForWrite() const { return (uint(mLockMode) & uint(ESurfaceLockMode::Write)) != 0; }
inline bool IsLockedForReadWrite() const { return IsLockedForRead() && IsLockedForWrite(); }
/// Access to the image data
inline const uint8 * GetData() const { JPH_ASSERT(IsLockedForRead()); return mData; }
inline uint8 * GetData() { JPH_ASSERT(IsLockedForWrite()); return mData; }
inline int GetStride() const { JPH_ASSERT(IsLocked()); return mStride; }
inline int GetLength() const { JPH_ASSERT(IsLocked()); return mLength; }
/// Get start of a specific scanline
inline const uint8 * GetScanLine(int inScanLine) const { JPH_ASSERT(inScanLine >= 0 && inScanLine < GetHeight()); return GetData() + inScanLine * GetStride(); }
inline uint8 * GetScanLine(int inScanLine) { JPH_ASSERT(inScanLine >= 0 && inScanLine < GetHeight()); return GetData() + inScanLine * GetStride(); }
protected:
/// These functions must be overridden by the hardware buffer
virtual void HardwareLock() const = 0;
virtual void HardwareUnLock() const = 0;
/// Data
ESurfaceFormat mFormat; ///< Pixel format of the surface
int mWidth; ///< Width of the image
int mHeight; ///< Height of the image
mutable int mLength; ///< Length in bytes of the image
mutable ESurfaceLockMode mLockMode;
mutable int mStride; ///< Width of one scanline in bytes
mutable uint8 * mData; ///< Pointer to image data, starting at top-left of locked rectangle
};
/// Class that contains an image in arbitrary format, backed by normal memory (not device specific)
class SoftwareSurface : public Surface
{
public:
/// Constructor
SoftwareSurface(int inWidth, int inHeight, ESurfaceFormat inFormat, int inStride = 0);
virtual ~SoftwareSurface() override;
protected:
/// These functions must be overridden by the hardware buffer
virtual void HardwareLock() const override;
virtual void HardwareUnLock() const override;
uint8 * mPixelData;
int mPixelStride;
int mPixelLength;
};

View File

@@ -0,0 +1,526 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/ZoomImage.h>
#include <Image/Surface.h>
#include <Image/BlitSurface.h>
#include <Jolt/Core/Profiler.h>
//////////////////////////////////////////////////////////////////////////////////////////
// ImageFilter
//
// Abstract class and some implementations of a filter, essentially an 1D weighting function
// which is not zero for t e [-GetSupport(), GetSupport()] and zero for all other t
// The integrand is usually 1 although it is not required for this implementation,
// since the filter is renormalized when it is sampled.
//////////////////////////////////////////////////////////////////////////////////////////
class ImageFilter
{
public:
// Destructor
virtual ~ImageFilter() = default;
// Get support of this filter (+/- the range the filter function is not zero)
virtual float GetSupport() const = 0;
// Sample filter function at a certain point
virtual float GetValue(float t) const = 0;
};
class ImageFilterBox : public ImageFilter
{
virtual float GetSupport() const override
{
return 0.5f;
}
virtual float GetValue(float t) const override
{
if (abs(t) <= 0.5f)
return 1.0f;
else
return 0.0f;
}
};
class ImageFilterTriangle : public ImageFilter
{
virtual float GetSupport() const override
{
return 1.0f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 1.0f)
return 1.0f - t;
else
return 0.0f;
}
};
class ImageFilterBell : public ImageFilter
{
virtual float GetSupport() const override
{
return 1.5f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 0.5f)
return 0.75f - t * t;
else if (t < 1.5f)
{
t = t - 1.5f;
return 0.5f * t * t;
}
else
return 0.0f;
}
};
class ImageFilterBSpline : public ImageFilter
{
virtual float GetSupport() const override
{
return 2.0f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 1.0f)
{
float tt = t * t;
return (0.5f * tt * t) - tt + (2.0f / 3.0f);
}
else if (t < 2.0f)
{
t = 2.0f - t;
return (1.0f / 6.0f) * (t * t * t);
}
else
return 0.0f;
}
};
class ImageFilterLanczos3 : public ImageFilter
{
virtual float GetSupport() const override
{
return 3.0f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 3.0f)
return Sinc(t) * Sinc(t / 3.0f);
else
return 0.0f;
}
private:
static float Sinc(float x)
{
x *= JPH_PI;
if (abs(x) < 1.0e-5f)
return 1.0f;
return Sin(x) / x;
}
};
class ImageFilterMitchell : public ImageFilter
{
virtual float GetSupport() const override
{
return 2.0f;
}
virtual float GetValue(float t) const override
{
float tt = t * t;
t = abs(t);
if (t < 1.0f)
return (7.0f * (t * tt) - 12.0f * tt + (16.0f / 3.0f)) / 6.0f;
else if (t < 2.0f)
return ((-7.0f / 3.0f) * (t * tt) + 12.0f * tt + -20.0f * t + (32.0f / 3.0f)) / 6.0f;
else
return 0.0f;
}
};
static const ImageFilter &GetFilter(EFilter inFilter)
{
static ImageFilterBox box;
static ImageFilterTriangle triangle;
static ImageFilterBell bell;
static ImageFilterBSpline bspline;
static ImageFilterLanczos3 lanczos3;
static ImageFilterMitchell mitchell;
switch (inFilter)
{
case FilterBox: return box;
case FilterTriangle: return triangle;
case FilterBell: return bell;
case FilterBSpline: return bspline;
case FilterLanczos3: return lanczos3;
case FilterMitchell: return mitchell;
default: JPH_ASSERT(false); return mitchell;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// ZoomSettings
//////////////////////////////////////////////////////////////////////////////////////////
const ZoomSettings ZoomSettings::sDefault;
ZoomSettings::ZoomSettings() :
mFilter(FilterMitchell),
mWrapFilter(true),
mBlur(1.0f)
{
}
bool ZoomSettings::operator == (const ZoomSettings &inRHS) const
{
return mFilter == inRHS.mFilter
&& mWrapFilter == inRHS.mWrapFilter
&& mBlur == inRHS.mBlur;
}
//////////////////////////////////////////////////////////////////////////////////////////
// Resizing a surface
//////////////////////////////////////////////////////////////////////////////////////////
// Structure used for zooming
struct Contrib
{
int mOffset; // Offset of this pixel (relative to start of scanline)
int mWeight; // Weight of this pixel in 0.12 fixed point format
};
static void sPrecalculateFilter(const ZoomSettings &inZoomSettings, int inOldLength, int inNewLength, int inOffsetFactor, Array<Array<Contrib>> &outContrib)
{
JPH_PROFILE("PrecalculateFilter");
// Get filter
const ImageFilter &filter = GetFilter(inZoomSettings.mFilter);
// Get scale
float scale = float(inNewLength) / inOldLength;
float fwidth, fscale;
if (scale < 1.0f)
{
// Minify, broaden filter
fwidth = filter.GetSupport() / scale;
fscale = scale;
}
else
{
// Enlarge, filter is always used as is
fwidth = filter.GetSupport();
fscale = 1.0f;
}
// Adjust filter for blur
fwidth *= inZoomSettings.mBlur;
fscale /= inZoomSettings.mBlur;
float min_fwidth = 1.0f;
if (fwidth < min_fwidth)
{
fwidth = min_fwidth;
fscale = filter.GetSupport() / min_fwidth;
}
// Make room for a whole scanline
outContrib.resize(inNewLength);
// Loop over the whole scanline
for (int i = 0; i < inNewLength; ++i)
{
// Compute center and left- and rightmost pixels affected
float center = float(i) / scale;
int left = int(floor(center - fwidth));
int right = int(ceil(center + fwidth));
// Reserve required elements
Array<Contrib> &a = outContrib[i];
a.reserve(right - left + 1);
// Total sum of all weights, for renormalization of the filter
int filter_sum = 0;
// Compute the contributions for each
for (int source = left; source <= right; ++source)
{
Contrib c;
// Initialize the offset
c.mOffset = source;
// Compute weight at this position in 0.12 fixed point
c.mWeight = int(4096.0f * filter.GetValue(fscale * (center - source)));
if (c.mWeight == 0) continue;
// Add weight to filter total
filter_sum += c.mWeight;
// Reflect the filter at the edges if the filter is not to be wrapped (clamp)
if (!inZoomSettings.mWrapFilter && (c.mOffset < 0 || c.mOffset >= inOldLength))
c.mOffset = -c.mOffset - 1;
// Wrap the offset so that it falls within the image
c.mOffset = (c.mOffset % inOldLength + inOldLength) % inOldLength;
// Check that the offset falls within the image
JPH_ASSERT(c.mOffset >= 0 && c.mOffset < inOldLength);
// Multiply the offset with the specified factor
c.mOffset *= inOffsetFactor;
// Add the filter element
a.push_back(c);
}
// Normalize the filter to 0.12 fixed point
if (filter_sum != 0)
for (uint n = 0; n < a.size(); ++n)
a[n].mWeight = (a[n].mWeight * 4096) / filter_sum;
}
}
static void sZoomHorizontal(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings)
{
JPH_PROFILE("ZoomHorizontal");
// Check zoom parameters
JPH_ASSERT(inSrc->GetHeight() == ioDst->GetHeight());
JPH_ASSERT(inSrc->GetFormat() == ioDst->GetFormat());
const int width = ioDst->GetWidth();
const int height = ioDst->GetHeight();
const int components = ioDst->GetNumberOfComponents();
const int delta_s = -components;
const int delta_d = ioDst->GetBytesPerPixel() - components;
// Pre-calculate filter contributions for a row
Array<Array<Contrib>> contrib;
sPrecalculateFilter(inZoomSettings, inSrc->GetWidth(), ioDst->GetWidth(), inSrc->GetBytesPerPixel(), contrib);
// Do the zoom
for (int y = 0; y < height; ++y)
{
const uint8 *s = inSrc->GetScanLine(y);
uint8 *d = ioDst->GetScanLine(y);
for (int x = 0; x < width; ++x)
{
const Array<Contrib> &line = contrib[x];
const size_t line_size_min_one = line.size() - 1;
int c = components;
do
{
int pixel = 0;
// Apply the filter for one color component
size_t j = line_size_min_one;
do
{
const Contrib &cmp = line[j];
pixel += cmp.mWeight * s[cmp.mOffset];
}
while (j--);
// Clamp the pixel value
if (pixel <= 0)
*d = 0;
else if (pixel >= (255 << 12))
*d = 255;
else
*d = uint8(pixel >> 12);
++s;
++d;
}
while (--c);
// Skip unused components if there are any
s += delta_s;
d += delta_d;
}
}
}
static void sZoomVertical(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings)
{
JPH_PROFILE("ZoomVertical");
// Check zoom parameters
JPH_ASSERT(inSrc->GetWidth() == ioDst->GetWidth());
JPH_ASSERT(inSrc->GetFormat() == ioDst->GetFormat());
const int width = ioDst->GetWidth();
const int height = ioDst->GetHeight();
const int components = ioDst->GetNumberOfComponents();
const int delta_s = inSrc->GetBytesPerPixel() - components;
const int delta_d = ioDst->GetBytesPerPixel() - components;
// Pre-calculate filter contributions for a row
Array<Array<Contrib>> contrib;
sPrecalculateFilter(inZoomSettings, inSrc->GetHeight(), ioDst->GetHeight(), inSrc->GetStride(), contrib);
// Do the zoom
for (int y = 0; y < height; ++y)
{
const uint8 *s = inSrc->GetScanLine(0);
uint8 *d = ioDst->GetScanLine(y);
const Array<Contrib> &line = contrib[y];
const size_t line_size_min_one = line.size() - 1;
for (int x = 0; x < width; ++x)
{
int c = components;
do
{
int pixel = 0;
// Apply the filter for one color component
size_t j = line_size_min_one;
do
{
const Contrib &cmp = line[j];
pixel += cmp.mWeight * s[cmp.mOffset];
}
while (j--);
// Clamp the pixel value
if (pixel <= 0)
*d = 0;
else if (pixel >= (255 << 12))
*d = 255;
else
*d = uint8(pixel >> 12);
++s;
++d;
}
while (--c);
// Skip unused components if there are any
s += delta_s;
d += delta_d;
}
}
}
bool ZoomImage(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings)
{
JPH_PROFILE("ZoomImage");
// Get filter
const ImageFilter &filter = GetFilter(inZoomSettings.mFilter);
// Determine the temporary format that will require the least amount of components to be zoomed and the least amount of bytes pushed around
ESurfaceFormat tmp_format;
ESurfaceFormat src_format = inSrc->GetClosest8BitFormat();
ESurfaceFormat dst_format = ioDst->GetClosest8BitFormat();
const FormatDescription &src_desc = GetFormatDescription(src_format);
const FormatDescription &dst_desc = GetFormatDescription(dst_format);
if (src_desc.GetNumberOfComponents() < dst_desc.GetNumberOfComponents())
tmp_format = src_format;
else if (src_desc.GetNumberOfComponents() > dst_desc.GetNumberOfComponents())
tmp_format = dst_format;
else if (src_desc.GetBytesPerPixel() < dst_desc.GetBytesPerPixel())
tmp_format = src_format;
else
tmp_format = dst_format;
// Create temporary source buffer if necessary
RefConst<Surface> src = inSrc;
if (inSrc->GetFormat() != tmp_format)
{
Ref<Surface> tmp = new SoftwareSurface(inSrc->GetWidth(), inSrc->GetHeight(), tmp_format);
if (!BlitSurface(inSrc, tmp))
return false;
src = tmp;
}
// Create temporary destination buffer if necessary
Ref<Surface> dst = ioDst;
if (ioDst->GetFormat() != tmp_format)
dst = new SoftwareSurface(ioDst->GetWidth(), ioDst->GetHeight(), tmp_format);
src->Lock(ESurfaceLockMode::Read);
dst->Lock(ESurfaceLockMode::Write);
if (src->GetWidth() == dst->GetWidth())
{
// Only vertical zoom required
sZoomVertical(src, dst, inZoomSettings);
}
else if (src->GetHeight() == dst->GetHeight())
{
// Only horizontal zoom required
sZoomHorizontal(src, dst, inZoomSettings);
}
else
{
// Determine most optimal order
float operations_vh = float(dst->GetWidth()) * (filter.GetSupport() * src->GetHeight() + filter.GetSupport() * dst->GetHeight());
float operations_hv = float(dst->GetHeight()) * (filter.GetSupport() * src->GetWidth() + filter.GetSupport() * dst->GetWidth());
if (operations_vh < operations_hv)
{
// Create temporary buffer to hold the vertical scale
Ref<Surface> tmp = new SoftwareSurface(src->GetWidth(), dst->GetHeight(), tmp_format);
tmp->Lock(ESurfaceLockMode::ReadWrite);
// First scale vertically then horizontally
sZoomVertical(src, tmp, inZoomSettings);
sZoomHorizontal(tmp, dst, inZoomSettings);
tmp->UnLock();
}
else
{
// Create temporary buffer to hold the horizontal scale
Ref<Surface> tmp = new SoftwareSurface(dst->GetWidth(), src->GetHeight(), tmp_format);
tmp->Lock(ESurfaceLockMode::ReadWrite);
// First scale horizontally then vertically
sZoomHorizontal(src, tmp, inZoomSettings);
sZoomVertical(tmp, dst, inZoomSettings);
tmp->UnLock();
}
}
src->UnLock();
dst->UnLock();
// Convert to destination if required
if (dst != ioDst)
if (!BlitSurface(dst, ioDst))
return false;
return true;
}

View File

@@ -0,0 +1,41 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class Surface;
/// Filter function used to rescale the image
enum EFilter
{
FilterBox,
FilterTriangle,
FilterBell,
FilterBSpline,
FilterLanczos3,
FilterMitchell,
};
/// Zoom settings for ZoomImage
class ZoomSettings
{
public:
/// Constructor
ZoomSettings();
/// Comparison operators
bool operator == (const ZoomSettings &inRHS) const;
/// Default settings
static const ZoomSettings sDefault;
EFilter mFilter; ///< Filter function for image scaling
bool mWrapFilter; ///< If true, the filter will be applied wrapping around the image, this provides better results for repeating textures
float mBlur; ///< If > 1 then the image will be blurred, if < 1 the image will be sharpened
};
/// Function to resize an image
bool ZoomImage(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings = ZoomSettings::sDefault);