You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Editor/Util/ImageTIF.cpp

499 lines
17 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageTIF.h"
/// libTiff
#include <tiffio.h> // TIFF library
// Function prototypes
static tsize_t libtiffDummyReadProc (thandle_t fd, tdata_t buf, tsize_t size);
static tsize_t libtiffDummyWriteProc (thandle_t fd, tdata_t buf, tsize_t size);
static toff_t libtiffDummySizeProc(thandle_t fd);
static toff_t libtiffDummySeekProc (thandle_t fd, toff_t off, int i);
//static int libtiffDummyCloseProc (thandle_t fd);
// Structure used to pass state to our in-memory TIFF file callbacks
struct MemImage
{
uint8 *buffer;
uint32 offset;
uint32 size;
};
/////////////////// Callbacks to libtiff
static int libtiffDummyMapFileProc(thandle_t, tdata_t*, toff_t*)
{
return 0;
}
static void libtiffDummyUnmapFileProc(thandle_t, tdata_t, toff_t)
{
}
static toff_t libtiffDummySizeProc(thandle_t fd)
{
MemImage *memImage = static_cast<MemImage *>(fd);
return memImage->size;
}
static tsize_t
libtiffDummyReadProc (thandle_t fd, tdata_t buf, tsize_t size)
{
MemImage *memImage = static_cast<MemImage *>(fd);
tsize_t nBytesLeft = memImage->size - memImage->offset;
if (size > nBytesLeft)
{
size = nBytesLeft;
}
memcpy(buf, &memImage->buffer[memImage->offset], size);
memImage->offset += static_cast<uint32>(size);
// Return the amount of data read
return size;
}
static tsize_t
libtiffDummyWriteProc ([[maybe_unused]] thandle_t fd, [[maybe_unused]] tdata_t buf, tsize_t size)
{
return (size);
}
static toff_t
libtiffDummySeekProc (thandle_t fd, toff_t off, int i)
{
MemImage *memImage = static_cast<MemImage *>(fd);
switch (i)
{
case SEEK_SET:
memImage->offset = static_cast<uint32>(off);
break;
case SEEK_CUR:
memImage->offset += static_cast<uint32>(off);
break;
case SEEK_END:
memImage->offset = static_cast<uint32>(memImage->size - off);
break;
default:
memImage->offset = static_cast<uint32>(off);
break;
}
// This appears to return the location that it went to
return memImage->offset;
}
static int
libtiffDummyCloseProc ([[maybe_unused]] thandle_t fd)
{
// Return a zero meaning all is well
return 0;
}
bool CImageTIF::Load(const QString& fileName, CImageEx& outImage)
{
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return false;
}
MemImage memImage;
std::vector<uint8> data;
memImage.size = static_cast<uint32>(file.GetLength());
data.resize(memImage.size);
memImage.buffer = &data[0];
memImage.offset = 0;
file.ReadRaw(memImage.buffer, memImage.size);
// Open the dummy document (which actually only exists in memory)
TIFF* tif = TIFFClientOpen (fileName.toUtf8().data(), "rm", (thandle_t)&memImage, libtiffDummyReadProc,
libtiffDummyWriteProc, libtiffDummySeekProc,
libtiffDummyCloseProc, libtiffDummySizeProc, libtiffDummyMapFileProc, libtiffDummyUnmapFileProc);
// TIFF* tif = TIFFOpen(fileName,"r");
bool bRet = false;
if (tif)
{
uint32 dwWidth, dwHeight;
size_t npixels;
uint32* raster;
char* dccfilename = nullptr;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &dwWidth);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &dwHeight);
TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &dccfilename);
npixels = dwWidth * dwHeight;
raster = (uint32*)_TIFFmalloc((tsize_t)(npixels * sizeof(uint32)));
if (raster)
{
if (TIFFReadRGBAImage(tif, dwWidth, dwHeight, raster, 0))
{
if (outImage.Allocate(dwWidth, dwHeight))
{
char* dest = (char*)outImage.GetData();
uint32 dwPitch = dwWidth * 4;
for (uint32 dwY = 0; dwY < dwHeight; ++dwY)
{
char* src2 = (char*)&raster[(dwHeight - 1 - dwY) * dwWidth];
char* dest2 = &dest[dwPitch * dwY];
memcpy(dest2, src2, dwWidth * 4);
}
if (dccfilename)
{
outImage.SetDccFilename(dccfilename);
}
bRet = true;
}
}
_TIFFfree(raster);
}
TIFFClose(tif);
}
if (!bRet)
{
outImage.Detach();
}
return bRet;
}
bool CImageTIF::Load(const QString& fileName, CFloatImage& outImage)
{
// 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
static const int GEOTIFF_MODELPIXELSCALE_TAG = 33550;
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return false;
}
MemImage memImage;
std::vector<uint8> data;
memImage.size = static_cast<int>(file.GetLength());
data.resize(memImage.size);
memImage.buffer = &data[0];
memImage.offset = 0;
file.ReadRaw(memImage.buffer, memImage.size);
// Open the dummy document (which actually only exists in memory)
TIFF* tif = TIFFClientOpen(fileName.toUtf8().data(), "rm", (thandle_t)&memImage, libtiffDummyReadProc,
libtiffDummyWriteProc, libtiffDummySeekProc,
libtiffDummyCloseProc, libtiffDummySizeProc, libtiffDummyMapFileProc, libtiffDummyUnmapFileProc);
// TIFF* tif = TIFFOpen(fileName,"r");
bool bRet = false;
if (tif)
{
uint32 width = 0, height = 0;
uint16 spp = 0, bpp = 0, format = 0;
char* dccfilename = nullptr;
TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &dccfilename);
TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &height);
TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bpp); // how many bits each color component is. typically 8-bit, but could be 16-bit.
TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); // how many color components per pixel? 1=greyscale, 3=RGB, 4=RGBA
TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &format); // format of the pixel data - int, uint, float.
// There are two types of 32-bit floating point TIF semantics. Paint programs tend to use values in the 0.0 - 1.0 range.
// GeoTIFF files use values where 1.0 = 1 meter by default, but also have an optional ZScale parameter to provide additional
// scaling control.
// By default, we'll assume this is a regular TIFF that we want to leave in the 0.0 - 1.0 range.
float pixelValueScale = 1.0f;
// Check to see if it's a GeoTIFF, and if so, whether or not it has the ZScale parameter.
uint32 tagCount = 0;
double *pixelScales = nullptr;
if (TIFFGetField(tif, GEOTIFF_MODELPIXELSCALE_TAG, &tagCount, &pixelScales) == 1)
{
// 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))
{
pixelValueScale = static_cast<float>(pixelScales[2]);
}
}
uint32 linesize = static_cast<uint32>(TIFFScanlineSize(tif));
uint8* linebuf = static_cast<uint8*>(_TIFFmalloc(linesize));
// We assume that a scanline has all of the samples in it. Validate the assumption.
assert(linesize == (width * (bpp / 8) * spp));
// Aliases for linebuf to make it easier to pull different types out of the scanline.
uint16* linebufUint16 = reinterpret_cast<uint16*>(linebuf);
uint32* linebufUint32 = reinterpret_cast<uint32*>(linebuf);
float* linebufFloat = reinterpret_cast<float*>(linebuf);
if (linebuf)
{
if (outImage.Allocate(width, height))
{
float* dest = outImage.GetData();
bRet = true;
float maxPixelValue = 0.0f;
for (uint32 y = 0; y < height; y++)
{
TIFFReadScanline(tif, linebuf, y);
// For each pixel, we either scale or clamp the values to a 16-bit range. It is asymmetric behaviour, but based
// on assumptions about the input data:
// 8-bit values are scaled up because 8-bit textures used as heightmaps are usually scaled-down 16-bit values.
// 32-bit values may or may not need to scale down, depending on the intended authoring range. Our assumption
// is that they were most likely authored with the intent of 1:1 value translations.
for (uint32 x = 0; x < width; x++)
{
switch (bpp)
{
case 8:
// Scale 0-255 to 0.0 - 1.0
dest[(y * width) + x] = static_cast<float>(linebuf[x * spp]) / static_cast<float>(std::numeric_limits<uint8>::max());
break;
case 16:
// Scale 0-65535 to 0.0 - 1.0
dest[(y * width) + x] = static_cast<float>(linebufUint16[x * spp]) / static_cast<float>(std::numeric_limits<uint16>::max());
break;
case 32:
// 32-bit values could be ints or floats.
if (format == SAMPLEFORMAT_INT)
{
// Scale 0-max int32 to 0.0 - 1.0
dest[(y * width) + x] = clamp_tpl(static_cast<float>(linebufUint32[x * spp]) / static_cast<float>(std::numeric_limits<int32>::max()), 0.0f, 1.0f);
}
else if (format == SAMPLEFORMAT_UINT)
{
// Scale 0-max uint32 to 0.0 - 1.0
dest[(y * width) + x] = clamp_tpl(static_cast<float>(linebufUint32[x * spp]) / static_cast<float>(std::numeric_limits<uint32>::max()), 0.0f, 1.0f);
}
else if (format == SAMPLEFORMAT_IEEEFP)
{
dest[(y * width) + x] = linebufFloat[x * spp] * pixelValueScale;
}
else
{
// Unknown / unsupported format.
bRet = false;
}
break;
default:
// Unknown / unsupported format.
bRet = false;
break;
}
maxPixelValue = max(maxPixelValue, dest[(y * width) + x]);
}
}
if (dccfilename)
{
outImage.SetDccFilename(dccfilename);
}
// If this is a GeoTIFF using 32-bit floats, we will end up outside the 0.0 - 1.0 range. Let's scale it back down to 0.0 - 1.0.
if (maxPixelValue > 1.0f)
{
for (uint32 y = 0; y < height; y++)
{
for (uint32 x = 0; x < width; x++)
{
dest[(y * width) + x] = dest[(y * width) + x] / maxPixelValue;
}
}
}
}
_TIFFfree(linebuf);
}
TIFFClose(tif);
}
if (!bRet)
{
outImage.Detach();
}
return bRet;
}
//////////////////////////////////////////////////////////////////////////
bool CImageTIF::SaveRAW(const QString& fileName, const void* pData, int width, int height, int bytesPerChannel, int numChannels, bool bFloat, const char* preset)
{
if (bFloat && (bytesPerChannel != 2 && bytesPerChannel != 4))
{
bFloat = false;
}
bool bRet = false;
CFileUtil::OverwriteFile(fileName);
TIFF* tif = TIFFOpen(fileName.toUtf8().data(), "wb");
if (tif)
{
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, numChannels);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bytesPerChannel * 8);
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1);
TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, (numChannels == 1) ? PHOTOMETRIC_MINISBLACK : PHOTOMETRIC_RGB);
TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
if (bFloat)
{
TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP);
}
if (preset && preset[0])
{
AZStd::string tiffphotoshopdata, valueheader;
AZStd::string presetkeyvalue = AZStd::string("/preset=") + AZStd::string(preset);
valueheader.push_back('\x1C');
valueheader.push_back('\x02');
valueheader.push_back('\x28');
valueheader.push_back((presetkeyvalue.size() >> 8) & 0xFF);
valueheader.push_back((presetkeyvalue.size()) & 0xFF);
valueheader.append(presetkeyvalue);
tiffphotoshopdata.push_back('8');
tiffphotoshopdata.push_back('B');
tiffphotoshopdata.push_back('I');
tiffphotoshopdata.push_back('M');
tiffphotoshopdata.push_back('\x04');
tiffphotoshopdata.push_back('\x04');
tiffphotoshopdata.push_back('\x00');
tiffphotoshopdata.push_back('\x00');
tiffphotoshopdata.push_back((valueheader.size() >> 24) & 0xFF);
tiffphotoshopdata.push_back((valueheader.size() >> 16) & 0xFF);
tiffphotoshopdata.push_back((valueheader.size() >> 8) & 0xFF);
tiffphotoshopdata.push_back((valueheader.size()) & 0xFF);
tiffphotoshopdata.append(valueheader);
TIFFSetField(tif, TIFFTAG_PHOTOSHOP, tiffphotoshopdata.size(), tiffphotoshopdata.c_str());
}
size_t pitch = width * bytesPerChannel * numChannels;
char* raster = (char*) _TIFFmalloc((tsize_t)(pitch * height));
memcpy(raster, pData, pitch * height);
bRet = true;
for (int h = 0; h < height; ++h)
{
size_t offset = h * pitch;
int err = TIFFWriteScanline(tif, raster + offset, h, 0);
if (err < 0)
{
bRet = false;
break;
}
}
_TIFFfree(raster);
TIFFClose(tif);
}
return bRet;
}
const char* CImageTIF::GetPreset(const QString& fileName)
{
std::vector<uint8> data;
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return nullptr;
}
MemImage memImage;
memImage.size = static_cast<uint32>(file.GetLength());
data.resize(memImage.size);
memImage.buffer = &data[0];
memImage.offset = 0;
file.ReadRaw(memImage.buffer, memImage.size);
TIFF* tif = TIFFClientOpen (fileName.toUtf8().data(), "rm", (thandle_t)&memImage, libtiffDummyReadProc,
libtiffDummyWriteProc, libtiffDummySeekProc,
libtiffDummyCloseProc, libtiffDummySizeProc, libtiffDummyMapFileProc, libtiffDummyUnmapFileProc);
AZStd::string strReturn;
char* preset = nullptr;
int size;
if (tif)
{
TIFFGetField(tif, TIFFTAG_PHOTOSHOP, &size, &preset);
for (int i = 0; i < size; ++i)
{
if (!strncmp((preset + i), "preset", 6))
{
char* presetoffset = preset + i;
strReturn = presetoffset;
if (strReturn.find('/') != -1)
{
strReturn = strReturn.substr(0, strReturn.find('/'));
}
break;
}
}
TIFFClose(tif);
}
return strReturn.c_str();
}