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,227 @@
/*
* 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 "ImageLoaders.h"
#include <ImageProcessing/ImageObject.h>
#include <AzCore/IO/FileIO.h>
#include <limits>
namespace
{
//---------------------------------------------------------------------------
// Load and save the VTP Binary Terrain (BT) format, documented here:
// http://vterrain.org/Implementation/Formats/BT.html
// This structure represents a binary layout in the file. To direct load & save it, we need to remove all structure memory padding
#pragma pack(push,1)
struct BtHeader
{
char headerTag[7]; // Should be "binterr"
char headerTagVersion[3]; // Should be "1.3"
AZ::s32 columns; // # of columns in the heightfield
AZ::s32 rows; // # of rows in the heightfield
AZ::s16 bytesPerPoint; // bytes per height value, either 2 for signed ints or 4 for floats
AZ::s16 isFloatingPointData; // 1 if height values are floats, 0 for 16-bit signed ints
AZ::s16 horizUnits; // 0 if degrees, 1 if meters, 2 if international feet, 3 if US survey feet
AZ::s16 utmZone; // UTM projection zone 1 to 60 or -1 to -60 (see https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system )
AZ::s16 datum; // Datum value (6001 to 6094), see http://www.epsg.org/
double leftExtent; // left coordinate projection of the file
double rightExtent; // right coordinate projection of the file
double bottomExtent; // bottom coordinate projection of the file
double topExtent; // top coordinate projection of the file
AZ::s16 externalProjection; // 1 if projection is in an external .prj file, 0 if it's contained in the header
float scale; // vertical units in meters. 0.0 should be treated as 1.0
char unused[190];
};
#pragma pack(pop)
AZStd::vector<char> LoadFile(const AZStd::string& fileName)
{
AZ::IO::FileIOBase* fileReader = AZ::IO::FileIOBase::GetInstance();
if (!fileReader)
{
return {};
}
// an engine compatible file reader has been attached, so use that.
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ::u64 fileSize = 0;
if (!fileReader->Open(fileName.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, fileHandle))
{
return {};
}
if ((!fileReader->Size(fileHandle, fileSize)) || (fileSize == 0))
{
fileReader->Close(fileHandle);
return {};
}
AZStd::vector<char> fileBuf(fileSize);
if (!fileReader->Read(fileHandle, fileBuf.data(), fileSize, true))
{
fileReader->Close(fileHandle);
return {};
}
fileReader->Close(fileHandle);
return fileBuf;
}
bool IsHeaderValid(const BtHeader* header, std::size_t fileSize)
{
bool validData = true;
// Do some quick error-checking on the header to make sure it meets our expectations
// Does the header have the right header tag? (binterr1.0 - binterr1.3)
validData = validData && (memcmp(header->headerTag, "binterr", sizeof(header->headerTag)) == 0);
validData = validData && (header->headerTagVersion[0] == '1') && (header->headerTagVersion[1] == '.') && (header->headerTagVersion[2] >= '0') && (header->headerTagVersion[2] <= '3');
// Will the grid fit into a reasonable image size?
validData = validData && (header->columns >= 0) && (header->columns < 65536);
validData = validData && (header->rows >= 0) && (header->rows < 65536);
// Do we either have 32-bit floats or 16-bit ints?
validData = validData && (((header->isFloatingPointData == 1) && (header->bytesPerPoint == 4)) || ((header->isFloatingPointData == 0) && (header->bytesPerPoint == 2)));
// Is the remaining data exactly the size needed to fill our image?
AZ::s32 total = header->columns * header->rows * header->bytesPerPoint;
validData = validData && ((fileSize - sizeof(BtHeader)) == total);
return validData;
}
}
namespace ImageProcessing
{
bool BTLoader::IsExtensionSupported(const char* extension)
{
return strcmp(extension, "bt") == 0;
}
/*
Most of the logic here was taken from ImageBT.cpp. Please make sure
any changes are kept in sync :)
*/
IImageObject* BTLoader::LoadImageFromBT(const AZStd::string& fileName)
{
auto fileData = LoadFile(fileName);
if (fileData.size() < sizeof(BtHeader))
{
return nullptr;
}
auto header = reinterpret_cast<BtHeader*>(fileData.data());
if (!header || !IsHeaderValid(header, fileData.size()))
{
return nullptr;
}
if (header->scale == 0.0f)
{
header->scale = 1.0f;
}
// The BT format defines the data as stored in column-first order, from bottom to top.
// However, some BT files store the data in row-first order, from top to bottom.
// There isn't anything that clearly specifies which type of file it is. If you load it the wrong way,
// the data will look like a bunch of wavy stripes.
// The only difference I've found in test files is datum values above 8000, which appears to be an invalid value for datum
// (it should be 6001-6904 according to the BT definition)
constexpr AZ::s32 invalidDatumValueDenotingColumnFirstData = 8000;
bool isColumnFirstData = (header->datum >= invalidDatumValueDenotingColumnFirstData) ? true : false;
AZ::s32 imageWidth = 0;
AZ::s32 imageHeight = 0;
if (isColumnFirstData)
{
imageWidth = header->rows;
imageHeight = header->columns;
}
else
{
imageWidth = header->columns;
imageHeight = header->rows;
}
IImageObject* image = IImageObject::CreateImage(imageWidth, imageHeight, 1, EPixelFormat::ePixelFormat_R32F);
AZ::u8* p = nullptr;
AZ::u32 dwPitch = 0;
image->GetImagePointer(0, p, dwPitch);
auto dst = reinterpret_cast<float*>(p);
auto maxPixel = std::numeric_limits<float>::lowest();
auto minPixel = std::numeric_limits<float>::max();
auto terrainData = reinterpret_cast<const AZ::u8*>(header + 1);
// Read in the pixel data
if (header->isFloatingPointData)
{
for (AZ::s32 y = 0; y < imageHeight; ++y)
{
for (AZ::s32 x = 0; x < imageWidth; ++x)
{
float height = *reinterpret_cast<const float*>(terrainData);
terrainData += sizeof(float);
// Scale based on what our header defines
float setVal = dst[(y * imageWidth) + x] = height * header->scale;
maxPixel = AZStd::max(maxPixel, setVal);
minPixel = AZStd::min(minPixel, setVal);
}
}
}
else
{
for (AZ::s32 y = 0; y < imageHeight; ++y)
{
for (AZ::s32 x = 0; x < imageWidth; ++x)
{
float height = static_cast<float>(*reinterpret_cast<const AZ::u16*>(terrainData));
terrainData += sizeof(AZ::s16);
// Scale based on what our header defines
float setVal = dst[(y * imageWidth) + x] = height * header->scale;
maxPixel = AZStd::max(maxPixel, setVal);
minPixel = AZStd::min(minPixel, setVal);
}
}
}
// Scale our range down to 0 - 1
auto diff = maxPixel - minPixel;
if (AZ::GetAbs(diff) < std::numeric_limits<float>::epsilon())
{
diff = 1.0f;
}
auto imagePixels = imageWidth * imageHeight;
for (AZ::s32 i = 0; i < imagePixels; ++i)
{
dst[i] = (dst[i] - minPixel) / diff;
}
return image;
}
}
@@ -0,0 +1,72 @@
/*
* 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 <ImageLoader/ImageLoaders.h>
#include <ImageProcessing/ImageObject.h>
#include <QFileInfo>
namespace ImageProcessing
{
IImageObject* LoadImageFromFile(const AZStd::string& filename)
{
QFileInfo fileInfo(filename.c_str());
QString ext = fileInfo.suffix();
if (TIFFLoader::IsExtensionSupported(ext.toUtf8()))
{
return TIFFLoader::LoadImageFromTIFF(filename);
}
else if (BTLoader::IsExtensionSupported(ext.toUtf8()))
{
return BTLoader::LoadImageFromBT(filename);
}
else if (QtImageLoader::IsExtensionSupported(ext.toUtf8()))
{
return QtImageLoader::LoadImageFromFile(filename);
}
AZ_Warning("ImageProcessing", false, "No proper image loader to load file: %s", filename.c_str());
return nullptr;
}
bool IsExtensionSupported(const char* extension)
{
if (TIFFLoader::IsExtensionSupported(extension))
{
return true;
}
else if (BTLoader::IsExtensionSupported(extension))
{
return true;
}
else if (QtImageLoader::IsExtensionSupported(extension))
{
return true;
}
return false;
}
const AZStd::string LoadEmbeddedSettingFromFile(const AZStd::string& filename)
{
QFileInfo fileInfo(filename.c_str());
QString ext = fileInfo.suffix();
if (TIFFLoader::IsExtensionSupported(ext.toUtf8()))
{
return TIFFLoader::LoadSettingFromTIFF(filename);
}
return "";
}
}// namespace ImageProcessing
@@ -0,0 +1,51 @@
/*
* 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 <AzCore/std/string/string.h>
namespace ImageProcessing
{
class IImageObject;
IImageObject* LoadImageFromFile(const AZStd::string& filename);
bool IsExtensionSupported(const char* extension);
const AZStd::string LoadEmbeddedSettingFromFile(const AZStd::string& filename);
// Tiff loader. The loader support uncompressed tiff with with 1~4 channels and 8bit and 16bit uint or 16bits and 32bits float per channel
// QImage also support tiff (tiff plugin), but it only supports 8bits uint
namespace TIFFLoader
{
bool IsExtensionSupported(const char* extension);
// Load a tiff file to an image object.
IImageObject* LoadImageFromTIFF(const AZStd::string& filename);
// Load embedded .exportsettings string from tiff which was exported by deprecated feature of CryTif plugin.
const AZStd::string LoadSettingFromTIFF(const AZStd::string& filename);
};// namespace ImageTIFF
namespace BTLoader
{
bool IsExtensionSupported(const char* extension);
// Load a BT file to an image object.
IImageObject* LoadImageFromBT(const AZStd::string& fileName);
}// namespace BTLoader
// Image loader through Qt's QImage with image formats supported native and through plugins
namespace QtImageLoader
{
bool IsExtensionSupported(const char* extension);
// Load image file which supported by QtImage to an image object
IImageObject* LoadImageFromFile(const AZStd::string& filename);
};// namespace QtImageLoader
}// namespace ImageProcessing
@@ -0,0 +1,77 @@
/*
* 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 <ImageLoader/ImageLoaders.h>
#include <ImageProcessing/ImageObject.h>
#include <QImage>
#include <QImageReader>
///////////////////////////////////////////////////////////////////////////////////
namespace ImageProcessing
{
namespace QtImageLoader
{
IImageObject* LoadImageFromFile(const AZStd::string& filename)
{
//try to open the image
QImage qimage(filename.c_str());
if (qimage.isNull())
{
return NULL;
}
//convert to format which compatiable our pixel format
QImage::Format format = qimage.format();
if (qimage.format() != QImage::Format_RGBA8888)
{
qimage = qimage.convertToFormat(QImage::Format_RGBA8888);
}
//create a new image object
IImageObject *pImage = IImageObject::CreateImage(qimage.width(), qimage.height(), 1,
ePixelFormat_R8G8B8A8);
//get a pointer to the image objects pixel data
uint8* pDst;
uint32 dwPitch;
pImage->GetImagePointer(0, pDst, dwPitch);
//copy the qImage into the image object
for (uint32 dwY = 0; dwY < (uint32)qimage.height(); ++dwY)
{
uint8* dstLine = &pDst[dwPitch * dwY];
uchar* srcLine = qimage.scanLine(dwY);
memcpy(dstLine, srcLine, dwPitch);
}
return pImage;
}
bool IsExtensionSupported(const char* extension)
{
QList<QByteArray> imgFormats = QImageReader::supportedImageFormats();
for (int i = 0; i < imgFormats.size(); ++i)
{
if (QString::fromUtf8(imgFormats[i]).toLower() == QString(extension).toLower())
{
return true;
}
}
return false;
}
}//namespace QtImageLoader
} //namespace ImageProcessing
@@ -0,0 +1,660 @@
/*
* 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/Debug/Trace.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/Casting/numeric_cast.h>
#include <ImageLoader/ImageLoaders.h>
#include <ImageProcessing/ImageObject.h>
#include <QString>
#include <libtiff/tiffio.h> // TIFF library
namespace ImageProcessing
{
namespace TIFFLoader
{
class TiffFileRead
{
public:
TiffFileRead(const AZStd::string& filename)
: m_tif(nullptr)
{
m_tif = TIFFOpen(filename.c_str(), "r");;
}
~TiffFileRead()
{
if (m_tif != nullptr)
{
TIFFClose(m_tif);
}
}
TIFF *GetTiff()
{
return m_tif;
}
private:
TIFF *m_tif;
};
bool IsExtensionSupported(const char* extension)
{
QString ext = QString(extension).toLower();
// This is the list of file extensions supported by this loader
return ext == "tif" || ext == "tiff";
}
struct TiffData
{
AZ::u32 m_channels = 0;
AZ::u32 m_photometric = 0;
AZ::u32 m_bitsPerPixel = 0;
AZ::u16 m_format = 0;
AZ::u32 m_width = 0;
AZ::u32 m_height = 0;
AZ::u32 m_tileWidth = 0;
AZ::u32 m_tileHeight = 0;
bool m_isTiled = false;
AZ::u32 m_bufSize = 0;
bool m_isGeoTiff = false;
float m_pixelValueScale = 1.0f;
EPixelFormat m_pixelFormat = EPixelFormat::ePixelFormat_Unknown;
};
static void Process8BitTiff(AZ::u8* dst, const AZ::u8* src, AZ::u32 destIdx, AZ::u32 srcIdx, const TiffData& data, AZ::u8& dstMult)
{
if (data.m_channels == 1)
{
if (data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
dst[destIdx] = aznumeric_cast<AZ::u8>(src[srcIdx] * data.m_pixelValueScale);
}
else
{
dst[destIdx] = aznumeric_cast<AZ::u8>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u8>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 2] = aznumeric_cast<AZ::u8>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 3] = 0xFF;
dstMult = 4;
}
}
else if (data.m_channels == 2)
{
if (data.m_photometric == PHOTOMETRIC_SEPARATED)
{
// convert CMY to RGB (PHOTOMETRIC_SEPARATED refers to inks in TIFF, the value is inverted)
dst[destIdx] = aznumeric_cast<AZ::u8>(0xFF - src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u8>(0xFF - src[srcIdx + 1] * data.m_pixelValueScale);
dst[destIdx + 2] = 0x00;
dst[destIdx + 3] = 0xFF;
dstMult = 4;
}
else
{
dst[destIdx] = aznumeric_cast<AZ::u8>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u8>(src[srcIdx + 1] * data.m_pixelValueScale);
dstMult = 2;
}
}
else
{
dst[destIdx] = aznumeric_cast<AZ::u8>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u8>(src[srcIdx + 1] * data.m_pixelValueScale);
dst[destIdx + 2] = aznumeric_cast<AZ::u8>(src[srcIdx + 2] * data.m_pixelValueScale);
dst[destIdx + 3] = (data.m_channels == 3) ? 0xFF : aznumeric_cast<AZ::u8>(src[srcIdx + 3] * data.m_pixelValueScale);
dstMult = 4;
}
}
static void Process16BitHDRTiff(AZ::s16* dst, const AZ::s16* src, AZ::u32 destIdx, AZ::u32 srcIdx, const TiffData& data, AZ::u8& dstMult)
{
if (data.m_channels == 1)
{
if (data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
dst[destIdx] = aznumeric_cast<AZ::s16>(src[srcIdx] * data.m_pixelValueScale);
}
else
{
dst[destIdx] = aznumeric_cast<AZ::s16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::s16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 2] = aznumeric_cast<AZ::s16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 3] = 1;
dstMult = 4;
}
}
else if (data.m_channels == 2)
{
if (data.m_photometric == PHOTOMETRIC_SEPARATED)
{
//but convert CMY to RGB (PHOTOMETRIC_SEPARATED refers to inks in TIFF, the value is inverted)
dst[destIdx] = uint16(1.0f - src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = uint16(1.0f - src[srcIdx + 1] * data.m_pixelValueScale);
dst[destIdx + 2] = 0;
dst[destIdx + 3] = 1;
dstMult = 4;
}
else
{
dst[destIdx] = aznumeric_cast<AZ::s16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::s16>(src[srcIdx + 1] * data.m_pixelValueScale);
dstMult = 2;
}
}
else
{
dst[destIdx] = aznumeric_cast<AZ::s16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::s16>(src[srcIdx + 1] * data.m_pixelValueScale);
dst[destIdx + 2] = aznumeric_cast<AZ::s16>(src[srcIdx + 2] * data.m_pixelValueScale);
dst[destIdx + 3] = (data.m_channels == 3) ? 1 : aznumeric_cast<AZ::s16>(src[srcIdx + 3] * data.m_pixelValueScale);
dstMult = 4;
}
}
static void Process16BitTiff(AZ::u16* dst, const AZ::u16* src, AZ::u32 destIdx, AZ::u32 srcIdx, const TiffData& data, AZ::u8& dstMult)
{
if (data.m_channels == 1)
{
if (data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
dst[destIdx] = aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
}
else
{
dst[destIdx] = aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 2] = aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 3] = 0xFFFF;
dstMult = 4;
}
}
else if (data.m_channels == 2)
{
if (data.m_photometric == PHOTOMETRIC_SEPARATED)
{
//convert CMY to RGB (PHOTOMETRIC_SEPARATED refers to inks in TIFF, the value is inverted)
dst[destIdx] = 0xFFFF - aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = 0xFFFF - aznumeric_cast<AZ::u16>(src[srcIdx + 1] * data.m_pixelValueScale);
dst[destIdx + 2] = 0x0000;
dst[destIdx + 3] = 0xFFFF;
dstMult = 4;
}
else
{
dst[destIdx] = aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u16>(src[srcIdx + 1] * data.m_pixelValueScale);
dstMult = 2;
}
}
else
{
dst[destIdx] = aznumeric_cast<AZ::u16>(src[srcIdx] * data.m_pixelValueScale);
dst[destIdx + 1] = aznumeric_cast<AZ::u16>(src[srcIdx + 1] * data.m_pixelValueScale);
dst[destIdx + 2] = aznumeric_cast<AZ::u16>(src[srcIdx + 2] * data.m_pixelValueScale);
dst[destIdx + 3] = (data.m_channels == 3) ? 0xFFFF : aznumeric_cast<AZ::u16>(src[srcIdx + 3] * data.m_pixelValueScale);
dstMult = 4;
}
}
static void Process32BitHDRTiff(float* dst, const float* src, AZ::u32 destIdx, AZ::u32 srcIdx, const TiffData& data, AZ::u8& dstMult)
{
auto getScaledOrClamped = [&data](auto val)
{
// GeoTiff doesn't clamp because negative values are legitimate when the data represents height values below sea level.
return data.m_isGeoTiff ? (val * data.m_pixelValueScale) : AZ::GetMax(val, 0.0f);
};
if (data.m_channels == 1)
{
if (data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
// clamp negative values
const float v = getScaledOrClamped(src[srcIdx]);
dst[destIdx] = v;
}
else
{
// clamp negative values
const float v = getScaledOrClamped(src[srcIdx]);
dst[destIdx] = v;
dst[destIdx + 1] = v;
dst[destIdx + 2] = v;
dst[destIdx + 3] = 1.0f;
dstMult = 4;
}
}
else if (data.m_channels == 2)
{
if (data.m_photometric == PHOTOMETRIC_SEPARATED)
{
//convert CMY to RGB (PHOTOMETRIC_SEPARATED refers to inks in TIFF, the value is inverted)
dst[destIdx] = 1.0f - getScaledOrClamped(src[srcIdx]);
dst[destIdx + 1] = 1.0f - getScaledOrClamped(src[srcIdx + 1]);
dst[destIdx + 2] = 0.0f;
dst[destIdx + 3] = 1.0f;
dstMult = 4;
}
else
{
dst[destIdx] = src[srcIdx] * data.m_pixelValueScale;
dst[destIdx + 1] = src[srcIdx + 1] * data.m_pixelValueScale;
dstMult = 2;
}
}
else
{
// clamp negative values; don't swap red and blue -> RGB(A)
dst[destIdx] = getScaledOrClamped(src[srcIdx]);
dst[destIdx + 1] = getScaledOrClamped(src[srcIdx + 1]);
dst[destIdx + 2] = getScaledOrClamped(src[srcIdx + 2]);
dst[destIdx + 3] = (data.m_channels == 3) ? 1.0f : getScaledOrClamped(src[srcIdx + 3]) * data.m_pixelValueScale;
dstMult = 4;
}
}
static TiffData GetTiffData(TIFF* tif)
{
TiffData data;
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &data.m_channels);
TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &data.m_photometric);
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &data.m_bitsPerPixel);
TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &data.m_format);
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &data.m_width);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &data.m_height);
TIFFGetField(tif, TIFFTAG_TILEWIDTH, &data.m_tileWidth);
TIFFGetField(tif, TIFFTAG_TILELENGTH, &data.m_tileHeight);
// Check to see if this is a tiled TIFF (vs a scanline-based TIFF)
if ((data.m_tileWidth > 0) && (data.m_tileHeight > 0))
{
// Tiled TIFF, so our buffer needs to be tile-sized
data.m_isTiled = true;
data.m_bufSize = TIFFTileSize(tif);
}
else
{
// Scanline TIFF, so our buffer needs to be scanline-sized.
data.m_bufSize = TIFFScanlineSize(tif);
// Treat scanlines like a tile of 1 x width size.
data.m_tileHeight = 1;
data.m_tileWidth = data.m_width;
}
// Defined in GeoTIFF format - http://web.archive.org/web/20160403164508/http://www.remotesensing.org/geotiff/spec/geotiffhome.html
// Used to get the X, Y, Z scales from a GeoTIFF file
constexpr auto GEOTIFF_MODELPIXELSCALE_TAG = 33550;
// Check to see if it's a GeoTIFF, and if so, whether or not it has the ZScale parameter.
AZ::u32 tagCount = 0;
double* pixelScales = nullptr;
if (TIFFGetField(tif, GEOTIFF_MODELPIXELSCALE_TAG, &tagCount, &pixelScales) == 1)
{
data.m_isGeoTiff = true;
// if there's an xyz scale, and the Z scale isn't 0, let's use it.
if ((tagCount == 3) && (pixelScales != nullptr) && (pixelScales[2] != 0.0f))
{
data.m_pixelValueScale = static_cast<float>(pixelScales[2]);
}
}
// Retrieve the pixel format of the image
switch (data.m_bitsPerPixel)
{
case 8:
{
data.m_pixelFormat = ePixelFormat_R8G8B8X8;
if (data.m_channels == 1 && data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
data.m_pixelFormat = ePixelFormat_R8;
}
else if (data.m_channels == 4)
{
data.m_pixelFormat = ePixelFormat_R8G8B8A8;
}
break;
}
case 16:
{
if (data.m_format == SAMPLEFORMAT_IEEEFP)
{
data.m_pixelFormat = ePixelFormat_R16G16B16A16F;
if (data.m_channels == 1 && data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
data.m_pixelFormat = ePixelFormat_R16F;
}
}
else
{
data.m_pixelFormat = ePixelFormat_R16G16B16A16;
if (data.m_channels == 1 && data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
data.m_pixelFormat = ePixelFormat_R16;
}
}
break;
}
case 32:
{
if (data.m_format == SAMPLEFORMAT_IEEEFP)
{
data.m_pixelFormat = ePixelFormat_R32G32B32A32F;
if (data.m_channels == 1 && data.m_photometric != PHOTOMETRIC_MINISBLACK)
{
data.m_pixelFormat = ePixelFormat_R32F;
}
}
break;
}
default:
break;
}
return data;
}
static IImageObject* LoadTIFF(TIFF* tif)
{
TiffData data = GetTiffData(tif);
AZStd::unique_ptr<IImageObject> destImageObject;
destImageObject.reset(IImageObject::CreateImage(data.m_width, data.m_height,
1, data.m_pixelFormat));
uint8* dst;
uint32 pitch;
destImageObject->GetImagePointer(0, dst, pitch);
AZStd::vector<AZ::u8> buf(data.m_bufSize);
AZ::u8 dstMult = 1;
// Loop across the image height, one tile at a time
for (AZ::u32 imageY = 0; imageY < data.m_height; imageY += data.m_tileHeight)
{
// If we aren't actually tiled, we'll need to read a scanline here
if (!data.m_isTiled)
{
if (TIFFReadScanline(tif, buf.data(), imageY) == -1)
{
AZ_Error("LoadTIFF", false, "Error reading scanline.");
return nullptr;
}
}
// Loop across the image width, one tile at a time
for (AZ::u32 imageX = 0; imageX < data.m_width; imageX += data.m_tileWidth)
{
// If we *are* tiled, read in a new tile here
if (data.m_isTiled)
{
if (TIFFReadTile(tif, buf.data(), imageX, imageY, 0, 0) == -1)
{
AZ_Error("LoadTIFF", false, "Error reading tile.");
return nullptr;
}
}
// For each pixel in the tile buffer, read it in and convert.
for (AZ::u32 tileY = 0; tileY < data.m_tileHeight; ++tileY)
{
for (AZ::u32 tileX = 0; tileX < data.m_tileWidth; ++tileX)
{
AZ::u32 srcIdx = ((tileY * data.m_tileWidth) + tileX) * data.m_channels;
AZ::u32 destIdx = (((imageY + tileY) * data.m_width) + (imageX + tileX)) * dstMult;
switch (data.m_bitsPerPixel)
{
case 8:
Process8BitTiff(dst, buf.data(), destIdx, srcIdx, data, dstMult);
break;
case 16:
{
switch (data.m_format)
{
case SAMPLEFORMAT_INT:
case SAMPLEFORMAT_IEEEFP:
Process16BitHDRTiff(reinterpret_cast<AZ::s16*>(dst), reinterpret_cast<AZ::s16*>(buf.data()),
destIdx, srcIdx, data, dstMult);
break;
default:
Process16BitTiff(reinterpret_cast<AZ::u16*>(dst), reinterpret_cast<AZ::u16*>(buf.data()),
destIdx, srcIdx, data, dstMult);
break;
}
break;
}
case 32:
if (data.m_format == SAMPLEFORMAT_IEEEFP)
{
Process32BitHDRTiff(reinterpret_cast<float*>(dst), reinterpret_cast<float*>(buf.data()),
destIdx, srcIdx, data, dstMult);
}
else
{
AZ_Error("LoadTIFF", false, "Unknown / unsupported format.");
return nullptr;
}
break;
default:
AZ_Error("LoadTIFF", false, "Unknown / unsupported format.");
return nullptr;
}
}
}
}
}
return destImageObject.release();
}
IImageObject* LoadImageFromTIFF(const AZStd::string& filename)
{
TiffFileRead tiffRead(filename);
TIFF* tif = tiffRead.GetTiff();
IImageObject* destImageObject = nullptr;
if (!tif)
{
AZ_Warning("Image Processing", false, "%s: Open tiff failed (%s)", __FUNCTION__, filename.c_str());
return destImageObject;
}
uint32 bitsPerChannel = 0;
uint32 channels = 0;
uint32 format = 0;
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitsPerChannel);
TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &format);
if (channels != 1 && channels != 2 && channels != 3 && channels != 4)
{
AZ_Warning("Image Processing", false, "Unsupported TIFF pixel format (channel count: %d)", channels);
return destImageObject;
}
uint32 width = 0;
uint32 height = 0;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
if (width <= 0 || height <= 0)
{
AZ_Error("Image Processing", false, "%s failed (empty image)", __FUNCTION__);
return destImageObject;
}
destImageObject = LoadTIFF(tif);
if (destImageObject == nullptr)
{
AZ_Error("Image Processing", false, "Failed to read TIFF pixels");
}
return destImageObject;
}
const AZStd::string LoadSettingFromTIFF(const AZStd::string& filename)
{
AZStd::string setting = "";
TiffFileRead tiffRead(filename);
TIFF* tif = tiffRead.GetTiff();
if (tif == nullptr)
{
return setting;
}
// get image metadata
const unsigned char* buffer = nullptr;
unsigned int bufferLength = 0;
if (!TIFFGetField(tif, TIFFTAG_PHOTOSHOP, &bufferLength, &buffer)) // 34377 IPTC TAG
{
return setting;
}
const unsigned char* const bufferEnd = buffer + bufferLength;
// detailed structure here:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504
while (buffer < bufferEnd)
{
const unsigned char* const bufferStart = buffer;
// sanity check
if (buffer[0] != '8' || buffer[1] != 'B' || buffer[2] != 'I' || buffer[3] != 'M')
{
AZ_Warning("Image Processing", false, "Invalid Photoshop TIFF file [%s]!", filename.c_str());
return setting;
}
buffer += 4;
// get image resource id
const unsigned short resourceId = (((unsigned short)buffer[0]) << 8) | (unsigned short)buffer[1];
buffer += 2;
// get size of pascal string
const unsigned int nameSize = (unsigned int)buffer[0];
++buffer;
// get pascal string
AZStd::string szName(buffer, buffer + nameSize);
buffer += nameSize;
// align 2 bytes
if ((buffer - bufferStart) & 1)
{
++buffer;
}
// get size of resource data
const unsigned int resDataSize =
(((unsigned int)buffer[0]) << 24) |
(((unsigned int)buffer[1]) << 16) |
(((unsigned int)buffer[2]) << 8) |
(unsigned int)buffer[3];
buffer += 4;
// IPTC-NAA record. Contains the [File Info...] information. Old RC use this section to store the setting string.
if (resourceId == 0x0404)
{
const unsigned char* const iptcBufferStart = buffer;
// Old RC uses IPTC ApplicationRecord tags SpecialInstructions to store the setting string
// IPTC Details: https://iptc.org/std/photometadata/specification/mapping/iptc-pmd-newsmlg2.html
unsigned int iptcPos = 0;
while (iptcPos + 5 < resDataSize)
{
int marker = iptcBufferStart[iptcPos++];
int recordNumber = iptcBufferStart[iptcPos++];
int dataSetNumber = iptcBufferStart[iptcPos++];
int fieldLength = (iptcBufferStart[iptcPos++] << 8);
fieldLength += iptcBufferStart[iptcPos++];
// Ignore fields other than SpecialInstructions
if (marker != 0x1C || recordNumber != 0x02 || dataSetNumber != 0x28 )
{
iptcPos += fieldLength;
continue;
}
//save the setting string before close file
setting = AZStd::string(iptcBufferStart + iptcPos, iptcBufferStart + iptcPos + fieldLength);
return setting;
}
}
buffer += resDataSize;
// align 2 bytes
if ((buffer - bufferStart) & 1)
{
++buffer;
}
}
return setting;
}
}// namespace ImageTIFF
} //namespace ImageProcessing