Files
o3de/Code/Sandbox/Editor/Util/ImageUtil.cpp
T
Ken Pruiksma 2bc36aa6c6 [SPEC-5991] Removing ImageHDR.h/.cpp. These files are no longer needed and created a licensing issue. (#1420)
The only place this code was referenced was in using textured tooltips, if the texture used for that tooltip was a .hdr file. However, the few .hdr files in all of o3de are Atom material / lighting related - definitely not used for tooltips.

It's likely bitmap tooltips in general are not used at all anymore, so more code could probably be ripped out, but this solves the immediate problem without making too many changes.
2021-06-18 13:05:35 -05:00

531 lines
16 KiB
C++

/*
* 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.
// Description : Image utilities implementation.
#include "EditorDefs.h"
#include "ImageUtil.h"
// Editor
#include "Util/ImageGif.h"
#include "Util/ImageTIF.h"
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::Save(const QString& strFileName, CImageEx& inImage)
{
QImage imgBitmap;
ImageToQImage(inImage, imgBitmap);
// Explicitly set the pixels per meter in our images to a consistent default.
// The normal default is 96 pixels per inch, or 3780 pixels per meter.
// However, the Windows scaling display setting can cause these numbers to vary
// on different machines, producing output files that have slightly different
// headers from machine to machine, which often isn't desirable.
const int defaultPixelsPerMeter = 3780;
imgBitmap.setDotsPerMeterX(defaultPixelsPerMeter);
imgBitmap.setDotsPerMeterY(defaultPixelsPerMeter);
return imgBitmap.save(strFileName);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SaveBitmap(const QString& szFileName, CImageEx& inImage)
{
return Save(szFileName, inImage);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SaveJPEG(const QString& strFileName, CImageEx& inImage)
{
return Save(strFileName, inImage);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::Load(const QString& fileName, CImageEx& image)
{
QImage imgBitmap(fileName);
if (imgBitmap.isNull())
{
CLogFile::FormatLine("Invalid file: %s", fileName.toUtf8().data());
return false;
}
return QImageToImage(imgBitmap, image);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::LoadJPEG(const QString& strFileName, CImageEx& outImage)
{
return CImageUtil::Load(strFileName, outImage);
}
//===========================================================================
bool CImageUtil::LoadBmp(const QString& fileName, CImageEx& image)
{
return CImageUtil::Load(fileName, image);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SavePGM(const QString& fileName, const CImageEx& image)
{
// There are two types of PGM ("Portable Grey Map") files - "raw" (binary) and "plain" (ASCII). This function supports the "plain PGM" format.
// See http://netpbm.sourceforge.net/doc/pgm.html or https://en.wikipedia.org/wiki/Netpbm_format for the definition.
uint32 width = image.GetWidth();
uint32 height = image.GetHeight();
uint32* pixels = image.GetData();
// Create the file header.
string fileHeader;
fileHeader.Format(
// P2 = PGM header for ASCII output. (P5 is PGM header for binary output)
"P2\n"
// width and height of the image
"%d %d\n"
// The maximum grey value in the file. (i.e. the max value for any given pixel below)
"65535\n"
, width, height);
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "wt");
if (!file)
{
return false;
}
// First print the file header
fprintf(file, fileHeader.c_str());
// Then print all the pixels.
for (int32 y = 0; y < height; y++)
{
for (int32 x = 0; x < width; x++)
{
fprintf(file, "%d ", pixels[x + (y * width)]);
}
fprintf(file, "\n");
}
fclose(file);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::LoadPGM(const QString& fileName, CImageEx& image)
{
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "rt");
if (!file)
{
return false;
}
const char seps[] = " \n\t\r";
char* token;
int32 width = 0;
int32 height = 0;
int32 numColors = 1;
fseek(file, 0, SEEK_END);
int fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
char* str = new char[fileSize];
fread(str, fileSize, 1, file);
char* nextToken = nullptr;
token = azstrtok(str, 0, seps, &nextToken);
while (token != NULL && token[0] == '#')
{
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
token = azstrtok(NULL, 0, seps, &nextToken);
}
if (azstricmp(token, "P2") != 0)
{
// Bad file. not supported pgm.
delete[]str;
fclose(file);
return false;
}
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
} while (token != NULL && token[0] == '#');
width = atoi(token);
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
} while (token != NULL && token[0] == '#');
height = atoi(token);
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
} while (token != NULL && token[0] == '#');
numColors = atoi(token);
image.Allocate(width, height);
uint32* p = image.GetData();
int size = width * height;
int i = 0;
while (token != NULL && i < size)
{
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
} while (token != NULL && token[0] == '#');
*p++ = atoi(token);
i++;
}
delete[]str;
fclose(file);
// If we have 16-bit greyscale values that we're storing into 32-bit pixels, denote it with an appropriate texture type.
if (numColors > 255)
{
image.SetFormat(eTF_R16G16);
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::LoadImage(const QString& fileName, CImageEx& image, bool* pQualityLoss)
{
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
if (pQualityLoss)
{
*pQualityLoss = false;
}
_splitpath_s(fileName.toUtf8().data(), drive, dir, fname, ext);
// Only DDS has explicit sRGB flag - we'll assume by default all formats are stored in gamma space
image.SetSRGB(true);
if (azstricmp(ext, ".bmp") == 0)
{
return LoadBmp(fileName, image);
}
else if (azstricmp(ext, ".tif") == 0)
{
return CImageTIF().Load(fileName, image);
}
else if (azstricmp(ext, ".jpg") == 0)
{
if (pQualityLoss)
{
*pQualityLoss = true; // we assume JPG has quality loss
}
return LoadJPEG(fileName, image);
}
else if (azstricmp(ext, ".gif") == 0)
{
return CImageGif().Load(fileName, image);
}
else if (azstricmp(ext, ".pgm") == 0)
{
return LoadPGM(fileName, image);
}
else if (azstricmp(ext, ".png") == 0)
{
return CImageUtil::Load(fileName, image);
}
else
{
return CImageUtil::Load(fileName, image);
}
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SaveImage(const QString& fileName, CImageEx& image)
{
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
// Remove the read-only attribute so the file can be overwritten.
QFile(fileName).setPermissions(QFile::ReadUser | QFile::WriteUser);
_splitpath_s(fileName.toUtf8().data(), drive, dir, fname, ext);
if (azstricmp(ext, ".bmp") == 0)
{
return SaveBitmap(fileName, image);
}
else if (azstricmp(ext, ".jpg") == 0)
{
return SaveJPEG(fileName, image);
}
else if (azstricmp(ext, ".pgm") == 0)
{
return SavePGM(fileName, image);
}
else
{
return Save(fileName, image);
}
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::ScaleToFit(const CByteImage& srcImage, CByteImage& trgImage)
{
trgImage.ScaleToFit(srcImage);
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::DownScaleSquareTextureTwice(const CImageEx& srcImage, CImageEx& trgImage, IImageUtil::_EAddrMode eAddressingMode)
{
uint32* pSrcData = srcImage.GetData();
int nSrcWidth = srcImage.GetWidth();
int nSrcHeight = srcImage.GetHeight();
int nTrgWidth = srcImage.GetWidth() >> 1;
int nTrgHeight = srcImage.GetHeight() >> 1;
// reallocate target
trgImage.Release();
trgImage.Allocate(nTrgWidth, nTrgHeight);
uint32* pDstData = trgImage.GetData();
// values in this filter are the log2 of the actual multiplicative values .. see DXCFILTER_BLUR3X3 for the used 3x3 filter
static int filter[3][3] =
{
{0, 1, 0},
{1, 2, 1},
{0, 1, 0}
};
for (int i = 0; i < nTrgHeight; i++)
{
for (int j = 0; j < nTrgWidth; j++)
{
// filter3x3
int x = j << 1;
int y = i << 1;
int r, g, b, a;
r = b = g = a = 0;
uint32 col;
if (eAddressingMode == IImageUtil::WRAP) // TODO: this condition could be compile-time static by making it a template arg
{
for (int ii = 0; ii < 3; ii++)
{
for (int jj = 0; jj < 3; jj++)
{
col = pSrcData[((y + nSrcHeight + ii - 1) % nSrcHeight) * nSrcWidth + ((x + nSrcWidth + jj - 1) % nSrcWidth)];
r += (col & 0xff) << filter[ii][jj];
g += ((col >> 8) & 0xff) << filter[ii][jj];
b += ((col >> 16) & 0xff) << filter[ii][jj];
a += ((col >> 24) & 0xff) << filter[ii][jj];
}
}
}
else
{
assert(eAddressingMode == IImageUtil::CLAMP);
for (int ii = 0; ii < 3; ii++)
{
for (int jj = 0; jj < 3; jj++)
{
int x1 = clamp_tpl<int>((x + jj), 0, nSrcWidth - 1);
int y1 = clamp_tpl<int>((y + ii), 0, nSrcHeight - 1);
col = pSrcData[ y1 * nSrcWidth + x1];
r += (col & 0xff) << filter[ii][jj];
g += ((col >> 8) & 0xff) << filter[ii][jj];
b += ((col >> 16) & 0xff) << filter[ii][jj];
a += ((col >> 24) & 0xff) << filter[ii][jj];
}
}
}
// the sum of the multiplicative values here is 16 so we shift by 4 bits
r >>= 4;
g >>= 4;
b >>= 4;
a >>= 4;
uint32 res = r + (g << 8) + (b << 16) + (a << 24);
*pDstData++ = res;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::ScaleToFit(const CImageEx& srcImage, CImageEx& trgImage)
{
trgImage.ScaleToFit(srcImage);
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::ScaleToDoubleFit(const CImageEx& srcImage, CImageEx& trgImage)
{
uint32 x, y, u, v;
unsigned int* destRow, * dest, * src, * sourceRow;
uint32 srcW = srcImage.GetWidth();
uint32 srcH = srcImage.GetHeight();
uint32 trgHalfW = trgImage.GetWidth() / 2;
uint32 trgH = trgImage.GetHeight();
uint32 xratio = trgHalfW > 0 ? (srcW << 16) / trgHalfW : 1;
uint32 yratio = trgH > 0 ? (srcH << 16) / trgH : 1;
src = srcImage.GetData();
destRow = trgImage.GetData();
v = 0;
for (y = 0; y < trgH; y++)
{
u = 0;
sourceRow = src + (v >> 16) * srcW;
dest = destRow;
for (x = 0; x < trgHalfW; x++)
{
*(dest + trgHalfW) = sourceRow[u >> 16];
*dest++ = sourceRow[u >> 16];
u += xratio;
}
v += yratio;
destRow += trgHalfW * 2;
}
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::SmoothImage(CByteImage& image, int numSteps)
{
assert(numSteps > 0);
uint8* buf = image.GetData();
int w = image.GetWidth();
int h = image.GetHeight();
for (int steps = 0; steps < numSteps; steps++)
{
// Smooth the image.
for (int y = 1; y < h - 1; y++)
{
// Precalculate for better speed
uint8* ptr = &buf[y * w + 1];
for (int x = 1; x < w - 1; x++)
{
// Smooth it out
*ptr =
(
(uint32)ptr[1] +
ptr[w] +
ptr[-1] +
ptr[-w] +
ptr[w + 1] +
ptr[w - 1] +
ptr[-w + 1] +
ptr[-w - 1]
) >> 3;
// Next pixel
ptr++;
}
}
}
}
unsigned char CImageUtil::GetBilinearFilteredAt(const int iniX256, const int iniY256, const CByteImage& image)
{
// assert(image.IsValid()); if(!image.IsValid())return(0); // this shouldn't be
DWORD x = (DWORD)(iniX256) >> 8;
DWORD y = (DWORD)(iniY256) >> 8;
if (x >= image.GetWidth() - 1 || y >= image.GetHeight() - 1)
{
return image.ValueAt(x, y); // border is not filtered, 255 to get in range 0..1
}
DWORD rx = (DWORD)(iniX256) & 0xff; // fractional aprt
DWORD ry = (DWORD)(iniY256) & 0xff; // fractional aprt
DWORD top = (DWORD)image.ValueAt((int)x, (int)y) * (256 - rx) // left top
+ (DWORD)image.ValueAt((int)x + 1, (int)y) * rx; // right top
DWORD bottom = (DWORD)image.ValueAt((int)x, (int)y + 1) * (256 - rx) // left bottom
+ (DWORD)image.ValueAt((int)x + 1, (int)y + 1) * rx; // right bottom
return (unsigned char)((top * (256 - ry) + bottom * ry) >> 16);
}
bool CImageUtil::QImageToImage(const QImage& bitmap, CImageEx& image)
{
QImage convertedBitmap;
const QImage *srcBitmap = &bitmap;
if (bitmap.format() != QImage::Format_RGBA8888)
{
convertedBitmap = bitmap.convertToFormat(QImage::Format_RGBA8888);
srcBitmap = &convertedBitmap;
}
if (image.Allocate(srcBitmap->width(), srcBitmap->height()) == false)
{
return false;
}
AZStd::copy(srcBitmap->bits(), srcBitmap->bits() + (srcBitmap->width() * srcBitmap->height() * sizeof(uint32)), reinterpret_cast<uint8*>(image.GetData()));
return true;
}
bool CImageUtil::ImageToQImage(const CImageEx& image, QImage& bitmapObj)
{
bitmapObj = QImage(image.GetWidth(), image.GetHeight(), QImage::Format_RGBA8888);
AZStd::copy(image.GetData(), image.GetData() + image.GetWidth() * image.GetHeight(), reinterpret_cast<uint32*>(bitmapObj.bits()));
return true;
}