Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,121 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageConvert.h>
#include <Processing/PixelFormatInfo.h>
#include <Converters/PixelOperation.h>
///////////////////////////////////////////////////////////////////////////////////
//functions for maintaining alpha coverage.
namespace ImageProcessing
{
void CImageObject::TransferAlphaCoverage(const TextureSettings* textureSetting, const IImageObjectPtr srcImg)
{
EPixelFormat srcFmt = srcImg->GetPixelFormat();
//both this image and src image need to be uncompressed
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_pixelFormat)
|| !CPixelFormats::GetInstance().IsPixelFormatUncompressed(srcFmt))
{
AZ_Assert(false, "Both source image and dest image need to be uncompressed");
return;
}
const float fAlphaRef = 0.5f; // Seems to give good overall results
const float fDesiredAlphaCoverage = srcImg->ComputeAlphaCoverage(0, fAlphaRef);
//create pixel operation function
IPixelOperationPtr pixelOp = CreatePixelOperation(m_pixelFormat);
//get count of bytes per pixel
AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(m_pixelFormat)->bitsPerBlock / 8;
for (uint32 mip = 0; mip < GetMipCount(); mip++)
{
const float fAlphaOffset = textureSetting->ComputeMIPAlphaOffset(mip);
const float fAlphaScale = ComputeAlphaCoverageScaleFactor(mip, fDesiredAlphaCoverage, fAlphaRef);
AZ::u8* pixelBuf = m_mips[mip]->m_pData;
const AZ::u32 pixelCount = GetPixelCount(mip);
for (AZ::u32 i = 0; i < pixelCount; ++i, pixelBuf += pixelBytes)
{
float r, g, b, a;
pixelOp->GetRGBA(pixelBuf, r, g, b, a);
a = AZ::GetMin(a * fAlphaScale + fAlphaOffset, 1.0f);
pixelOp->SetRGBA(pixelBuf, r, g, b, a);
}
}
}
float CImageObject::ComputeAlphaCoverageScaleFactor(AZ::u32 mip, float fDesiredCoverage, float fAlphaRef) const
{
float minAlphaRef = 0.0f;
float maxAlphaRef = 1.0f;
float midAlphaRef = 0.5f;
// Find best alpha test reference value using a binary search
for (int i = 0; i < 10; i++)
{
const float currentCoverage = ComputeAlphaCoverage(mip, midAlphaRef);
if (currentCoverage > fDesiredCoverage)
{
minAlphaRef = midAlphaRef;
}
else if (currentCoverage < fDesiredCoverage)
{
maxAlphaRef = midAlphaRef;
}
else
{
break;
}
midAlphaRef = (minAlphaRef + maxAlphaRef) * 0.5f;
}
return fAlphaRef / midAlphaRef;
}
float CImageObject::ComputeAlphaCoverage(AZ::u32 mip, float fAlphaRef) const
{
//This function only works with uncompressed image
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_pixelFormat))
{
AZ_Assert(false, "This image need to be uncompressed");
return 0;
}
uint32 coverage = 0;
//create pixel operation function
IPixelOperationPtr pixelOp = CreatePixelOperation(m_pixelFormat);
//get count of bytes per pixel
AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(m_pixelFormat)->bitsPerBlock / 8;
AZ::u8* pixelBuf = m_mips[mip]->m_pData;
const AZ::u32 pixelCount = GetPixelCount(mip);
for (AZ::u32 i = 0; i < pixelCount; ++i, pixelBuf += pixelBytes)
{
float r, g, b, a;
pixelOp->GetRGBA(pixelBuf, r, g, b, a);
coverage += a > fAlphaRef;
}
return (float)coverage / (float)(pixelCount);
}
} // namespace ImageProcessing
@@ -0,0 +1,315 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageToProcess.h>
namespace ImageProcessing
{
const int COLORCHART_IMAGE_WIDTH = 78;
const int COLORCHART_IMAGE_HEIGHT = 66;
// color chart in cry engine is a special image data, with size 78x66, you may see in game screenshot which is defined by a rectangle
// area with a yellow-black dash line boarder
// Create color chart function is to read that block of image data and convert it to a color table then save it to another image
// with size 256x16.
class C3dLutColorChart
{
public:
C3dLutColorChart() {}
~C3dLutColorChart() {};
//generate default color chart data
void GenerateDefault();
//generate color chart data from input image
bool GenerateFromInput(IImageObjectPtr image);
//ouput the color chart data to an image object
IImageObjectPtr GenerateChartImage();
protected:
//extract color chart data from specified location in an image
void ExtractFromImageAt(IImageObjectPtr pImg, AZ::u32 x, AZ::u32 y);
//find color chart location in an image
static bool FindColorChart(const IImageObjectPtr pImg, AZ::u32& outLocX, AZ::u32& outLocY);
//if there is a color chart at specified location
static bool IsColorChartAt(AZ::u32 x, AZ::u32 y, void* pData, AZ::u32 pitch);
private:
enum EPrimaryShades
{
ePS_Red = 16,
ePS_Green = 16,
ePS_Blue = 16,
ePS_NumColors = ePS_Red * ePS_Green * ePS_Blue
};
struct SColor
{
unsigned char r, g, b, _padding;
};
typedef AZStd::vector<SColor> ColorMapping;
ColorMapping m_mapping;
};
void C3dLutColorChart::GenerateDefault()
{
m_mapping.reserve(ePS_NumColors);
for (int b = 0; b < ePS_Blue; ++b)
{
for (int g = 0; g < ePS_Green; ++g)
{
for (int r = 0; r < ePS_Red; ++r)
{
SColor col;
col.r = 255 * r / (ePS_Red);
col.g = 255 * g / (ePS_Green);
col.b = 255 * b / (ePS_Blue);
int l = 255 - (col.r * 3 + col.g * 6 + col.b) / 10;
col.r = col.g = col.b = (unsigned char)l;
m_mapping.push_back(col);
}
}
}
}
//find color chart location in a image
bool C3dLutColorChart::FindColorChart(const IImageObjectPtr pImg, AZ::u32& outLocX, AZ::u32& outLocY)
{
const AZ::u32 width = pImg->GetWidth(0);
const AZ::u32 height = pImg->GetHeight(0);
//the origin image is too small to have a color chart
if (width < COLORCHART_IMAGE_WIDTH || height < COLORCHART_IMAGE_HEIGHT)
{
return false;
}
AZ::u8* pData;
AZ::u32 pitch;
pImg->GetImagePointer(0, pData, pitch);
//check all the posible start location on whether there might be a color chart
for (AZ::u32 y = 0; y <= height - COLORCHART_IMAGE_HEIGHT; ++y)
{
for (AZ::u32 x = 0; x <= width - COLORCHART_IMAGE_WIDTH; ++x)
{
if (IsColorChartAt(x, y, pData, pitch))
{
outLocX = x;
outLocY = y;
return true;
}
}
}
return false;
}
bool C3dLutColorChart::GenerateFromInput(IImageObjectPtr image)
{
AZ::u32 outLocX, outLocY;
if (FindColorChart(image, outLocX, outLocY))
{
ExtractFromImageAt(image, outLocX, outLocY);
return true;
}
return false;
}
IImageObjectPtr C3dLutColorChart::GenerateChartImage()
{
const AZ::u32 mipCount = 1;
IImageObjectPtr image( IImageObject::CreateImage(ePS_Red * ePS_Blue, ePS_Green, 1, ePixelFormat_R8G8B8A8));
{
AZ::u8* pData;
AZ::u32 pitch;
image->GetImagePointer(0, pData, pitch);
size_t nSlicePitch = (pitch / ePS_Blue);
AZ::u32 src = 0;
AZ::u32 dst = 0;
for (int b = 0; b < ePS_Blue; ++b)
{
for (int g = 0; g < ePS_Green; ++g)
{
AZ::u8* p = pData + g * pitch + b * nSlicePitch;
for (int r = 0; r < ePS_Red; ++r)
{
const SColor& c = m_mapping[src];
p[0] = c.r;
p[1] = c.g;
p[2] = c.b;
p[3] = 255;
++src;
p += 4;
}
}
}
}
return image;
}
void C3dLutColorChart::ExtractFromImageAt(IImageObjectPtr image, AZ::u32 x, AZ::u32 y)
{
int ox = x + 1;
int oy = y + 1;
AZ::u8* pData;
AZ::u32 pitch;
image->GetImagePointer(0, pData, pitch);
m_mapping.reserve(ePS_NumColors);
for (int b = 0; b < ePS_Blue; ++b)
{
int px = ox + ePS_Red * (b % 4);
int py = oy + ePS_Green * (b / 4);
for (int g = 0; g < ePS_Green; ++g)
{
for (int r = 0; r < ePS_Red; ++r)
{
AZ::u8* p = pData + pitch * (py + g) + (px + r) * 4;
SColor col;
col.r = p[0];
col.g = p[1];
col.b = p[2];
m_mapping.push_back(col);
}
}
}
}
//check if image data at location x and y could be a color chart
//based on if the boarder is dash lines with two pixel each segement
//the idea and implementation are both coming from CryEngine.
bool C3dLutColorChart::IsColorChartAt(AZ::u32 x, AZ::u32 y, void* pData, AZ::u32 pitch)
{
struct Color
{
private:
int c[3];
public:
Color(AZ::u32 x, AZ::u32 y, void* pPixels, AZ::u32 pitch)
{
const uint8* p = (const uint8*)pPixels + pitch * y + x * 4;
c[0] = p[0];
c[1] = p[1];
c[2] = p[2];
}
bool isSimilar(const Color& a, int maxDiff) const
{
return
abs(a.c[0] - c[0]) <= maxDiff &&
abs(a.c[1] - c[1]) <= maxDiff &&
abs(a.c[2] - c[2]) <= maxDiff;
}
};
const Color colorRef[2] =
{
Color(x, y, pData, pitch),
Color(x + 2, y, pData, pitch)
};
// We require two colors of the border to be at least a bit different
if (colorRef[0].isSimilar(colorRef[1], 15))
{
return false;
}
static const int kMaxDiff = 3;
int refIdx = 0;
//rectangle's top
for (int i = 0; i < COLORCHART_IMAGE_WIDTH; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x + i, y, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x + i + 1, y, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
refIdx = 0;
//left
for (int i = 0; i < COLORCHART_IMAGE_HEIGHT; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x, y + i, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x, y + i + 1, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
refIdx = 0;
//right
for (int i = 0; i < COLORCHART_IMAGE_HEIGHT; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x + COLORCHART_IMAGE_WIDTH - 1, y + i, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x + COLORCHART_IMAGE_WIDTH - 1, y + i + 1, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
refIdx = 0;
//bottom
for (int i = 0; i < COLORCHART_IMAGE_WIDTH; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x + i, y + COLORCHART_IMAGE_HEIGHT - 1, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x + i + 1, y + COLORCHART_IMAGE_HEIGHT - 1, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
return true;
}
void ImageToProcess::CreateColorChart()
{
C3dLutColorChart colorChart;
//get color chart data from source image.
if (!colorChart.GenerateFromInput(m_img))
{
//if load from image failed then generate default color data
colorChart.GenerateDefault();
}
//save color chart data to an image and save as current
m_img = colorChart.GenerateChartImage();
}
}
@@ -0,0 +1,170 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageToProcess.h>
#include <Processing/PixelFormatInfo.h>
#include <Compressors/Compressor.h>
#include <Converters/PixelOperation.h>
///////////////////////////////////////////////////////////////////////////////////
//functions for maintaining alpha coverage.
namespace ImageProcessing
{
void ImageToProcess::ConvertFormat(EPixelFormat fmtDst)
{
//pixel format before convertion
EPixelFormat fmtSrc = Get()->GetPixelFormat();
//return directly if the image already has the desired pixel format
if (fmtDst == fmtSrc)
{
return;
}
uint32 dwWidth, dwHeight, dwMips;
dwWidth = Get()->GetWidth(0);
dwHeight = Get()->GetHeight(0);
dwMips = Get()->GetMipCount();
//if the output image size doesn't work the desired pixel format. set to fallback format
const PixelFormatInfo* dstFmtInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtDst);
if (!CPixelFormats::GetInstance().IsImageSizeValid(fmtDst, dwWidth, dwHeight, true))
{
AZ_Warning("Image Processing", false, "Output pixel format %d doesn't work with output image size %d x %d",
fmtDst, dwWidth, dwHeight);
//fall back to safe texture format
if (dstFmtInfo->nChannels == 1)
{
fmtDst = dstFmtInfo->bHasAlpha ? ePixelFormat_A8 : ePixelFormat_R8;
}
else if (dstFmtInfo->nChannels == 2)
{
fmtDst = ePixelFormat_R8G8;
}
else
{
fmtDst = dstFmtInfo->bHasAlpha ? ePixelFormat_R8G8B8A8 : ePixelFormat_R8G8B8X8;
}
}
//convert src image to uncompressed formats if it's compressed format
bool isSrcUncompressed = CPixelFormats::GetInstance().IsPixelFormatUncompressed(fmtSrc);
bool isDstUncompressed = CPixelFormats::GetInstance().IsPixelFormatUncompressed(fmtDst);
if (isSrcUncompressed && isDstUncompressed)
{//both are uncompressed
ConvertFormatUncompressed(fmtDst);
}
else if (!isSrcUncompressed && !isDstUncompressed)
{ //both are compressed
AZ_Assert(false, "unusual user case. but we can still handle it");
}
else
{ //one fmt is compressed format
//use the compressed format to find right compressor
EPixelFormat compressedFmt = isSrcUncompressed ? fmtDst : fmtSrc;
EPixelFormat uncompressedFmt = isSrcUncompressed ? fmtSrc : fmtDst;
ICompressorPtr compressor = ICompressor::FindCompressor(compressedFmt, isSrcUncompressed);
if (compressor == nullptr)
{
//no avaible compressor for compressed format
AZ_Warning("Image Processing", false, "No avaliable compressor for pixel format %d", compressedFmt);
return;
}
//check if the uncompressed fmt also supported by the compressor
EPixelFormat desiredUncompressedFmt = compressor->GetSuggestedUncompressedFormat(compressedFmt, uncompressedFmt);
if (desiredUncompressedFmt != uncompressedFmt)
{
//we need to do intermedia convertion to convert to the temperory format
ConvertFormat(desiredUncompressedFmt);
ConvertFormat(fmtDst);
}
else
{
IImageObjectPtr dstImage = nullptr;
if (isSrcUncompressed)
{
dstImage = compressor->CompressImage(Get(), fmtDst, &m_compressOption);
}
else
{
dstImage = compressor->DecompressImage(Get(), fmtDst);
}
Set(dstImage);
}
if (Get() == nullptr)
{
AZ_Error("Image Processing", false, "The selected compressor failed to compress this image");
}
}
}
void ImageToProcess::ConvertFormatUncompressed(EPixelFormat fmtTo)
{
IImageObjectPtr srcImage = m_img;
EPixelFormat srcFmt = srcImage->GetPixelFormat();
EPixelFormat dstFmt = fmtTo;
if (!(CPixelFormats::GetInstance().IsPixelFormatUncompressed(srcFmt)
&& CPixelFormats::GetInstance().IsPixelFormatUncompressed(dstFmt)))
{
AZ_Assert(false, "both source and dest images' pixel format need to be uncompressed");
return;
}
IImageObjectPtr dstImage(m_img->AllocateImage(fmtTo));
AZ_Assert(srcImage->GetPixelCount(0) == dstImage->GetPixelCount(0), "dest image has different size than source image");
//create pixel operation function for src and dst images
IPixelOperationPtr srcOp = CreatePixelOperation(srcFmt);
IPixelOperationPtr dstOp = CreatePixelOperation(dstFmt);
//get count of bytes per pixel for both src and dst images
uint32 srcPixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(srcFmt)->bitsPerBlock / 8;
uint32 dstPixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(dstFmt)->bitsPerBlock / 8;
const uint32 dwMips = dstImage->GetMipCount();
float r, g, b, a;
for (uint32 dwMip = 0; dwMip < dwMips; ++dwMip)
{
uint8* srcPixelBuf;
uint32 srcPitch;
srcImage->GetImagePointer(dwMip, srcPixelBuf, srcPitch);
uint8* dstPixelBuf;
uint32 dstPitch;
dstImage->GetImagePointer(dwMip, dstPixelBuf, dstPitch);
const uint32 pixelCount = srcImage->GetPixelCount(dwMip);
for (uint32 i = 0; i < pixelCount; ++i, srcPixelBuf += srcPixelBytes, dstPixelBuf += dstPixelBytes)
{
srcOp->GetRGBA(srcPixelBuf, r, g, b, a);
dstOp->SetRGBA(dstPixelBuf, r, g, b, a);
}
}
m_img = dstImage;
}
} // namespace ImageProcessing
@@ -0,0 +1,620 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageToProcess.h>
#include <Processing/PixelFormatInfo.h>
#include <Processing/ImageConvert.h>
#include <Processing/ImageFlags.h>
#include <Compressors/Compressor.h>
#include <Converters/PixelOperation.h>
#include <Converters/Cubemap.h>
#include <CubeMapGen/CCubeMapProcessor.h>
namespace ImageProcessing
{
CubemapLayoutInfo CubemapLayout::s_layoutList[CubemapLayoutTypeCount];
template <class TInteger>
inline bool IsPowerOfTwo(TInteger x)
{
return (x & (x - 1)) == 0;
}
CubemapLayoutInfo::CubemapLayoutInfo()
: m_type(CubemapLayoutNone)
, m_rows(0)
, m_columns(0)
{
}
void CubemapLayoutInfo::SetFaceInfo(CubemapFace face, AZ::u8 row, AZ::u8 col, CubemapFaceDirection dir)
{
m_faceInfos[face].row = row;
m_faceInfos[face].column = col;
m_faceInfos[face].direction = dir;
}
void CubemapLayout::InitCubemapLayoutInfos()
{
//CubemapLayoutHorizontal
//left , right, front, back, top, bottom;
//NOTE: this layout is widely used in game projects by Jan 2018 since other layouts weren't supported correctly
//but the faces in one has unusual directions compare to other format.
//The direction matters when using it as input for Cubemap generation filter.
//Left: rotated left 90 degree. Right: rotated right 90 degree
//Front: rotated 180 degree. Back: no rotation
//Top: rotate 180 degree. Bottom: no rotation
CubemapLayoutInfo *info = &s_layoutList[CubemapLayoutHorizontal];
info->m_rows = 1;
info->m_columns = 6;
info->m_type = CubemapLayoutHorizontal;
info->SetFaceInfo(FaceLeft, 0, 0, CubemapFaceDirection::DirRotateLeft90);
info->SetFaceInfo(FaceRight, 0, 1, CubemapFaceDirection::DirRotateRight90);
info->SetFaceInfo(FaceFront, 0, 2, CubemapFaceDirection::DirRotate180);
info->SetFaceInfo(FaceBack, 0, 3, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceTop, 0, 4, CubemapFaceDirection::DirRotate180);
info->SetFaceInfo(FaceBottom, 0, 5, CubemapFaceDirection::DirNoRotation);
//CubemapLayoutHorizontalCross
// top
// left front right back
// bottom
info = &s_layoutList[CubemapLayoutHorizontalCross];
info->m_rows = 3;
info->m_columns = 4;
info->m_type = CubemapLayoutHorizontalCross;
info->SetFaceInfo(FaceLeft, 1, 0, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceRight, 1, 2, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceFront, 1, 1, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceBack, 1, 3, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceTop, 0, 1, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceBottom, 2, 1, CubemapFaceDirection::DirNoRotation);
//CubemapLayoutVerticalCross
// top
// left front right
// bottom
// back
info = &s_layoutList[CubemapLayoutVerticalCross];
info->m_rows = 4;
info->m_columns = 3;
info->m_type = CubemapLayoutVerticalCross;
info->SetFaceInfo(FaceLeft, 1, 0, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceRight, 1, 2, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceFront, 1, 1, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceBack, 3, 1, CubemapFaceDirection::DirRotate180);
info->SetFaceInfo(FaceTop, 0, 1, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceBottom, 2, 1, CubemapFaceDirection::DirNoRotation);
//CubemapLayoutVertical
// left
// right
// front
// back
// top
// bottom
info = &s_layoutList[CubemapLayoutVertical];
info->m_rows = 6;
info->m_columns = 1;
info->m_type = CubemapLayoutVertical;
info->SetFaceInfo(FaceLeft, 0, 0, CubemapFaceDirection::DirRotateLeft90);
info->SetFaceInfo(FaceRight, 1, 0, CubemapFaceDirection::DirRotateRight90);
info->SetFaceInfo(FaceFront, 2, 0, CubemapFaceDirection::DirRotate180);
info->SetFaceInfo(FaceBack, 3, 0, CubemapFaceDirection::DirNoRotation);
info->SetFaceInfo(FaceTop, 4, 0, CubemapFaceDirection::DirRotate180);
info->SetFaceInfo(FaceBottom, 5, 0, CubemapFaceDirection::DirNoRotation);
//make sure all types were initialized
for (int i = 0; i < CubemapLayoutTypeCount; i++)
{
AZ_Assert(s_layoutList[i].m_type == i, "layout %d is not initialized", i);
}
}
const float* GetTransformMatrix(CubemapFaceDirection dir, bool isInvert)
{
switch (dir)
{
case CubemapFaceDirection::DirNoRotation:
{
static const float mat[] = { 1, 0, 0, 1 };
return mat;
}
case CubemapFaceDirection::DirRotateLeft90:
{
//thelta = 90 degree
//{cos, -sin, sin, cos}
if (isInvert)
{
return GetTransformMatrix(CubemapFaceDirection::DirRotateRight90, false);
}
static const float mat[] = { 0, -1, 1, 0 };
return mat;
}
case CubemapFaceDirection::DirRotateRight90:
{
//thelta = -90 degree
if (isInvert)
{
return GetTransformMatrix(CubemapFaceDirection::DirRotateLeft90, false);
}
static const float mat[] = { 0, 1, -1, 0 };
return mat;
}
case CubemapFaceDirection::DirRotate180:
{
//thelta = 180 degree
static const float mat[] = { -1, 0, 0, -1 };
return mat;
}
case CubemapFaceDirection::DirMirrorHorizontal:
{
static const float mat[] = { 1, 0, 0, -1 };
return mat;
}
default:
{
AZ_Assert(false, "unimplemented direction matrix");
static const float mat[] = { 1, 0, 0, 1 };
return mat;
}
}
}
void TransformImage(CubemapFaceDirection srcDir, CubemapFaceDirection dstDir, const AZ::u8* srcImageBuf,
AZ::u8* dstImageBuf, AZ::u8 bytePerPixel, AZ::u32 rectSize)
{
//get final matrix to transform dst back to src
const float* m1 = GetTransformMatrix(dstDir, true);
const float* m2 = GetTransformMatrix(srcDir, false);
float mtx[4];
mtx[0] = m1[0] * m2[0] + m1[1] * m2[2];
mtx[1] = m1[0] * m2[1] + m1[1] * m2[3];
mtx[2] = m1[2] * m2[0] + m1[3] * m2[2];
mtx[3] = m1[2] * m2[1] + m1[3] * m2[3];
const float* noRotate = GetTransformMatrix(CubemapFaceDirection::DirNoRotation, false);
if (memcmp(noRotate, mtx, 4 * sizeof(float)) == 0)
{
memcpy(dstImageBuf, srcImageBuf, rectSize*rectSize*bytePerPixel);
return;
}
//for each pixel in dst image, find it's location in src and copy the data from there
float halfSize = rectSize / 2;
for (AZ::u32 row = 0; row < rectSize; row++)
{
for (AZ::u32 col = 0; col < rectSize; col++)
{
//coordinate in image center as origin and right as positive X, up as positive Y
float dstX = col + 0.5f - halfSize;
float dstY = halfSize - row - 0.5f;
float srcX = dstX * mtx[0] + dstY * mtx[1];
float srcY = dstX * mtx[2] + dstY * mtx[3];
AZ::u32 srcCol = srcX + halfSize;
AZ::u32 srcRow = halfSize - srcY;
memcpy(&dstImageBuf[(row*rectSize + col)*bytePerPixel],
&srcImageBuf[(srcRow*rectSize + srcCol)*bytePerPixel], bytePerPixel);
}
}
}
CubemapLayout::CubemapLayout()
: m_info(nullptr)
, m_image(nullptr)
, m_faceSize(256)
{
}
CubemapLayout* CubemapLayout::CreateCubemapLayout(IImageObjectPtr image)
{
//only support uncompressed format.
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(image->GetPixelFormat()))
{
AZ_Assert(false, "CubemapLayout only support uncompressed image");
return nullptr;
}
CubemapLayout* layout = nullptr;
CubemapLayoutInfo* info = GetCubemapLayoutInfo(image);
if (info)
{
layout = new CubemapLayout();
layout->m_info = GetCubemapLayoutInfo(image);
layout->m_image = image;
layout->m_faceSize = image->GetWidth(0)/layout->m_info->m_columns;
}
return layout;
}
CubemapLayoutInfo* CubemapLayout::GetCubemapLayoutInfo(CubemapLayoutType type)
{
if (type == CubemapLayoutNone)
{
return nullptr;
}
//if it's never initialized
if (s_layoutList[0].m_type == CubemapLayoutNone)
{
InitCubemapLayoutInfos();
}
return &s_layoutList[type];
}
CubemapLayoutInfo* CubemapLayout::GetCubemapLayoutInfo(IImageObjectPtr image)
{
//if it's never initialized
if (s_layoutList[0].m_type == CubemapLayoutNone)
{
InitCubemapLayoutInfos();
}
if (image == nullptr)
{
return nullptr;
}
uint32 width, height;
width = image->GetWidth(0);
height = image->GetHeight(0);
CubemapLayoutInfo* info = nullptr;
for (int i = 0; i < CubemapLayoutTypeCount; i++)
{
if (width * s_layoutList[i].m_rows == height*s_layoutList[i].m_columns)
{
info = &s_layoutList[i];
//we require the face size need to be power of two
if (IsPowerOfTwo(width / info->m_columns))
{
return info;
}
else
{
return nullptr;
}
}
}
return nullptr;
}
//public functions to get faces information for associated image
AZ::u32 CubemapLayout::GetFaceSize()
{
return m_faceSize;
}
CubemapLayoutInfo* CubemapLayout::GetLayoutInfo()
{
return m_info;
}
CubemapFaceDirection CubemapLayout::GetFaceDirection(CubemapFace face)
{
return m_info->m_faceInfos[face].direction;
}
void CubemapLayout::GetFaceData(CubemapFace face, void* outBuffer, AZ::u32& outSize)
{
//only valid for uncompressed
AZ::u32 sizePerPixel = CPixelFormats::GetInstance().GetPixelFormatInfo(m_image->GetPixelFormat())->bitsPerBlock / 8;
AZ::u8* imageBuf;
AZ::u32 dwPitch;
m_image->GetImagePointer(0, imageBuf, dwPitch);
AZ::u8* dstBuf = (AZ::u8*)outBuffer;
AZ::u32 startX = m_info->m_faceInfos[face].column * m_faceSize;
AZ::u32 startY = m_info->m_faceInfos[face].row * m_faceSize;
//face size is same as rows for uncompressed format
for (AZ::u32 y = 0; y < m_faceSize; y++)
{
AZ::u32 scanlineSize = m_faceSize*sizePerPixel;
AZ::u8* srcBuf = &imageBuf[(startY + y) * dwPitch + startX*sizePerPixel];
memcpy(dstBuf, srcBuf, scanlineSize);
dstBuf += scanlineSize;
}
outSize = m_faceSize*m_faceSize*sizePerPixel;
}
void CubemapLayout::SetFaceData(CubemapFace face, void* dataBuffer, [[maybe_unused]] AZ::u32 dataSize)
{
//only valid for uncompressed
AZ::u32 sizePerPixel = CPixelFormats::GetInstance().GetPixelFormatInfo(m_image->GetPixelFormat())->bitsPerBlock / 8;
AZ::u8* imageBuf;
AZ::u32 dwPitch;
m_image->GetImagePointer(0, imageBuf, dwPitch);
AZ::u8* srcBuf = (AZ::u8*)dataBuffer;
AZ::u32 startX = m_info->m_faceInfos[face].column * m_faceSize;
AZ::u32 startY = m_info->m_faceInfos[face].row * m_faceSize;
//face size is same as rows for uncompressed format
for (AZ::u32 y = 0; y < m_faceSize; y++)
{
AZ::u32 scanlineSize = m_faceSize*sizePerPixel;
AZ::u8* dstBuf = &imageBuf[(startY + y) * dwPitch + startX*sizePerPixel];
memcpy(dstBuf, srcBuf, scanlineSize);
srcBuf += scanlineSize;
}
}
void* CubemapLayout::GetFaceMemBuffer(AZ::u32 mip, CubemapFace face, AZ::u32& outPitch)
{
if (CubemapLayoutVertical != m_info->m_type)
{
AZ_Assert(false, "this should only be used for CubemapLayoutVertical which has continous memory for each face");
return nullptr;
}
AZ::u32 faceSize = m_faceSize >> mip;
AZ::u8* imageBuf;
m_image->GetImagePointer(mip, imageBuf, outPitch);
AZ::u32 startY = m_info->m_faceInfos[face].row * faceSize;
//use startY is same as rows from m_image since the pixel format is uncompressed
return &imageBuf[startY * outPitch];
}
void CubemapLayout::SetToFaceMemBuffer(AZ::u32 mip, CubemapFace face, void* dataBuffer)
{
if (CubemapLayoutVertical != m_info->m_type)
{
AZ_Assert(false, "this should only be used for CubemapLayoutVertical which has continuous memory for each face");
return;
}
AZ::u32 faceSize = m_faceSize >> mip;
AZ::u32 pitch;
AZ::u8* imageBuf;
m_image->GetImagePointer(mip, imageBuf, pitch);
AZ::u32 startY = m_info->m_faceInfos[face].row * faceSize;
//use startY is same as rows from m_image since the pixel format is uncompressed
memcpy(&imageBuf[startY * pitch], dataBuffer, faceSize*pitch);
}
void CubemapLayout::GetRectForFace(AZ::u32 mip, CubemapFace face, QRect& outRect)
{
AZ::u32 faceSize = m_faceSize >> mip;
AZ::u32 startY = m_info->m_faceInfos[face].row * faceSize;
AZ::u32 startX = m_info->m_faceInfos[face].column * faceSize;
outRect.setRect(startX, startY, faceSize, faceSize);
}
bool ImageToProcess::ConvertCubemapLayout(CubemapLayoutType dstLayoutType)
{
const EPixelFormat srcPixelFormat = m_img->GetPixelFormat();
//it need to be uncompressed format
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(srcPixelFormat))
{
AZ_Assert(false, "Please convert the image to uncompressed pixel format before calling ConvertCubemapLayout");
return false;
}
//check if it's valid cubemap size
CubemapLayoutInfo* layoutInfo = CubemapLayout::GetCubemapLayoutInfo(m_img);
if (layoutInfo == nullptr)
{
AZ_Error("Image Processing", false, "The original image doesn't have a valid size (layout) as cubemap");
return false;
}
//if the source is same as output layout, return directly
if (layoutInfo->m_type == dstLayoutType)
{
return true;
}
CubemapLayoutInfo* dstLayoutInfo = CubemapLayout::GetCubemapLayoutInfo(dstLayoutType);
//create cubemap layout for source image for later operation.
CubemapLayout *srcCubemap = CubemapLayout::CreateCubemapLayout(m_img);
AZ::u32 faceSize = srcCubemap->GetFaceSize();
//create new image with same pixel format and copy prperties from source image
IImageObjectPtr newImage(IImageObject::CreateImage(faceSize * dstLayoutInfo->m_columns,
faceSize*dstLayoutInfo->m_rows, 1, srcPixelFormat));
CubemapLayout *dstCubemap = CubemapLayout::CreateCubemapLayout(newImage);
newImage->CopyPropertiesFrom(newImage);
//copy data from src cube to dst cube for each face
//temp buf for copy over data
AZ::u32 sizePerPixel = CPixelFormats::GetInstance().GetPixelFormatInfo(srcPixelFormat)->bitsPerBlock/8; //only valid for uncompressed
AZ::u8 *buf = new AZ::u8[faceSize*faceSize*sizePerPixel];
AZ::u8 *tempBuf = new AZ::u8[faceSize*faceSize*sizePerPixel];
for (AZ::u32 faceIdx = 0; faceIdx < FaceCount; faceIdx++)
{
AZ::u32 outSize = 0;
CubemapFace face = (CubemapFace)faceIdx;
srcCubemap->GetFaceData(face, buf, outSize);
CubemapFaceDirection srcDir = srcCubemap->GetFaceDirection(face);
CubemapFaceDirection dstDir = dstCubemap->GetFaceDirection(face);
if (srcDir == dstDir)
{
dstCubemap->SetFaceData(face, buf, outSize);
}
else
{
//transform the image
TransformImage(srcDir, dstDir, buf, tempBuf, sizePerPixel, faceSize);
dstCubemap->SetFaceData(face, tempBuf, outSize);
}
}
//clean up
delete[] buf;
delete[] tempBuf;
delete srcCubemap;
delete dstCubemap;
newImage->AddImageFlags(EIF_Cubemap);
m_img = newImage;
return true;
}
bool ImageConvertProcess::FillCubemapMipmaps()
{
//this function only works with pixel format rgba32f
const EPixelFormat srcPixelFormat = m_image->Get()->GetPixelFormat();
if (srcPixelFormat != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "%s only works with pixel format rgba32f", __FUNCTION__);
return false;
}
//only if the src image has one mip
if (m_image->Get()->GetMipCount() != 1)
{
AZ_Assert(false, "%s called for a mipmapped image. ", __FUNCTION__);
return false;
}
CubemapLayout *srcCubemap = CubemapLayout::CreateCubemapLayout(m_image->Get());
uint32 outWidth;
uint32 outHeight;
uint32 outReduce = 0;
AZ::u32 srcFaceSize = srcCubemap->GetFaceSize();
//get output face size
GetOutputExtent(srcFaceSize, srcFaceSize, outWidth, outHeight, outReduce, &m_textureSetting, &m_presetSetting);
AZ_Assert(outWidth == outHeight, "something wrong with GetOutputExtent function");
//get final cubemap image size
outWidth *= srcCubemap->GetLayoutInfo()->m_columns;
outHeight *= srcCubemap->GetLayoutInfo()->m_rows;
//max mipmap count
uint32 maxMipCount;
if (m_presetSetting.m_mipmapSetting == nullptr || !m_textureSetting.m_enableMipmap)
{
maxMipCount = 1;
}
else
{
//calculate based on face size, and use final export format which may save some low level mip calculation
maxMipCount = CPixelFormats::GetInstance().ComputeMaxMipCount(m_presetSetting.m_pixelFormat, srcFaceSize, srcFaceSize);
//the FilterImage function won't do well with rect size 1. avoiding cubemap with face size 1
if (srcFaceSize >> maxMipCount == 1 && maxMipCount > 1)
{
maxMipCount -= 1;
}
}
//create new new output image with proper face
IImageObjectPtr outImage(IImageObject::CreateImage(outWidth, outHeight, maxMipCount, srcPixelFormat));
outImage->CopyPropertiesFrom(m_image->Get());
CubemapLayout *dstCubemap = CubemapLayout::CreateCubemapLayout(outImage);
AZ::u32 outFaceSize = dstCubemap->GetFaceSize();
AZ::u32 dstMipCount = outImage->GetMipCount();
//filter the image for top mip first
for (int iSide = 0; iSide < 6; ++iSide)
{
QRect srcRect;
QRect dstRect;
srcRect.setLeft(0);
srcRect.setRight(srcFaceSize);
srcRect.setTop(iSide * srcFaceSize);
srcRect.setBottom((iSide + 1) * srcFaceSize);
dstRect.setLeft(0);
dstRect.setRight(outFaceSize);
dstRect.setTop(iSide * outFaceSize);
dstRect.setBottom((iSide + 1) * outFaceSize);
FilterImage(m_textureSetting.m_mipGenType, m_textureSetting.m_mipGenEval, 0, 0, m_image->Get(), 0,
outImage, 0, &srcRect, &dstRect);
}
CCubeMapProcessor atiCubemanGen;
//ATI's cubemap generator to filter the image edges to avoid seam problem
// https://gpuopen.com/archive/gamescgi/cubemapgen/
//the thread support was done with windows thread function so it's removed for multi-dev platform support
atiCubemanGen.m_NumFilterThreads = 0;
// input and output cubemap set to have save dimensions,
atiCubemanGen.Init(outFaceSize, outFaceSize, dstMipCount, 4);
// Load the 6 faces of the input cubemap and copy them into the cubemap processor
void* pMem;
uint32 nPitch;
for (int iFace = 0; iFace < 6; ++iFace)
{
pMem = dstCubemap->GetFaceMemBuffer(0, (CubemapFace)iFace, nPitch);
atiCubemanGen.SetInputFaceData(
iFace, // FaceIdx,
CP_VAL_FLOAT32, // SrcType,
4, // SrcNumChannels,
nPitch, // SrcPitch,
pMem, // SrcDataPtr,
1000000.0f, // MaxClamp,
1.0f, // Degamma,
1.0f); // Scale
}
//Filter cubemap
atiCubemanGen.InitiateFiltering(
m_presetSetting.m_cubemapSetting->m_angle, //BaseFilterAngle,
m_presetSetting.m_cubemapSetting->m_mipAngle, //InitialMipAngle,
m_presetSetting.m_cubemapSetting->m_mipSlope, //MipAnglePerLevelScale,
(int)m_presetSetting.m_cubemapSetting->m_filter, //FilterType, CP_FILTER_TYPE_COSINE for diffuse cube
m_presetSetting.m_cubemapSetting->m_edgeFixup > 0? CP_FIXUP_PULL_LINEAR : CP_FIXUP_NONE, //FixupType, CP_FIXUP_PULL_LINEAR if FixupWidth> 0
m_presetSetting.m_cubemapSetting->m_edgeFixup, //FixupWidth,
true, //bUseSolidAngle,
16, //GlossScale,
0, //GlossBias
128); //SampleCountGGX
// Download data into it
for (int iFace = 0; iFace < 6; ++iFace)
{
for (unsigned int dstMip = 0; dstMip < dstMipCount; ++dstMip)
{
pMem = dstCubemap->GetFaceMemBuffer(dstMip, (CubemapFace)iFace, nPitch);
atiCubemanGen.GetOutputFaceData(iFace, dstMip, CP_VAL_FLOAT32, 4, nPitch, pMem, 1.0f, 1.0f);
}
}
delete srcCubemap;
delete dstCubemap;
//set back to image
m_image->Set(outImage);
return true;
}
} // namespace ImageProcessing
@@ -0,0 +1,118 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Processing/ImageToProcess.h>
namespace ImageProcessing
{
// note: lumberyard is right hand Z up coordinate
// please don't change the order of the enum since we are using it to match the face id defined in AMD's CubemapGen
// and they are using left hand Y up coordinate
enum CubemapFace
{
FaceLeft = 0,
FaceRight,
FaceFront,
FaceBack,
FaceTop,
FaceBottom,
FaceCount
};
//we are treating the orientation of faces in 4x3 layout as the original direction.
enum class CubemapFaceDirection
{
DirNoRotation = 0,
DirRotateLeft90,
DirRotateRight90,
DirRotate180,
DirMirrorHorizontal
};
//this class contains information to describe a cubemap layout
class CubemapLayoutInfo
{
public:
struct FaceInfo
{
AZ::u8 row;
AZ::u8 column;
CubemapFaceDirection direction;
};
//rows and columns of how cubemap's faces laid
AZ::u8 m_rows;
AZ::u8 m_columns;
//the type of this layout info for
CubemapLayoutType m_type;
//the index of row and column where all the faces located
FaceInfo m_faceInfos[FaceCount];
CubemapLayoutInfo();
void SetFaceInfo(CubemapFace face, AZ::u8 row, AZ::u8 col, CubemapFaceDirection dir);
};
//class to help doing operations with faces for an image as cubemap
class CubemapLayout
{
public:
//create a cubemapLayout object for the image. It can be used later to get image information as a cubemap
static CubemapLayout* CreateCubemapLayout(IImageObjectPtr image);
//get layout info for input layout type
static CubemapLayoutInfo* GetCubemapLayoutInfo(CubemapLayoutType type);
//get layout info for input image based on its size
static CubemapLayoutInfo* GetCubemapLayoutInfo(IImageObjectPtr image);
//public functions to get faces information for associated image
AZ::u32 GetFaceSize();
//get the rect where the face in the image
void GetRectForFace(AZ::u32 mip, CubemapFace face, QRect& outRect);
CubemapLayoutInfo* GetLayoutInfo();
//set/get pixels' data from/to specific face. only works for mip 0
void GetFaceData(CubemapFace face, void* outBuffer, AZ::u32& outSize);
void SetFaceData(CubemapFace face, void* dataBuffer, AZ::u32 dataSize);
//get the face's direction
CubemapFaceDirection GetFaceDirection(CubemapFace face);
//get memory for a face from Image data. only works for CubemapLayoutVertical since its memory for each face is continuous
void* GetFaceMemBuffer(AZ::u32 mip, CubemapFace face, AZ::u32& outPitch);
void SetToFaceMemBuffer(AZ::u32 mip, CubemapFace face, void* dataBuffer);
private:
//information for all supported cubemap layouts
static CubemapLayoutInfo s_layoutList[CubemapLayoutTypeCount];
//the image associated for this CubemapLayout
IImageObjectPtr m_image;
//the layout information of m_image
CubemapLayoutInfo *m_info;
//the size of the cubemap's face (which is square and power of 2).
uint32 m_faceSize;
//private constructor. User should always use CreateCubemapLayout create a layout for an image object
CubemapLayout();
//initialize information of all available cubemap layouts
static void InitCubemapLayoutInfos();
};
}//end namspace ImageProcessing
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,329 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <ImageProcessing_precompiled.h>
#include <math.h>
#include "FIR-Weights.h"
/* ####################################################################################################################
*/
namespace ImageProcessing
{
void calculateFilterRange(unsigned int srcFactor, int& srcFirst, int& srcLast,
unsigned int dstFactor, int dstFirst, int dstLast,
double blurFactor, class IWindowFunction<double>* windowFunction)
{
double s, t, u, scaleFactor; /* scale factors */
double srcRadius, srcCenter; /* window position and size */
#define s0 0
#define s1 srcFactor
#define d0 0
#define d1 dstFactor
/* the mapping from discrete destination coordinates to continuous source coordinates: */
#define MAP(b, scaleFactor, offset) ((b) + (offset)) / (scaleFactor)
/* relation of dstFactor to srcFactor */
s = (double)dstFactor / srcFactor;
t = d0 - s * (s0 - 0.5) - 0.5;
/* compute offsets for MAP */
u = d0 - s * (s0 - 0.5) - t;
/* find scale of filter
* when minifying, scaleFactor = 1/s, but when magnifying, scaleFactor = 1
*/
scaleFactor = (blurFactor == 0.0 ? 1.0 : (blurFactor > 0.0 ? (1.0 + blurFactor) : 1.0 / (1.0 - blurFactor))) * maximum(1., 1. / s);
/* find support radius of scaled filter
* if the window's length is <= 0.5 then we've got point sampling.
*/
srcRadius = maximum(0.5, scaleFactor * windowFunction->getLength());
/* sample the continuous filter, scaled by scaleFactor and
* positioned at continuous source coordinate srcCenter
*/
{
srcCenter = MAP(dstFirst + 0, s, u);
/* find the source coordinate range of this positioned filter window */
srcFirst = int(floor(srcCenter - srcRadius + 0.5));
}
{
srcCenter = MAP(dstLast - 1, s, u);
/* find the source coordinate range of this positioned filter window */
srcLast = int(floor(srcCenter + srcRadius + 0.5));
}
}
template<>
FilterWeights<signed short int>* calculateFilterWeights<signed short int>(unsigned int srcFactor, int srcFirst, int srcLast,
unsigned int dstFactor, int dstFirst, int dstLast, signed short int numRepetitions,
double blurFactor, class IWindowFunction<double>* windowFunction,
bool peaknorm, bool& plusminus)
{
#define WEIGHTBITS 15
#define WEIGHTONE (1 << WEIGHTBITS) /* filter weight of one */
double s, t, u, scaleFactor; /* scale factors */
double srcRadius, srcCenter; /* window position and size */
double sumfWeights, neg, pos, nrmWeights, fWeight; /* window position and size */
int i, i0, i1; /* window position and size */
int dstPosition;
signed short int n;
bool trimZeros = true, stillzero;
int lastnonzero, hWeight, highest;
signed int sumiWeights, iWeight;
signed short int* weightsPtr, *weightsMem;
FilterWeights<signed short int>* weightsObjs;
bool pm, pma = false;
/* pre-calculate filter window solutions for all rows
*/
weightsObjs = new FilterWeights<signed short int>[dstLast - dstFirst];
#define s0 0
#define s1 srcFactor
#define d0 0
#define d1 dstFactor
/* relation of dstFactor to srcFactor */
s = (double)dstFactor / srcFactor;
t = d0 - s * (s0 - 0.5) - 0.5;
/* compute offsets for MAP */
u = d0 - s * (s0 - 0.5) - t;
/* find scale of filter
* when minifying, scaleFactor = 1/s, but when magnifying, scaleFactor = 1
*/
scaleFactor = (blurFactor == 0.0 ? 1.0 : (blurFactor > 0.0 ? (1.0 + blurFactor) : 1.0 / (1.0 - blurFactor))) * maximum(1., 1. / s);
/* find support radius of scaled filter
* if the window's length is <= 0.5 then we've got point sampling.
*/
srcRadius = maximum(0.5, scaleFactor * windowFunction->getLength());
/* sample the continuous filter, scaled by ap->scaleFactor and
* positioned at continuous source coordinate srcCenter, for source coordinates in
* the range [0..len-1], writing the weights into wtab.
* Scale the weights so they sum up to WEIGHTONE, and trim leading and trailing
* zeros if trimZeros is true.
*/
#undef NORMALIZE_SUMMED_PEAK
#define NORMALIZE_MAXXED_PEAK
for (dstPosition = dstFirst, pm = false; dstPosition < dstLast; dstPosition++)
{
srcCenter = MAP(dstPosition, s, u);
/* find the source coordinate range of this positioned filter window */
i0 = int(floor(srcCenter - srcRadius + 0.5));
i1 = int(floor(srcCenter + srcRadius + 0.5));
/* clip against the source-range */
if (i0 < srcFirst)
{
i0 = srcFirst;
}
if (i1 > srcLast)
{
i1 = srcLast;
}
/* this is possible if we hit the final line */
if (i1 <= i0)
{
if (i1 >= srcLast)
{
i0 = i1 - 1;
}
else
{
i1 = i0 + 1;
}
}
AZ_Assert(i0 >= srcFirst, "%s: Invalid source coordinate range!", __FUNCTION__);
AZ_Assert(i1 <= srcLast, "%s: Invalid source coordinate range!", __FUNCTION__);
AZ_Assert(i0 < i1, "%s: Invalid source coordinate range!", __FUNCTION__);
/* find maximum peak to normalize the filter */
for (sumfWeights = 0, pos = 0, neg = 0, i = i0; i < i1; i++)
{
/* evaluate the filter function: */
fWeight = (*windowFunction)((i + 0.5 - srcCenter) / scaleFactor);
#if defined(NORMALIZE_SUMMED_PEAK)
/* get positive and negative summed peaks */
if (fWeight >= 0)
{
pos += fWeight;
}
else
{
neg += fWeight;
}
#elif defined(NORMALIZE_MAXXED_PEAK)
/* get positive and negative maximum peaks */
minmax(fWeight, neg, pos);
#endif
sumfWeights += fWeight;
}
/* the range of source samples to buffer: */
weightsMem = new signed short int[(i1 - i0) * abs(numRepetitions)];
/* set nrmWeights so that sumWeights of windowFunction() is approximately WEIGHTONE
* this needs to be adjusted because the maximum weight-coefficient
* is NOT allowed to leave [-32768,32767]
* a case like {+1.25,-0.25} does produce a sumWeights of 1.0 BUT
* produced a weight much too high (-40000)
*/
#if defined(NORMALIZE_SUMMED_PEAK)
sumfWeights = maximum(-neg, pos);
#elif defined(NORMALIZE_MAXXED_PEAK)
sumfWeights = maximum(sumfWeights, maximum(-neg, pos));
#endif
if (!peaknorm)
{
nrmWeights = (sumfWeights == 0. ? WEIGHTONE : (-neg > pos ? WEIGHTONE - 1 : WEIGHTONE) / sumfWeights);
}
else
{
nrmWeights = (sumfWeights == 0. ? WEIGHTONE : (-neg > pos ? WEIGHTONE - 1 : WEIGHTONE) / maximum(-neg, pos));
}
/* compute the discrete, sampled filter coefficients */
stillzero = trimZeros;
for (sumiWeights = 0, hWeight = -WEIGHTONE, weightsPtr = weightsMem, i = i0; i < i1; i++)
{
/* evaluate the filter function: */
fWeight = (*windowFunction)((i + 0.5 - srcCenter) / scaleFactor);
/* normalize against the peak sumWeights, because the sums are not allowed to leave -32768/32767 */
fWeight = fWeight * nrmWeights;
iWeight = int(round(fWeight));
/* find first nonzero */
if (stillzero && (iWeight == 0))
{
i0++;
}
else
{
AZ_Assert((-fWeight >= -32768.5) && (-fWeight <= 32767.5), "%s:The weight exceeded the maximum weight-coefficient.", __FUNCTION__);
if (!peaknorm)
{
sumiWeights += iWeight;
}
else
{
sumiWeights = maximum(sumiWeights, iWeight);
}
#define sgnextend(n, iWeight) (n & 1 ? (iWeight < 0 ? -1 : 0) : iWeight)
if (numRepetitions < 0)
{
/* add weight to table, interleaved sign */
for (n = 0; n < -numRepetitions; n++)
{
*weightsPtr++ = sgnextend(n, -iWeight);
}
}
else
{
/* add weight to table */
for (n = 0; n < numRepetitions; n++)
{
*weightsPtr++ = -iWeight;
}
}
stillzero = false;
/* find last nonzero */
if (iWeight != 0)
{
lastnonzero = i;
}
/* check for negative values */
if (iWeight < 0)
{
pm = pma = true;
}
/* find most influential value */
if (iWeight >= hWeight)
{
highest = i;
hWeight = iWeight;
}
}
}
if (sumiWeights == 0)
{
i0 = (i0 + i1) >> 1;
i1 = (i0 + 1);
for (n = 0, weightsPtr = weightsMem; n < numRepetitions; n++)
{
*weightsPtr++ = -WEIGHTONE;
}
}
else
{
/* skip leading and trailing zeros */
if (trimZeros)
{
/* set i0 and i1 to the nonzero support of the filter */
i0 = i0;
i1 = i1 = lastnonzero + 1;
}
if (sumiWeights != WEIGHTONE)
{
/* Fudge with the highest value */
i = highest;
/* fudge srcCenter sample */
iWeight = WEIGHTONE - sumiWeights;
for (n = 0, weightsPtr = weightsMem + (i - i0) * numRepetitions; n < numRepetitions; n++)
{
*weightsPtr++ -= iWeight;
}
}
}
/* the new adjusted range of source samples to buffer: */
weightsObjs[dstPosition].first = i0;
weightsObjs[dstPosition].last = i1;
weightsObjs[dstPosition].hasNegativeWeights = pm;
weightsObjs[dstPosition].weights = weightsMem;
}
plusminus = pma;
return weightsObjs;
}
}
@@ -0,0 +1,83 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include "FIR-Windows.h"
namespace ImageProcessing
{
/* ####################################################################################################################
*/
template<class DataType>
inline DataType abs (const DataType& ths) { return (ths < 0 ? -ths : ths); }
template<class DataType>
inline void minmax (const DataType& ths, DataType& mn, DataType& mx) { mn = (mn > ths ? ths : mn); mx = (mx < ths ? ths : mx); }
template<class DataType>
inline DataType minimum(const DataType& ths, const DataType& tht) { return (ths < tht ? ths : tht); }
template<class DataType>
inline DataType maximum(const DataType& ths, const DataType& tht) { return (ths > tht ? ths : tht); }
/* ####################################################################################################################
*/
template<class T>
class FilterWeights
{
public:
FilterWeights()
: weights(nullptr)
{
}
~FilterWeights()
{
delete[] weights;
}
public:
// window-position
int first, last;
// do we encounter positive as well as negative weights
bool hasNegativeWeights;
/* weights, summing up to -(1 << 15),
* means weights are given negative
* that enables us to use signed short
* multiplication while occupying 0x8000
*/
T* weights;
};
/* ####################################################################################################################
*/
void calculateFilterRange (unsigned int srcFactor, int& srcFirst, int& srcLast,
unsigned int dstFactor, int dstFirst, int dstLast,
double blurFactor, class IWindowFunction<double>* windowFunction);
template<typename T>
FilterWeights<T>* calculateFilterWeights(unsigned int srcFactor, int srcFirst, int srcLast,
unsigned int dstFactor, int dstFirst, int dstLast, signed short int numRepetitions,
double blurFactor, class IWindowFunction<double>* windowFunction,
bool peaknorm, bool& plusminus);
template<>
FilterWeights<signed short int>* calculateFilterWeights<signed short int>(unsigned int srcFactor, int srcFirst, int srcLast,
unsigned int dstFactor, int dstFirst, int dstLast, signed short int numRepetitions,
double blurFactor, class IWindowFunction<double>* windowFunction,
bool peaknorm, bool& plusminus);
} //end namespace ImageProcessing
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <ImageProcessing/ImageObject.h>
#include <Processing/ImageToProcess.h>
#include <Processing/PixelFormatInfo.h>
#include <ImageProcessing/PixelFormats.h>
#include <Converters/FIR-Weights.h>
#include <Converters/PixelOperation.h>
#include <Processing/ImageFlags.h>
namespace ImageProcessing
{
///////////////////////////////////////////////////////////////////////////////////
// Lookup table for a function 'float fn(float x)'.
// Computed function values are stored in the table for x in [0.0; 1.0].
//
// If passed x is less than xMin (xMin must be >= 0) or greater than 1.0,
// then the original function is called.
// Otherwise, a value from the table (linearly interpolated)
// is returned.
template <int TABLE_SIZE>
class FunctionLookupTable
{
public:
FunctionLookupTable(float(*fn)(float x), float xMin, float maxAllowedDifference)
: m_fn(fn)
, m_xMin(xMin)
, m_fMaxDiff(maxAllowedDifference)
{
}
void Initialize() const
{
m_initialized = true;
AZ_Assert(m_xMin >= 0.0f, "wrong initial data for m_xMin");
for (int i = 0; i <= TABLE_SIZE; ++i)
{
const float x = i / (float)TABLE_SIZE;
const float y = (*m_fn)(x);
m_table[i] = y;
}
}
inline float compute(float x) const
{
if (x < m_xMin || x > 1)
{
return m_fn(x);
}
const float f = x * TABLE_SIZE;
const int i = int(f);
if (!m_initialized)
{
Initialize();
}
if (i >= TABLE_SIZE)
{
return m_table[TABLE_SIZE];
}
const float alpha = f - i;
return (1 - alpha) * m_table[i] + alpha * m_table[i + 1];
}
public:
bool Test(const float maxDifferenceAllowed) const
{
if (int(-0.99f) != 0 ||
int(+0.00f) != 0 ||
int(+0.01f) != 0 ||
int(+0.99f) != 0 ||
int(+1.00f) != 1 ||
int(+1.01f) != 1 ||
int(+1.99f) != 1 ||
int(+2.00f) != 2 ||
int(+2.01f) != 2)
{
return false;
}
if (m_xMin < 0)
{
return false;
}
const int n = 1000000;
for (int i = 0; i <= n; ++i)
{
const float x = 1.1f * (i / (float)n);
const float resOriginal = m_fn(x);
const float resTable = compute(x);
const float difference = resOriginal - resTable;
if (fabs(difference) > maxDifferenceAllowed)
{
return false;
}
}
return true;
}
private:
float(*m_fn)(float x);
float m_xMin;
mutable float m_table[TABLE_SIZE + 1];
mutable bool m_initialized = false;
float m_fMaxDiff = 0.0f;
};
static float GammaToLinear(float x)
{
return (x <= 0.04045f) ? x / 12.92f : powf((x + 0.055f) / 1.055f, 2.4f);
}
static float LinearToGamma(float x)
{
return (x <= 0.0031308f) ? x * 12.92f : 1.055f * powf(x, 1.0f / 2.4f) - 0.055f;
}
static FunctionLookupTable<1024> s_lutGammaToLinear(GammaToLinear, 0.04045f, 0.00001f);
static FunctionLookupTable<1024> s_lutLinearToGamma(LinearToGamma, 0.05f, 0.00001f);
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
bool ImageToProcess::GammaToLinearRGBA32F(bool bDeGamma)
{
// return immediately if there is no need to de-gamma image and the source is in the desired format
EPixelFormat srcFmt = m_img->GetPixelFormat();
if (!bDeGamma && (srcFmt == ePixelFormat_R32G32B32A32F))
{
return true;
}
//convert to 32F first
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(srcFmt))
{
AZ_Warning("Image Processing", false, "This is not common user case with compressed format input. But it may continue");
ConvertFormat(ePixelFormat_R32G32B32A32F);
}
IImageObjectPtr srcImage = m_img;
EPixelFormat dstFmt = ePixelFormat_R32G32B32A32F;
IImageObjectPtr dstImage(m_img->AllocateImage(dstFmt));
//create pixel operation function for src and dst images
IPixelOperationPtr srcOp = CreatePixelOperation(srcFmt);
IPixelOperationPtr dstOp = CreatePixelOperation(dstFmt);
//get count of bytes per pixel for both src and dst images
uint32 srcPixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(srcFmt)->bitsPerBlock / 8;
uint32 dstPixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(dstFmt)->bitsPerBlock / 8;
const uint32 dwMips = dstImage->GetMipCount();
float r, g, b, a;
for (uint32 dwMip = 0; dwMip < dwMips; ++dwMip)
{
uint8* srcPixelBuf;
uint32 srcPitch;
srcImage->GetImagePointer(dwMip, srcPixelBuf, srcPitch);
uint8* dstPixelBuf;
uint32 dstPitch;
dstImage->GetImagePointer(dwMip, dstPixelBuf, dstPitch);
const uint32 pixelCount = srcImage->GetPixelCount(dwMip);
for (uint32 i = 0; i < pixelCount; ++i, srcPixelBuf += srcPixelBytes, dstPixelBuf += dstPixelBytes)
{
srcOp->GetRGBA(srcPixelBuf, r, g, b, a);
if (bDeGamma)
{
r = s_lutGammaToLinear.compute(r);
g = s_lutGammaToLinear.compute(g);
b = s_lutGammaToLinear.compute(b);
}
dstOp->SetRGBA(dstPixelBuf, r, g, b, a);
}
}
m_img = dstImage;
if (bDeGamma)
{
m_img->RemoveImageFlags(EIF_SRGBRead);
}
return true;
}
void ImageToProcess::LinearToGamma()
{
if (Get()->HasImageFlags(EIF_SRGBRead))
{
AZ_Assert(false, "%s: input image is already SRGB", __FUNCTION__);
return;
}
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_img->GetPixelFormat()))
{
AZ_Assert(false, "This is not common user case with compressed format input. But it may continue");
ConvertFormat(ePixelFormat_R32G32B32A32F);
}
EPixelFormat srcFmt = m_img->GetPixelFormat();
IImageObjectPtr srcImage = m_img;
IImageObjectPtr dstImage(m_img->AllocateImage(srcFmt));
//create pixel operation function
IPixelOperationPtr pixelOp = CreatePixelOperation(srcFmt);
//get count of bytes per pixel for both src and dst images
uint32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(srcFmt)->bitsPerBlock / 8;
const uint32 dwMips = srcImage->GetMipCount();
float r, g, b, a;
for (uint32 dwMip = 0; dwMip < dwMips; ++dwMip)
{
uint8* srcPixelBuf;
uint32 srcPitch;
srcImage->GetImagePointer(dwMip, srcPixelBuf, srcPitch);
uint8* dstPixelBuf;
uint32 dstPitch;
dstImage->GetImagePointer(dwMip, dstPixelBuf, dstPitch);
const uint32 pixelCount = srcImage->GetPixelCount(dwMip);
for (uint32 i = 0; i < pixelCount; ++i, srcPixelBuf += pixelBytes, dstPixelBuf += pixelBytes)
{
pixelOp->GetRGBA(srcPixelBuf, r, g, b, a);
r = s_lutLinearToGamma.compute(r);
g = s_lutLinearToGamma.compute(g);
b = s_lutLinearToGamma.compute(b);
pixelOp->SetRGBA(dstPixelBuf, r, g, b, a);
}
}
m_img = dstImage;
Get()->AddImageFlags(EIF_SRGBRead);
}
} // namespace ImageProcessing
@@ -0,0 +1,110 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageToProcess.h>
#include <Processing/ImageConvert.h>
#include <Processing/PixelFormatInfo.h>
#include <Converters/FIR-Windows.h>
#include <Converters/PixelOperation.h>
namespace ImageProcessing
{
// higher mip level is subtracted by lower mip level when applying the [cheap] high pass filter
void ImageToProcess::CreateHighPass(AZ::u32 dwMipDown)
{
//no need to convert if mip go down 0
if (dwMipDown == 0)
{
return;
}
const EPixelFormat ePixelFormat = m_img->GetPixelFormat();
if (ePixelFormat != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "You need convert the orginal image to ePixelFormat_R32G32B32A32F before call this function");
return;
}
AZ::u32 dwWidth, dwHeight, dwMips;
dwWidth = m_img->GetWidth(0);
dwHeight = m_img->GetHeight(0);
dwMips = m_img->GetMipCount();
if (dwMipDown >= dwMips)
{
AZ_Warning("Image Processing", false, "CreateHighPass can't go down %i MIP levels for high pass as there are not\
enough MIP levels available, going down by %i instead", dwMipDown, dwMips - 1);
dwMipDown = dwMips - 1;
}
IImageObjectPtr newImage(IImageObject::CreateImage(dwWidth, dwHeight, dwMips, ePixelFormat));
newImage->CopyPropertiesFrom(m_img);
IPixelOperationPtr pixelOp = CreatePixelOperation(ePixelFormat);
AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(ePixelFormat)->bitsPerBlock / 8;
AZ::u32 dstMips = newImage->GetMipCount();
for (AZ::u32 dstMip = 0; dstMip < dwMipDown; ++dstMip)
{
// linear interpolation
FilterImage(MipGenType::triangle, MipGenEvalType::sum, 0.0f, 0.0f, m_img, dwMipDown, newImage, dstMip, NULL, NULL);
const AZ::u32 pixelCountIn = m_img->GetWidth(dstMip) *m_img->GetHeight(dstMip);
const AZ::u32 pixelCountOut = newImage->GetWidth(dstMip) * newImage->GetHeight(dstMip);
//substraction
AZ::u8* srcPixelBuf;
AZ::u32 srcPitch;
m_img->GetImagePointer(dstMip, srcPixelBuf, srcPitch);
AZ::u8* dstPixelBuf;
AZ::u32 dstPitch;
newImage->GetImagePointer(dstMip, dstPixelBuf, dstPitch);
const AZ::u32 pixelCount = newImage->GetPixelCount(dstMip);
for (AZ::u32 i = 0; i < pixelCount; ++i, srcPixelBuf += pixelBytes, dstPixelBuf += pixelBytes)
{
float r1, g1, b1, a1, r2, g2, b2, a2;
pixelOp->GetRGBA(srcPixelBuf, r1, g1, b1, a1);
pixelOp->GetRGBA(dstPixelBuf, r2, g2, b2, a2);
r2 = AZ::GetClamp<float>(r1 - r2 + 0.5f, 0.0f, 1.0f);
g2 = AZ::GetClamp<float>(g1 - g2 + 0.5f, 0.0f, 1.0f);
b2 = AZ::GetClamp<float>(b1 - b2 + 0.5f, 0.0f, 1.0f);
a2 = AZ::GetClamp<float>(a1 - a2 + 0.5f, 0.0f, 1.0f);
pixelOp->SetRGBA(dstPixelBuf, r2, g2, b2, a2);
}
}
// mips below the chosen highpass mip are grey
for (AZ::u32 dstMip = dwMipDown; dstMip < dstMips; ++dstMip)
{
AZ::u8* dstPixelBuf;
AZ::u32 dstPitch;
newImage->GetImagePointer(dstMip, dstPixelBuf, dstPitch);
const AZ::u32 pixelCount = newImage->GetPixelCount(dstMip);
for (AZ::u32 i = 0; i < pixelCount; ++i, dstPixelBuf += pixelBytes)
{
pixelOp->SetRGBA(dstPixelBuf, 0.5f, 0.5f, 0.5f, 1.0f);
}
}
m_img = newImage;
}
} // namespace ImageProcessing
@@ -0,0 +1,81 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <ImageProcessing/PixelFormats.h>
#include <ImageProcessing/ImageObject.h>
#include <Converters/PixelOperation.h>
#include <Processing/PixelFormatInfo.h>
#include <Converters/Histogram.h>
///////////////////////////////////////////////////////////////////////////////////
namespace ImageProcessing
{
float GetLuminance(const float& r, const float& g, const float& b)
{
return (r * 0.30f + g * 0.59f + b * 0.11f);
}
bool ComputeLuminanceHistogram(IImageObjectPtr imageObject, Histogram<256>& histogram)
{
EPixelFormat pixelFormat = imageObject->GetPixelFormat();
if (!(CPixelFormats::GetInstance().IsPixelFormatUncompressed(pixelFormat)))
{
AZ_Assert(false, "%s function only works with uncompressed pixel format", __FUNCTION__);
return false;
}
//create pixel operation function
IPixelOperationPtr pixelOp = CreatePixelOperation(pixelFormat);
//setup histogram bin
static const size_t binCount = 256;
Histogram<binCount>::Bins bins;
Histogram<binCount>::clearBins(bins);
//get count of bytes per pixel
AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat)->bitsPerBlock / 8;
const AZ::u32 mipCount = imageObject->GetMipCount();
float color[4];
for (uint32 mip = 0; mip < mipCount; ++mip)
{
AZ::u8* pixelBuf;
AZ::u32 pitch;
imageObject->GetImagePointer(mip, pixelBuf, pitch);
const uint32 pixelCount = imageObject->GetPixelCount(mip);
for (uint32 i = 0; i < pixelCount; ++i, pixelBuf += pixelBytes)
{
pixelOp->GetRGBA(pixelBuf, color[0], color[1], color[2], color[3]);
const float luminance = AZ::GetClamp(GetLuminance(color[0], color[1], color[2]), 0.0f, 1.0f);
const float f = luminance * binCount;
if (f <= 0)
{
++bins[0];
}
else
{
const int bin = int(f);
++bins[(bin < binCount) ? bin : binCount - 1];
}
}
}
histogram.set(bins);
return true;
}
} // end namespace ImageProcessing
@@ -0,0 +1,84 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
namespace ImageProcessing
{
template <size_t BIN_COUNT>
class Histogram
{
public:
typedef AZ::u64 Bins[BIN_COUNT];
public:
Histogram()
{
}
static void clearBins(Bins& bins)
{
memset(&bins, 0, sizeof(bins));
}
void set(const Bins& bins)
{
m_bins[0] = bins[0];
m_binsCumulative[0] = bins[0];
double sum = 0.0f;
for (size_t i = 1; i < BIN_COUNT; ++i)
{
m_bins[i] = bins[i];
m_binsCumulative[i] = m_binsCumulative[i - 1] + bins[i];
sum += i * double(bins[i]);
}
const AZ::u64 totalCount = getTotalSampleCount();
m_meanBin = (totalCount <= 0) ? 0.0f : float(sum / totalCount);
}
AZ::u64 getTotalSampleCount() const
{
return m_binsCumulative[BIN_COUNT - 1];
}
float getPercentage(size_t minBin, size_t maxBin) const
{
const AZ::u64 totalCount = getTotalSampleCount();
if ((totalCount <= 0) || (minBin > maxBin) || (maxBin < 0) || (minBin >= BIN_COUNT))
{
return 0.0f;
}
minBin = AZ::GetMax(minBin, size_t(0));
maxBin = AZ::GetMin(maxBin, BIN_COUNT-1);
const AZ::u64 count = m_binsCumulative[maxBin] - ((minBin <= 0) ? 0 : m_binsCumulative[minBin-1]);
return float((double(count) * 100.0) / double(totalCount));
}
float getMeanBin() const
{
return m_meanBin;
}
private:
Bins m_bins;
Bins m_binsCumulative;
float m_meanBin;
};
bool ComputeLuminanceHistogram(IImageObjectPtr imageObject, Histogram<256>& histogram);
}
@@ -0,0 +1,420 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <ImageProcessing_precompiled.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageFlags.h>
namespace ImageProcessing
{
template<const int qBits>
static void AdjustScaleForQuantization(float fBaseValue, float fBaseLine, float& cScale, float& cMinColor, float& cMaxColor)
{
const int qOne = (1 << qBits) - 1;
const int qUpperBits = (8 - qBits);
const int qLowerBits = qBits - qUpperBits;
const int v = int(floor(fBaseValue * qOne));
int v0 = v - (v != 0);
int v1 = v + 0;
int v2 = v + (v != qOne);
v0 = (v0 << qUpperBits) | (v0 >> qLowerBits);
v1 = (v1 << qUpperBits) | (v1 >> qLowerBits);
v2 = (v2 << qUpperBits) | (v2 >> qLowerBits);
const float f0 = v0 / 255.0f;
const float f1 = v1 / 255.0f;
const float f2 = v2 / 255.0f;
float fBaseLock = -1;
if (fabsf(f0 - fBaseValue) < fabsf(fBaseLock - fBaseValue))
{
fBaseLock = f0;
}
if (fabsf(f1 - fBaseValue) < fabsf(fBaseLock - fBaseValue))
{
fBaseLock = f1;
}
if (fabsf(f2 - fBaseValue) < fabsf(fBaseLock - fBaseValue))
{
fBaseLock = f2;
}
float lScale = (1.0f - fBaseLock) / (1.0f - fBaseLine);
float vScale = (1.0f - fBaseValue) / (1.0f - fBaseLine);
float sScale = lScale / vScale;
float csScale = (cScale / sScale);
float csBias = cMinColor - (1.0f - sScale) * (cScale / sScale);
if ((csBias > 0.0f) && ((csScale + csBias) < 1.0f))
{
cMinColor = csBias;
cScale = csScale;
cMaxColor = csScale + csBias;
}
}
///////////////////////////////////////////////////////////////////////////////////
void CImageObject::NormalizeImageRange(EColorNormalization eColorNorm, EAlphaNormalization eAlphaNorm, bool bMaintainBlack, int nExponentBits)
{
if (GetPixelFormat() != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "%s: unsupported source format", __FUNCTION__);
return;
}
uint32 dwWidth, dwHeight, dwMips;
GetExtent(dwWidth, dwHeight, dwMips);
// find image's range, can be negative
float cMinColor[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
float cMaxColor[4] = { -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX };
for (uint32 dwMip = 0; dwMip < dwMips; ++dwMip)
{
uint8* pSrcMem;
uint32 dwSrcPitch;
GetImagePointer(dwMip, pSrcMem, dwSrcPitch);
dwHeight = GetHeight(dwMip);
dwWidth = GetWidth(dwMip);
for (uint32 dwY = 0; dwY < dwHeight; ++dwY)
{
const float* pSrcPix = (float*)&pSrcMem[dwY * dwSrcPitch];
for (uint32 dwX = 0; dwX < dwWidth; ++dwX)
{
cMinColor[0] = AZ::GetMin(cMinColor[0], pSrcPix[0]);
cMinColor[1] = AZ::GetMin(cMinColor[1], pSrcPix[1]);
cMinColor[2] = AZ::GetMin(cMinColor[2], pSrcPix[2]);
cMinColor[3] = AZ::GetMin(cMinColor[3], pSrcPix[3]);
cMaxColor[0] = AZ::GetMax(cMaxColor[0], pSrcPix[0]);
cMaxColor[1] = AZ::GetMax(cMaxColor[1], pSrcPix[1]);
cMaxColor[2] = AZ::GetMax(cMaxColor[2], pSrcPix[2]);
cMaxColor[3] = AZ::GetMax(cMaxColor[3], pSrcPix[3]);
pSrcPix += 4;
}
}
}
if (bMaintainBlack)
{
cMinColor[0] = AZ::GetMin(0.f, cMinColor[0]);
cMinColor[1] = AZ::GetMin(0.f, cMinColor[1]);
cMinColor[2] = AZ::GetMin(0.f, cMinColor[2]);
cMinColor[3] = AZ::GetMin(0.f, cMinColor[3]);
}
AZ_Assert(cMaxColor[0] >= cMinColor[0] && cMaxColor[1] >= cMinColor[1] &&
cMaxColor[2] >= cMinColor[2] && cMaxColor[3] >= cMinColor[3], "bad color range");
// some graceful threshold to avoid extreme cases
if (cMaxColor[0] - cMinColor[0] < (3.f / 255))
{
cMinColor[0] = AZ::GetMax(0.f, cMinColor[0] - (2.f / 255));
cMaxColor[0] = AZ::GetMin(1.f, cMaxColor[0] + (2.f / 255));
}
if (cMaxColor[1] - cMinColor[1] < (3.f / 255))
{
cMinColor[1] = AZ::GetMax(0.f, cMinColor[1] - (2.f / 255));
cMaxColor[1] = AZ::GetMin(1.f, cMaxColor[1] + (2.f / 255));
}
if (cMaxColor[2] - cMinColor[2] < (3.f / 255))
{
cMinColor[2] = AZ::GetMax(0.f, cMinColor[2] - (2.f / 255));
cMaxColor[2] = AZ::GetMin(1.f, cMaxColor[2] + (2.f / 255));
}
if (cMaxColor[3] - cMinColor[3] < (3.f / 255))
{
cMinColor[3] = AZ::GetMax(0.f, cMinColor[3] - (2.f / 255));
cMaxColor[3] = AZ::GetMin(1.f, cMaxColor[3] + (2.f / 255));
}
// calculate range to normalize to
const float fMaxExponent = powf(2.0f, (float)nExponentBits) - 1.0f;
const float cUprValue = powf(2.0f, fMaxExponent);
if (eColorNorm == eColorNormalization_PassThrough)
{
cMinColor[0] = cMinColor[1] = cMinColor[2] = 0.f;
cMaxColor[0] = cMaxColor[1] = cMaxColor[2] = 1.f;
}
// don't touch alpha channel if not used
if (eAlphaNorm == eAlphaNormalization_SetToZero)
{
// Store the range explicitly into the structure for read-back.
// The formats which request range expansion don't support alpha.
cMinColor[3] = 0.f;
cMaxColor[3] = cUprValue;
}
else if (eAlphaNorm == eAlphaNormalization_PassThrough)
{
cMinColor[3] = 0.f;
cMaxColor[3] = 1.f;
}
// get the origins of the color model's lattice for the range of values
// these values need to be encoded as precise as possible under quantization
AZ::Vector4 cBaseLines = AZ::Vector4(0.0f, 0.0f, 0.0f, 0.0f);
AZ::Vector4 cScale = AZ::Vector4(cMaxColor[0] - cMinColor[0], cMaxColor[1] - cMinColor[1],
cMaxColor[2] - cMinColor[2], cMaxColor[3] - cMinColor[3]);
#if 0
// NOTE: disabled for now, in the future we can turn this on to force availability
// of value to guarantee for example perfect grey-scales (using YFF)
switch (GetImageFlags() & EIF_Colormodel)
{
case EIF_Colormodel_RGB:
cBaseLines = Vec4(0.0f, 0.0f, 0.0f, 0.0f);
break;
case EIF_Colormodel_CIE:
cBaseLines = Vec4(0.0f, 1.f / 3, 1.f / 3, 0.0f);
break;
case EIF_Colormodel_IRB:
cBaseLines = Vec4(0.0f, 1.f / 2, 1.f / 2, 0.0f);
break;
case EIF_Colormodel_YCC:
case EIF_Colormodel_YFF:
cBaseLines = Vec4(1.f / 2, 0.0f, 1.f / 2, 0.0f);
break;
}
Vec4 cBaseScale = cBaseLines;
cBaseLines = cBaseLines - cMinColor;
cBaseLines = cBaseLines / cScale;
if ((cBaseLines.x > 0.0f) && (cBaseLines.x < 1.0f))
{
AdjustScaleForQuantization<5>(cBaseLines.x, cBaseScale.x, cScale.x, cMinColor.x, cMaxColor.x);
}
if ((cBaseLines.y > 0.0f) && (cBaseLines.y < 1.0f))
{
AdjustScaleForQuantization<6>(cBaseLines.y, cBaseScale.y, cScale.y, cMinColor.y, cMaxColor.y);
}
if ((cBaseLines.z > 0.0f) && (cBaseLines.z < 1.0f))
{
AdjustScaleForQuantization<5>(cBaseLines.z, cBaseScale.z, cScale.z, cMinColor.z, cMaxColor.z);
}
#endif
// normalize the image
AZ::Vector4 vMin = AZ::Vector4(cMinColor[0], cMinColor[1], cMinColor[2], cMinColor[3]);
for (uint32 dwMip = 0; dwMip < dwMips; ++dwMip)
{
uint8* pSrcMem;
uint32 dwSrcPitch;
GetImagePointer(dwMip, pSrcMem, dwSrcPitch);
dwHeight = GetHeight(dwMip);
dwWidth = GetWidth(dwMip);
for (uint32 dwY = 0; dwY < dwHeight; ++dwY)
{
AZ::Vector4* pSrcPix = (AZ::Vector4*)&pSrcMem[dwY * dwSrcPitch];
for (uint32 dwX = 0; dwX < dwWidth; ++dwX)
{
*pSrcPix = *pSrcPix - vMin;
*pSrcPix = *pSrcPix / cScale;
*pSrcPix = *pSrcPix * cUprValue;
pSrcPix++;
}
}
}
// set up a range
SetColorRange(AZ::Color(cMinColor[0], cMinColor[1], cMinColor[2], cMinColor[3]),
AZ::Color(cMaxColor[0], cMaxColor[1], cMaxColor[2], cMaxColor[3]));
// set up a flag
AddImageFlags(EIF_RenormalizedTexture);
}
void CImageObject::ExpandImageRange([[maybe_unused]] EColorNormalization eColorMode, EAlphaNormalization eAlphaMode, int nExponentBits)
{
AZ_Assert(!((eAlphaMode != eAlphaNormalization_SetToZero) && (nExponentBits != 0)), "%s: Unexpected alpha mode", __FUNCTION__);
if (!HasImageFlags(EIF_RenormalizedTexture))
{
return;
}
if (GetPixelFormat() != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "%s: only supports source format A32B32G32R32F", __FUNCTION__);
return;
}
uint32 dwWidth, dwHeight, dwMips;
GetExtent(dwWidth, dwHeight, dwMips);
// calculate range to normalize to
const float fMaxExponent = powf(2.0f, (float)nExponentBits) - 1.0f;
float cUprValue = powf(2.0f, fMaxExponent);
// find image's range, can be negative
AZ::Color cMinColor = AZ::Color(FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX);
AZ::Color cMaxColor = AZ::Color(-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX);
GetColorRange(cMinColor, cMaxColor);
// don't touch alpha channel if not used
if (eAlphaMode == eAlphaNormalization_SetToZero)
{
// Overwrite the range explicitly into the structure.
// The formats which request range expansion don't support alpha.
cUprValue = cMaxColor.GetA();
cMinColor.SetA(1.f);
cMaxColor.SetA(1.f);
}
// expand the image
const AZ::Vector4 cScale = cMaxColor.GetAsVector4() - cMinColor.GetAsVector4();
for (uint32 dwMip = 0; dwMip < dwMips; ++dwMip)
{
uint8* pSrcMem;
uint32 dwSrcPitch;
GetImagePointer(dwMip, pSrcMem, dwSrcPitch);
dwHeight = GetHeight(dwMip);
dwWidth = GetWidth(dwMip);
for (uint32 dwY = 0; dwY < dwHeight; ++dwY)
{
AZ::Vector4* pSrcPix = (AZ::Vector4*)&pSrcMem[dwY * dwSrcPitch];
for (uint32 dwX = 0; dwX < dwWidth; ++dwX)
{
*pSrcPix = *pSrcPix / cUprValue;
*pSrcPix = *pSrcPix * cScale;
*pSrcPix = *pSrcPix + cMinColor.GetAsVector4();
pSrcPix++;
}
}
}
// set up a range
SetColorRange(AZ::Color(0.0f, 0.0f, 0.0f, 0.0f), AZ::Color(1.0f, 1.0f, 1.0f, 1.0f));
// set up a flag
RemoveImageFlags(EIF_RenormalizedTexture);
}
///////////////////////////////////////////////////////////////////////////////////
void CImageObject::NormalizeVectors(AZ::u32 firstMip, AZ::u32 maxMipCount)
{
if (GetPixelFormat() != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "%s: only supports source format A32B32G32R32F", __FUNCTION__);
return;
}
uint32 lastMip = AZ::GetMin(firstMip + maxMipCount, GetMipCount());
for (uint32 mip = firstMip; mip < lastMip; ++mip)
{
const uint32 pixelCount = GetPixelCount(mip);
uint8* imageMem;
uint32 pitch;
GetImagePointer(mip, imageMem, pitch);
float* pPixels = (float*)imageMem;
for (uint32 i = 0; i < pixelCount; ++i, pPixels += 4)
{
AZ::Vector3 vNormal = AZ::Vector3(pPixels[0] * 2.0f - 1.0f, pPixels[1] * 2.0f - 1.0f, pPixels[2] * 2.0f - 1.0f);
// TODO: every opposing vector addition produces the zero-vector for
// normals on the entire sphere, in that case the forward vector [0,0,1]
// isn't necessarily right and we should look at the adjacent normals
// for a direction
if (vNormal.IsZero())
{
vNormal = AZ::Vector3(1.0f, 0.0f, 0.0f);
}
else
{
vNormal.NormalizeSafe();
}
pPixels[0] = vNormal.GetX() * 0.5f + 0.5f;
pPixels[1] = vNormal.GetY() * 0.5f + 0.5f;
pPixels[2] = vNormal.GetZ() * 0.5f + 0.5f;
}
}
}
///////////////////////////////////////////////////////////////////////////////////
void CImageObject::ScaleAndBiasChannels(AZ::u32 firstMip, AZ::u32 maxMipCount, const AZ::Vector4& scale, const AZ::Vector4& bias)
{
if (GetPixelFormat() != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "%s: only supports source format A32B32G32R32F", __FUNCTION__);
return;
}
const uint32 lastMip = AZ::GetMin(firstMip + maxMipCount, GetMipCount());
for (uint32 mip = firstMip; mip < lastMip; ++mip)
{
const uint32 pixelCount = GetPixelCount(mip);
uint8* imageMem;
uint32 pitch;
GetImagePointer(mip, imageMem, pitch);
float* pPixels = (float*)imageMem;
for (uint32 i = 0; i < pixelCount; ++i, pPixels += 4)
{
pPixels[0] = pPixels[0] * scale.GetX() + bias.GetX();
pPixels[1] = pPixels[1] * scale.GetY() + bias.GetY();
pPixels[2] = pPixels[2] * scale.GetZ() + bias.GetZ();
pPixels[3] = pPixels[3] * scale.GetW() + bias.GetW();
}
}
}
///////////////////////////////////////////////////////////////////////////////////
void CImageObject::ClampChannels(AZ::u32 firstMip, AZ::u32 maxMipCount, const AZ::Vector4& min, const AZ::Vector4& max)
{
if (GetPixelFormat() != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "%s: only supports source format A32B32G32R32F", __FUNCTION__);
return;
}
const uint32 lastMip = AZ::GetMin(firstMip + maxMipCount, GetMipCount());
for (uint32 mip = firstMip; mip < lastMip; ++mip)
{
const uint32 pixelCount = GetPixelCount(mip);
uint8* imageMem;
uint32 pitch;
GetImagePointer(mip, imageMem, pitch);
float* pPixels = (float*)imageMem;
for (uint32 i = 0; i < pixelCount; ++i, pPixels += 4)
{
pPixels[0] = AZ::GetClamp(pPixels[0], float(min.GetX()), float(max.GetX()));
pPixels[1] = AZ::GetClamp(pPixels[1], float(min.GetY()), float(max.GetY()));
pPixels[2] = AZ::GetClamp(pPixels[2], float(min.GetZ()), float(max.GetZ()));
pPixels[3] = AZ::GetClamp(pPixels[3], float(min.GetW()), float(max.GetW()));
}
}
}
} //namespace ImageProcessing
@@ -0,0 +1,487 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <ImageProcessing_precompiled.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/ImageConvert.h>
#include <Processing/PixelFormatInfo.h>
#include <Converters/PixelOperation.h>
///////////////////////////////////////////////////////////////////////////////////
//functions for maintaining alpha coverage.
namespace ImageProcessing
{
//convertion: all data type supported by pixel channel <=> float
float U8ToF32(uint8 in)
{
return in / 255.f;
}
uint8 F32ToU8(float in)
{
return aznumeric_cast<uint8>(round(AZ::GetClamp(in, 0.f, 1.f) * 255));
}
float U16ToF32(uint16 in)
{
return in / 65535.f;
}
uint16 F32ToU16(float in)
{
return aznumeric_cast<uint16>(round(AZ::GetClamp(in, 0.f, 1.f) * 65535.f));
}
float HalfToF32(SHalf in)
{
return in;
}
SHalf F32ToHalf(float in)
{
return SHalf(in);
}
//stucture for RGBE pixel format
struct RgbE
{
static const int RGB9E5_EXPONENT_BITS = 5;
static const int RGB9E5_MANTISSA_BITS = 9;
static const int RGB9E5_EXP_BIAS = 15;
static const int RGB9E5_MAX_VALID_BIASED_EXP = 31;
static const int MAX_RGB9E5_EXP = (RGB9E5_MAX_VALID_BIASED_EXP - RGB9E5_EXP_BIAS);
static const int RGB9E5_MANTISSA_VALUES = (1 << RGB9E5_MANTISSA_BITS);
static const int MAX_RGB9E5_MANTISSA = (RGB9E5_MANTISSA_VALUES - 1);
static float MAX_RGB9E5;
unsigned int r : 9;
unsigned int g : 9;
unsigned int b : 9;
unsigned int e : 5;
static int log2(float x)
{
int bitfield = *((int*)(&x));
bitfield &= ~0x80000000;
return ((bitfield >> 23) - 127);
}
void GetRGBF(float& outR, float& outG, float& outB) const
{
int exponent = e - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS;
float scale = powf(2.0f, aznumeric_cast<float>(exponent));
outR = r * scale;
outG = g * scale;
outB = b * scale;
}
void SetRGBF(const float& inR, const float& inG, const float& inB)
{
float rf = AZStd::GetMax(0.0f, AZStd::GetMin(inR, MAX_RGB9E5));
float gf = AZStd::GetMax(0.0f, AZStd::GetMin(inG, MAX_RGB9E5));
float bf = AZStd::GetMax(0.0f, AZStd::GetMin(inB, MAX_RGB9E5));
float mf = AZStd::GetMax(rf, AZStd::GetMax(gf, bf));
e = AZStd::GetMax(0, log2(mf) + (RGB9E5_EXP_BIAS + 1));
int exponent = e - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS;
float scale = powf(2.0f, aznumeric_cast<float>(exponent));
r = AZStd::GetMin(511, (int)floorf(rf / scale + 0.5f));
g = AZStd::GetMin(511, (int)floorf(gf / scale + 0.5f));
b = AZStd::GetMin(511, (int)floorf(bf / scale + 0.5f));
}
};
float RgbE::MAX_RGB9E5 = (((float)MAX_RGB9E5_MANTISSA) / RGB9E5_MANTISSA_VALUES * (1 << MAX_RGB9E5_EXP));
//ePixelFormat_R8G8B8A8
class PixelOperationR8G8B8A8 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint8* data = buf;
r = U8ToF32(data[0]);
g = U8ToF32(data[1]);
b = U8ToF32(data[2]);
a = U8ToF32(data[3]);
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, const float& a) override
{
uint8* data = buf;
data[0] = F32ToU8(r);
data[1] = F32ToU8(g);
data[2] = F32ToU8(b);
data[3] = F32ToU8(a);
}
};
//ePixelFormat_R8G8B8X8
class PixelOperationR8G8B8X8 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint8* data = buf;
r = U8ToF32(data[0]);
g = U8ToF32(data[1]);
b = U8ToF32(data[2]);
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, [[maybe_unused]] const float& a) override
{
uint8* data = buf;
data[0] = F32ToU8(r);
data[1] = F32ToU8(g);
data[2] = F32ToU8(b);
data[3] = 0xff;
}
};
//ePixelFormat_B8G8R8A8
class PixelOperationB8G8R8A8 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint8* data = buf;
r = U8ToF32(data[2]);
g = U8ToF32(data[1]);
b = U8ToF32(data[0]);
a = U8ToF32(data[3]);
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, const float& a) override
{
uint8* data = buf;
data[0] = F32ToU8(b);
data[1] = F32ToU8(g);
data[2] = F32ToU8(r);
data[3] = F32ToU8(a);
}
};
//ePixelFormat_R8G8
class PixelOperationR8G8 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint8* data = buf;
r = U8ToF32(data[0]);
g = U8ToF32(data[1]);
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
uint8* data = buf;
data[0] = F32ToU8(r);
data[1] = F32ToU8(g);
}
};
//ePixelFormat_R8
class PixelOperationR8 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint8* data = buf;
r = U8ToF32(data[0]);
g = 0.f;
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, [[maybe_unused]] const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
uint8* data = buf;
data[0] = F32ToU8(r);
}
};
//ePixelFormat_A8
class PixelOperationA8 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint8* data = buf;
a = U8ToF32(data[0]);
//save alpha information to rgb too. useful for preview.
r = a;
g = a;
b = a;
}
void SetRGBA(uint8* buf, [[maybe_unused]] const float& r, [[maybe_unused]] const float& g, [[maybe_unused]] const float& b, const float& a) override
{
uint8* data = buf;
data[0] = F32ToU8(a);
}
};
//ePixelFormat_R16G16B16A16
class PixelOperationR16G16B16A16 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint16* data = (uint16*)(buf);
r = U16ToF32(data[0]);
g = U16ToF32(data[1]);
b = U16ToF32(data[2]);
a = U16ToF32(data[3]);
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, const float& a) override
{
uint16* data = (uint16*)(buf);
data[0] = F32ToU16(r);
data[1] = F32ToU16(g);
data[2] = F32ToU16(b);
data[3] = F32ToU16(a);
}
};
//ePixelFormat_R16G16
class PixelOperationR16G16 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint16* data = (uint16*)(buf);
r = U16ToF32(data[0]);
g = U16ToF32(data[1]);
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
uint16* data = (uint16*)(buf);
data[0] = F32ToU16(r);
data[1] = F32ToU16(g);
}
};
//ePixelFormat_R16
class PixelOperationR16 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const uint16* data = (uint16*)(buf);
r = U16ToF32(data[0]);
g = 0.f;
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, [[maybe_unused]] const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
uint16* data = (uint16*)(buf);
data[0] = F32ToU16(r);
}
};
//ePixelFormat_R9G9B9E5
class PixelOperationR9G9B9E5 : public IPixelOperation
{
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const RgbE* data = (RgbE*)(buf);
data->GetRGBF(r, g, b);
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, [[maybe_unused]] const float& a) override
{
RgbE* data = (RgbE*)(buf);
data->SetRGBF(r, g, b);
}
};
//ePixelFormat_R32G32B32A32F
class PixelOperationR32G32B32A32F : public IPixelOperation
{
public:
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const float* data = (float*)(buf);
r = data[0];
g = data[1];
b = data[2];
a = data[3];
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, const float& a) override
{
float* data = (float*)(buf);
data[0] = r;
data[1] = g;
data[2] = b;
data[3] = a;
}
};
//ePixelFormat_R32G32F
class PixelOperationR32G32F : public IPixelOperation
{
public:
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const float* data = (float*)(buf);
r = data[0];
g = data[1];
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
float* data = (float*)(buf);
data[0] = r;
data[1] = g;
}
};
//ePixelFormat_R32F
class PixelOperationR32F : public IPixelOperation
{
public:
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const float* data = (float*)(buf);
r = data[0];
g = 0.f;
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, [[maybe_unused]] const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
float* data = (float*)(buf);
data[0] = r;
}
};
//ePixelFormat_R16G16B16A16F
class PixelOperationR16G16B16A16F : public IPixelOperation
{
public:
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const SHalf* data = (SHalf*)(buf);
r = data[0];
g = data[1];
b = data[2];
a = data[3];
}
void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, const float& a) override
{
SHalf* data = (SHalf*)(buf);
data[0] = SHalf(r);
data[1] = SHalf(g);
data[2] = SHalf(b);
data[3] = SHalf(a);
}
};
//ePixelFormat_R16G16F
class PixelOperationR16G16F : public IPixelOperation
{
public:
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const SHalf* data = (SHalf*)(buf);
r = data[0];
g = data[1];
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
SHalf* data = (SHalf*)(buf);
data[0] = SHalf(r);
data[1] = SHalf(g);
}
};
//ePixelFormat_R16F
class PixelOperationR16F : public IPixelOperation
{
public:
void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) override
{
const SHalf* data = (SHalf*)(buf);
r = data[0];
g = 0.f;
b = 0.f;
a = 1.f;
}
void SetRGBA(uint8* buf, const float& r, [[maybe_unused]] const float& g, [[maybe_unused]] const float& b, [[maybe_unused]] const float& a) override
{
SHalf* data = (SHalf*)(buf);
data[0] = SHalf(r);
}
};
IPixelOperationPtr CreatePixelOperation(EPixelFormat pixelFmt)
{
switch (pixelFmt)
{
case ePixelFormat_R8G8B8A8:
return AZStd::make_shared<PixelOperationR8G8B8A8>();
case ePixelFormat_R8G8B8X8:
return AZStd::make_shared<PixelOperationR8G8B8X8>();
case ePixelFormat_B8G8R8A8:
return AZStd::make_shared<PixelOperationB8G8R8A8>();
case ePixelFormat_R8G8:
return AZStd::make_shared<PixelOperationR8G8>();
case ePixelFormat_R8:
return AZStd::make_shared<PixelOperationR8>();
case ePixelFormat_A8:
return AZStd::make_shared<PixelOperationA8>();
case ePixelFormat_R16G16B16A16:
return AZStd::make_shared<PixelOperationR16G16B16A16>();
case ePixelFormat_R16G16:
return AZStd::make_shared<PixelOperationR16G16>();
case ePixelFormat_R16:
return AZStd::make_shared<PixelOperationR16>();
case ePixelFormat_R9G9B9E5:
return AZStd::make_shared<PixelOperationR9G9B9E5>();
case ePixelFormat_R32G32B32A32F:
return AZStd::make_shared<PixelOperationR32G32B32A32F>();
case ePixelFormat_R32G32F:
return AZStd::make_shared<PixelOperationR32G32F>();
case ePixelFormat_R32F:
return AZStd::make_shared<PixelOperationR32F>();
case ePixelFormat_R16G16B16A16F:
return AZStd::make_shared<PixelOperationR16G16B16A16F>();
case ePixelFormat_R16G16F:
return AZStd::make_shared<PixelOperationR16G16F>();
case ePixelFormat_R16F:
return AZStd::make_shared<PixelOperationR16F>();
default:
AZ_Assert(false, "This function should be only called for uncompressed pixel format");
break;
}
return nullptr;
}
} // namespace ImageProcessing
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <ImageProcessing/PixelFormats.h>
namespace ImageProcessing
{
class IPixelOperation
{
public:
virtual ~IPixelOperation() {}
virtual void GetRGBA(const uint8* buf, float& r, float& g, float& b, float& a) = 0;
virtual void SetRGBA(uint8* buf, const float& r, const float& g, const float& b, const float& a) = 0;
};
typedef AZStd::shared_ptr<IPixelOperation> IPixelOperationPtr;
IPixelOperationPtr CreatePixelOperation(EPixelFormat pixelFmt);
}// namespace ImageProcessing