// Copyright Epic Games, Inc. All Rights Reserved. #include "Formats/TiffImageWrapper.h" #if WITH_LIBTIFF #include "Async/ParallelFor.h" #include "Async/TaskGraphInterfaces.h" #include "Containers/Queue.h" #include "CoreMinimal.h" #include "ImageWrapperPrivate.h" #include "Math/Float16.h" #include "Math/NumericLimits.h" #include "Math/UnrealMathUtility.h" #include "Misc/CoreStats.h" #include "Templates/IsSigned.h" #include "Templates/UnrealTypeTraits.h" #include THIRD_PARTY_INCLUDES_START #include "tiff.h" #include "tiffio.h" THIRD_PARTY_INCLUDES_END namespace UE::ImageWrapper::Private { namespace TiffImageWrapper { template DataTypeDest ConvertToWriteFormat(DataTypeSrc ReadedValue) { if constexpr (TIsSame::Value || TIsSame::Value) { return ReadedValue; } else if constexpr (TIsSame::Value || TIsFloatingPoint::Value) { return TNumericLimits::Max() * FMath::Clamp(ReadedValue, 0.0, 1.0); } else { // Naive linear interpolation return TNumericLimits::Max() * (double(ReadedValue) / double(TNumericLimits::Max())); } } /** * Does the actual reading and writing * Manage the specifics of each channel type read, conversion to their destination type and writing to the output buffer */ template struct TDefaultAdapter { TDefaultAdapter(TIFF* Tiff) { } void ReadAndWrite(TArrayView64& ReadArray, int64 ReadIndex, TArrayView64& WriteArray, int64 WriteIndex) { WriteArray[WriteIndex] = ConvertToWriteFormat(ReadArray[ReadIndex]); } static const uint8 ChannelPerReadIndex = 1; }; // ===== Small Read adapter ===== // It should be used as parent to another adapter to provide the ReadAndWrite function template struct TReadBitsBaseAdapter { TReadBitsBaseAdapter(TIFF* Tiff) { LineSizeScr = TIFFScanlineSize64(Tiff); TIFFGetField(Tiff, TIFFTAG_IMAGEWIDTH, &Width); } uint8 Read(TArrayView64& ReadArray, int64 ReadIndex) { constexpr uint8 BaseMask = uint8(0xff) >> ((ChannelPerReadIndex - 1) * NumBits); const uint32 Row = ReadIndex / Width; const uint32 Column = ReadIndex % Width; const uint32 PositionOfColInBuffer = Column / ChannelPerReadIndex; /** * The data is stored in reverse order in the byte. From highest to lowest * Example for eight bits from most significant to less (0, 1, 2, 3, 4, 5, 6, 7) */ const uint32 PositionInByte = Column % ChannelPerReadIndex; const uint8 NumShift = ((ChannelPerReadIndex - 1) - PositionInByte) * NumBits; const uint8 Mask = BaseMask << NumShift; ReadIndex = LineSizeScr * Row + PositionOfColInBuffer; const uint8 RawValues = ReadArray[ReadIndex]; const uint8 RawValue = RawValues & Mask; return RawValue >> NumShift; } uint64 LineSizeScr = 0; uint32 Width = 0; static const uint8 ChannelPerReadIndex = 8 / NumBits; }; template struct TReadBitsBaseAdapter<1>; template struct TReadBitsBaseAdapter<2>; template struct TReadBitsBaseAdapter<4>; // ===== Tiff Small bit to uint8 adapter ===== template struct TWriteBitsToUint8Adaptor : public TReadBitsBaseAdapter { using Parent = TReadBitsBaseAdapter; TWriteBitsToUint8Adaptor(TIFF* Tiff) : Parent(Tiff) { } void ReadAndWrite(TArrayView64& ReadArray, int64 ReadIndex, TArrayView64& WriteArray, int64 WriteIndex) { constexpr uint8 MaxValue = uint8(0xff) >> ((Parent::ChannelPerReadIndex - 1) * NumBits); const uint8 ReadedValue = Parent::Read(ReadArray, ReadIndex); WriteArray[WriteIndex] = TNumericLimits::Max() * (double(ReadedValue) / MaxValue); } }; // ===== Tiff Palette adapters ===== template struct TPaletteBaseAdapter { TPaletteBaseAdapter(TIFF* Tiff) { uint16* RedsPtr = nullptr; uint16* GreensPtr = nullptr; uint16* BluesPtr = nullptr; TIFFGetField(Tiff, TIFFTAG_COLORMAP, &RedsPtr, &GreensPtr, &BluesPtr); check (RedsPtr && GreensPtr && BluesPtr); uint16 BitsPerSample; TIFFGetField(Tiff, TIFFTAG_BITSPERSAMPLE, &BitsPerSample); int64 NumValues = int64(1) << BitsPerSample; Reds = TArrayView64(RedsPtr, NumValues); Greens = TArrayView64(GreensPtr, NumValues); Blues = TArrayView64(BluesPtr, NumValues); } void Write(TArrayView64& WriteArray, int64 WriteIndex, DataTypeSrc ReadedValue) { WriteArray[WriteIndex] = Reds[ReadedValue]; WriteArray[WriteIndex + 1] = Greens[ReadedValue]; WriteArray[WriteIndex + 2] = Blues[ReadedValue]; } TArrayView64 Reds; TArrayView64 Greens; TArrayView64 Blues; }; template struct TPaletteBaseAdapter; template struct TPaletteBaseAdapter; template struct TPaletteBaseAdapter; template struct TPaletteAdapter : public TPaletteBaseAdapter { using Parent = TPaletteBaseAdapter; TPaletteAdapter(TIFF* Tiff) : Parent(Tiff) { } void ReadAndWrite(TArrayView64& ReadArray, int64 ReadIndex, TArrayView64& WriteArray, int64 WriteIndex) { Parent::Write(WriteArray, WriteIndex, ReadArray[ReadIndex]); } static const uint8 ChannelPerReadIndex = 1; }; template struct TPaletteBitsAdapter : public TPaletteBaseAdapter, public TReadBitsBaseAdapter { using Reader = TReadBitsBaseAdapter; TPaletteBitsAdapter(TIFF* Tiff) : TPaletteBaseAdapter(Tiff) , Reader(Tiff) { } void ReadAndWrite(TArrayView64& ReadArray, int64 ReadIndex, TArrayView64& WriteArray, int64 WriteIndex) { Write(WriteArray, WriteIndex, Reader::Read(ReadArray, ReadIndex)); } }; // ===== Convert Grayscale two channel to RGBA ===== template struct TTwoChannelToFourAdapter : public ParentApdater { TTwoChannelToFourAdapter(TIFF* Tiff) : ParentApdater(Tiff) { } void ReadAndWrite(TArrayView64& ReadArray, int64 ReadIndex, TArrayView64& WriteArray, int64 WriteIndex) { bool IsAlpha = WriteIndex % 2 == 1; if (IsAlpha) { ParentApdater::ReadAndWrite(ReadArray, ReadIndex, WriteArray, WriteIndex + 2); } else { ParentApdater::ReadAndWrite(ReadArray, ReadIndex, WriteArray, WriteIndex); DataTypeDest ValueWritten = WriteArray[WriteIndex]; WriteArray[WriteIndex + 1] = ValueWritten; WriteArray[WriteIndex + 2] = ValueWritten; } } }; class FProcessDecodedDataTask { public: public: /** * Creates and initializes a new instance. * * @param InFunction The function to execute asynchronously. */ FProcessDecodedDataTask(TUniqueFunction&& InFunction) : Function(MoveTemp(InFunction)) , DesiredThread(IsInGameThread() ? ENamedThreads::AnyHiPriThreadHiPriTask : ENamedThreads::AnyBackgroundThreadNormalTask) {} public: /** * Performs the actual task. * * @param CurrentThread The thread that this task is executing on. * @param MyCompletionGraphEvent The completion event. */ void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { Function(); } /** * Returns the name of the thread that this task should run on. * * @return Always run on any thread. */ ENamedThreads::Type GetDesiredThread() { return DesiredThread; } /** * Gets the task's stats tracking identifier. * * @return Stats identifier. */ TStatId GetStatId() const { return GET_STATID(STAT_TaskGraph_OtherTasks); } /** * Gets the mode for tracking subsequent tasks. * * @return Always track subsequent tasks. */ static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } private: TUniqueFunction Function; ENamedThreads::Type DesiredThread; }; } class FScopedTiffAllocation { public: FScopedTiffAllocation(int64 BytesSize) { Allocation = _TIFFmalloc(BytesSize); } ~FScopedTiffAllocation() { _TIFFfree(Allocation); } void* Allocation = nullptr; }; struct FTIFFReadMemoryFile { static tmsize_t Read(thandle_t Handle, void* Buffer, tmsize_t Size) { FTiffImageWrapper* TiffImageWrapper = reinterpret_cast(Handle); int64 RemaningBytes = TiffImageWrapper->CompressedData.Num() - TiffImageWrapper->CurrentPosition; if (RemaningBytes > 0) { int64 NumBytesReaded = FMath::Min(RemaningBytes, Size); FMemory::Memcpy(Buffer, TiffImageWrapper->CompressedData.GetData() + TiffImageWrapper->CurrentPosition, NumBytesReaded); TiffImageWrapper->CurrentPosition += NumBytesReaded; return NumBytesReaded; } return 0; } static tmsize_t Write(thandle_t Handle, void* Buffer, tmsize_t Size) { // We don't support writing to a in memory file return -1; } static toff_t Seek(thandle_t Handle, toff_t Offset, int Whence) { FTiffImageWrapper* TiffImageWrapper = reinterpret_cast(Handle); const int Set = 0; const int OffsetFromCurrent = 1; const int FromEnd = 2; switch (Whence) { case Set: TiffImageWrapper->CurrentPosition = Offset; break; case OffsetFromCurrent: { const int64 NewPosition = TiffImageWrapper->CurrentPosition + Offset; if (NewPosition >= 0) { TiffImageWrapper->CurrentPosition = NewPosition; } break; } case FromEnd: { const int64 NewPosition = TiffImageWrapper->CompressedData.Num() + Offset; if (NewPosition >= 0) { TiffImageWrapper->CurrentPosition = NewPosition; } break; } default: return -1; } return TiffImageWrapper->CurrentPosition; } static toff_t GetSize(thandle_t Handle) { FTiffImageWrapper* TiffImageWrapper = reinterpret_cast(Handle); return TiffImageWrapper->CompressedData.Num(); } static int Close(thandle_t Handle) { return 0; } static int MapFile(thandle_t Handle, void** Base, toff_t* Size) { return 0; } static void UnmapFile(thandle_t Handle, void* base, toff_t Size) { } }; FTiffImageWrapper::~FTiffImageWrapper() { ReleaseTiffImage(); } void FTiffImageWrapper::Compress(int32 Quality) { checkf(false, TEXT("TIFF compression not supported")); } void FTiffImageWrapper::Uncompress(const ERGBFormat InFormat, int32 InBitDepth) { if (InFormat == Format && InBitDepth == BitDepth) { // Read using RGBA if (Format == ERGBFormat::BGRA && BitDepth == 8) { const int64 BufferSize = int64(Width) * int64(Height) * sizeof(uint32); const int Flags = 0; RawData.Empty(BufferSize); RawData.AddUninitialized(BufferSize); if (TIFFReadRGBAImageOriented(Tiff, Width, Height, static_cast(static_cast(RawData.GetData())), 0, ORIENTATION_LEFTTOP) != 0) { EParallelForFlags ParallelForFlags = IsInGameThread() ? EParallelForFlags::None : EParallelForFlags::BackgroundPriority; ParallelFor(Height, [this](int32 HeightIndex) { const int64 HeightOffset = HeightIndex * Width; for (int32 WidthIndex = 0; WidthIndex < Width; WidthIndex++) { // Swap from RGBA to BGRA const int64 CurrentPixelIndex = (WidthIndex + HeightOffset)* sizeof(uint32); uint8 Red = RawData[CurrentPixelIndex]; RawData[CurrentPixelIndex] = RawData[CurrentPixelIndex + 2]; RawData[CurrentPixelIndex + 2] = Red; } } , ParallelForFlags); } else { UnpackIntoRawBuffer(4); } } else if (Format == ERGBFormat::RGBA && BitDepth == 16 ) { UnpackIntoRawBuffer(4); } else if (Format == ERGBFormat::RGBAF && BitDepth == 16) { UnpackIntoRawBuffer(4); } else if (Format == ERGBFormat::Gray) { if (BitDepth == 8) { UnpackIntoRawBuffer(1); } else if (BitDepth == 16) { UnpackIntoRawBuffer(1); } } } else { SetError(TEXT("Unsupported requested format for the input image. Can't uncompress the tiff image.")); } } bool FTiffImageWrapper::SetCompressed(const void* InCompressedData, int64 InCompressedSize) { bool bResult = FImageWrapperBase::SetCompressed( InCompressedData, InCompressedSize ); if (bResult) { Tiff = TIFFClientOpen("" , "r" , this , FTIFFReadMemoryFile::Read , FTIFFReadMemoryFile::Write , FTIFFReadMemoryFile::Seek , FTIFFReadMemoryFile::Close , FTIFFReadMemoryFile::GetSize , FTIFFReadMemoryFile::MapFile , FTIFFReadMemoryFile::UnmapFile); if (Tiff) { TIFFGetField(Tiff, TIFFTAG_IMAGEWIDTH, &Width); TIFFGetField(Tiff, TIFFTAG_IMAGELENGTH, &Height); TIFFGetField(Tiff, TIFFTAG_PHOTOMETRIC, &Photometric); TIFFGetField(Tiff, TIFFTAG_COMPRESSION, &Compression); TIFFGetField(Tiff, TIFFTAG_SAMPLESPERPIXEL, &SamplesPerPixel); TIFFGetField(Tiff, TIFFTAG_BITSPERSAMPLE, &BitsPerSample); TIFFGetField(Tiff, TIFFTAG_SAMPLEFORMAT, &SampleFormat); Format = ERGBFormat::Invalid; if ( SampleFormat != SAMPLEFORMAT_UINT && SampleFormat != SAMPLEFORMAT_IEEEFP && SampleFormat != 0 /* assume it's uint */) { SetError(TEXT("The sample format of the tiff is unsuported. (We support UInt and IEEEFP)")); return false; } switch (Photometric) { case PHOTOMETRIC_RGB: if ( SamplesPerPixel == 3 || SamplesPerPixel == 4) { if (SampleFormat == SAMPLEFORMAT_IEEEFP && (BitsPerSample == 32 || BitsPerSample == 16)) { Format = ERGBFormat::RGBAF; BitDepth = 16; } else if (BitsPerSample == 16 || BitsPerSample == 32 || BitsPerSample == 64) { Format = ERGBFormat::RGBA; BitDepth = 16; } else { // Will be converted to 8 bits per channel Format = ERGBFormat::BGRA; BitDepth = 8; } } break; case PHOTOMETRIC_YCBCR: case PHOTOMETRIC_CIELAB: case PHOTOMETRIC_ICCLAB: case PHOTOMETRIC_ITULAB: Format = ERGBFormat::BGRA; BitDepth = 8; break; case PHOTOMETRIC_MINISBLACK: case PHOTOMETRIC_MINISWHITE: if (SamplesPerPixel == 1 || SamplesPerPixel == 2) { if (BitsPerSample == 1 || BitsPerSample == 2 || BitsPerSample == 4 || BitsPerSample == 8) { if (SamplesPerPixel == 1) { Format = ERGBFormat::Gray; } else { Format = ERGBFormat::BGRA; } BitDepth = 8; } else if (BitsPerSample == 16 || BitsPerSample == 32 || BitsPerSample == 64) { if (SamplesPerPixel == 1) { Format = ERGBFormat::Gray; } else if (SampleFormat == SAMPLEFORMAT_IEEEFP) { Format = ERGBFormat::RGBAF; } else { Format = ERGBFormat::RGBA; } BitDepth = 16; } } break; case PHOTOMETRIC_PALETTE: if (SamplesPerPixel == 1) { if (BitsPerSample == 1 || BitsPerSample == 2 || BitsPerSample == 4 || BitsPerSample == 8 || BitsPerSample == 32 || BitsPerSample == 64) { Format = ERGBFormat::RGBA; BitDepth = 16; } } break; default: break; } if (Format == ERGBFormat::Invalid) { SetError(TEXT("Unsupported Tiff content.")); return false; } } else { return false; } } return bResult; } void FTiffImageWrapper::ReleaseTiffImage() { if (Tiff) { TIFFClose(Tiff); Tiff = nullptr; } } template void FTiffImageWrapper::CallUnpackIntoRawBufferImpl(uint8 NumOfChannelDest, const bool bIsTiled) { const bool bShouldAddAlpha = NumOfChannelDest == 4 && SamplesPerPixel == 3; if (NumOfChannelDest == SamplesPerPixel || bShouldAddAlpha) { if (bIsTiled) { UnpackIntoRawBufferImpl (NumOfChannelDest, bShouldAddAlpha); } else { UnpackIntoRawBufferImpl (NumOfChannelDest, bShouldAddAlpha); } } else if (SamplesPerPixel == 2 && NumOfChannelDest == 4) { if (bIsTiled) { UnpackIntoRawBufferImpl> (NumOfChannelDest, false); } else { UnpackIntoRawBufferImpl> (NumOfChannelDest, false); } } else { SetError(TEXT("Tiff, Unsupported conversion of sample data format.")); } } template void FTiffImageWrapper::DefaultCallUnpackIntoRawBufferImpl(uint8 NumOfChannelDest, const bool bIsTiled) { CallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); } template void FTiffImageWrapper::PaletteCallUnpackIntoRawBufferImpl(uint8 NumOfChannelDest, const bool bIsTiled) { if (bIsTiled) { UnpackIntoRawBufferImpl (NumOfChannelDest, true); } else { UnpackIntoRawBufferImpl (NumOfChannelDest, true); } } template void FTiffImageWrapper::UnpackIntoRawBuffer(const uint8 NumOfChannelDest) { const int64 PixelSize = sizeof(DataTypeDest) * NumOfChannelDest;; const int64 RowSize = PixelSize * int64(Width); const int64 BufferSize = RowSize * int64(Height); const int Flags = 0; RawData.Empty(BufferSize); RawData.AddUninitialized(BufferSize); const bool bIsTiled = TIFFIsTiled(Tiff) != 0; if constexpr (TIsSame::Value) { if (BitsPerSample == 1) { CallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 2) { CallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 4) { CallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 8) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else { SetError(TEXT("Tiff, Unsupported bits per sample from a uint sample format.")); } } else if constexpr (TIsSame::Value || TIsSame::Value) { if (SampleFormat == SAMPLEFORMAT_UINT || SampleFormat == 0) { if (Photometric == PHOTOMETRIC_PALETTE) { if constexpr (TIsSame::Value) { switch (BitsPerSample) { case 1: PaletteCallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); break; case 2: PaletteCallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); break; case 4: PaletteCallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); break; case 8: PaletteCallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); break; case 16: PaletteCallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); break; case 32: PaletteCallUnpackIntoRawBufferImpl>(NumOfChannelDest, bIsTiled); break; default: SetError(TEXT("Tiff, Unsupported bits per sample from a Palette base image.")); break; } } } else if (BitsPerSample == 16) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 32) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 64) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else { SetError(TEXT("Tiff, Unsupported bits per sample from a uint sample format.")); } } else if (SampleFormat == SAMPLEFORMAT_IEEEFP) { if (BitsPerSample == 16) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 32) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else if (BitsPerSample == 64) { DefaultCallUnpackIntoRawBufferImpl(NumOfChannelDest, bIsTiled); } else { SetError(TEXT("Tiff, Unsupported bits per sample from a IEEEFP sample format.")); } } } if constexpr (!TIsSame::Value) { if (Photometric == PHOTOMETRIC_MINISWHITE) { TArrayView64 FinalImage(static_cast(static_cast(RawData.GetData())), RawData.Num()); ParallelFor(Width * Height, [&FinalImage](int32 Index) { DataTypeDest& FinalValue = FinalImage[Index]; FinalValue = TNumericLimits::Max() - FinalValue; }, IsInGameThread() ? EParallelForFlags::None : EParallelForFlags::BackgroundPriority); } } else { if (Photometric == PHOTOMETRIC_MINISWHITE) { FFloat16 MaxValue; MaxValue.Encoded = 0x7BFF; TArrayView64 FinalImage(static_cast(static_cast(RawData.GetData())), RawData.Num()); ParallelFor(Width * Height, [&FinalImage, MaxValue](int32 Index) { DataTypeDest& FinalValue = FinalImage[Index]; FinalValue = MaxValue - FinalValue; }, IsInGameThread() ? EParallelForFlags::None : EParallelForFlags::BackgroundPriority); } } } template bool FTiffImageWrapper::UnpackIntoRawBufferImpl(const uint8 NumOfChannelDest, const bool bAddAlpha) { using namespace TiffImageWrapper; const uint64 LineSizeScr = TIFFScanlineSize64(Tiff); const uint64 StripByteSize = TIFFStripSize64(Tiff); TArrayView64 WriteArray(static_cast(static_cast(RawData.GetData())), RawData.Num() / sizeof(DataTypeDest)); uint16 PlanarConfig = 0; TIFFGetFieldDefaulted(Tiff, TIFFTAG_PLANARCONFIG, &PlanarConfig); const bool bIsPlanarConfigSepareted = PlanarConfig == PLANARCONFIG_SEPARATE; const uint8 StepDest = bIsPlanarConfigSepareted ? NumOfChannelDest : 1; const uint8 NumberOfChannelInReadArray = bIsPlanarConfigSepareted ? 1 : SamplesPerPixel; int32 TileWidth = Width; int32 TileHeight = Height; if constexpr (bIsTiled) { if (!TIFFGetField(Tiff, TIFFTAG_TILEWIDTH, &TileWidth) || !TIFFGetField(Tiff, TIFFTAG_TILELENGTH, &TileHeight)) { SetError(TEXT("The tiff file is invalid.(Tiled image but no tile dimensions)")); return false; } } ReadWriteAdapter Adapter(Tiff); auto ProccessDecodedData = [this, &WriteArray, NumOfChannelDest, StepDest, NumberOfChannelInReadArray, TileWidth, TileHeight, &Adapter] (int32 NumberOfColumnReaded, int32 NumberOfRowReaded, TArray64& ReadedBuffer, uint8 SampleIndex, int32 BlockX, int32 BlockY) { TArrayView64 ReadArray(static_cast(static_cast(ReadedBuffer.GetData())), ReadedBuffer.Num() / sizeof(DataTypeSrc)); uint64 CurrentOffset = int64(BlockY) * Width * NumOfChannelDest; if constexpr (bIsTiled) { CurrentOffset *= TileHeight; CurrentOffset += int64(BlockX) * TileWidth * NumOfChannelDest; } ParallelFor(NumberOfRowReaded * NumberOfColumnReaded , [this, BlockY, &WriteArray, &ReadArray, NumOfChannelDest, StepDest, NumberOfChannelInReadArray, SampleIndex, CurrentOffset, NumberOfColumnReaded, TileWidth, &Adapter] (int32 PixelReadIndex) { int64 ReadIndex; int64 WriteIndex; if constexpr (bIsTiled) { const int32 PositionXInTile = PixelReadIndex % NumberOfColumnReaded; const int32 PositionYInTile = PixelReadIndex / NumberOfColumnReaded; WriteIndex = CurrentOffset + PositionXInTile * NumOfChannelDest + PositionYInTile * Width * NumOfChannelDest + SampleIndex; // The end of tile in X can be some garbage that act as padding data so that all tiles are the same size ReadIndex = PositionXInTile * NumberOfChannelInReadArray + PositionYInTile * TileWidth * NumberOfChannelInReadArray; } else { WriteIndex = CurrentOffset + int64(PixelReadIndex) * NumOfChannelDest + SampleIndex; ReadIndex = int64(PixelReadIndex) * NumberOfChannelInReadArray; } for (int32 I = 0; I < NumberOfChannelInReadArray; I++) { Adapter.ReadAndWrite(ReadArray, ReadIndex, WriteArray, WriteIndex); WriteIndex += StepDest; ++ReadIndex; } } , IsInGameThread() ? EParallelForFlags::None : EParallelForFlags::BackgroundPriority); }; const uint8 NumOfPlanes = bIsPlanarConfigSepareted ? SamplesPerPixel : 1; TQueue, ESPMode::ThreadSafe>, EQueueMode::Mpsc> UsableBufferQueue; FGraphEventArray Tasks; if constexpr (bIsTiled) { uint64 TileSize = TIFFTileSize64(Tiff); if (TileSize < TileHeight * TileWidth * NumberOfChannelInReadArray * sizeof(DataTypeSrc) / ReadWriteAdapter::ChannelPerReadIndex) { // Lib tiff and this code is not able to deal with channel of different bit sizes SetError(TEXT("Tiff tiles are smaller then expected. This generaly due to channels of different size which is not supported by UE")); return false; } Tasks.Reserve(TIFFNumberOfTiles(Tiff)); for (uint8 SampleIndex = 0; SampleIndex < NumOfPlanes; SampleIndex++) { for (int32 Y = 0, TileY = 0; Y < Height; Y += TileHeight, ++TileY) { const int32 NumberOfRow = Y + TileHeight > Height ? Height - Y : TileHeight; for (int32 X = 0, TileX = 0; X < Width; X += TileWidth, ++TileX) { TSharedPtr, ESPMode::ThreadSafe> BufferPtr; if (!UsableBufferQueue.Dequeue(BufferPtr)) { BufferPtr = MakeShared, ESPMode::ThreadSafe>(); BufferPtr->AddUninitialized(TileSize); } if (TIFFReadTile(Tiff, BufferPtr->GetData(), X, Y, 0, SampleIndex) < 0) { SetError(TEXT("Couldn't open Tiff tile. This is generally caused by a compression format that UE don't support.")); return false; } const int32 NumberOfColumn = X + TileWidth > Width ? Width - X : TileWidth; Tasks.Add(TGraphTask::CreateTask().ConstructAndDispatchWhenReady([&UsableBufferQueue, &ProccessDecodedData, NumberOfColumn, NumberOfRow, BufferPtr, SampleIndex, TileX, TileY]() { ProccessDecodedData(NumberOfColumn, NumberOfRow, *(BufferPtr.Get()), SampleIndex, TileX, TileY); UsableBufferQueue.Enqueue(BufferPtr); })); } } } } else { uint16 RowPerStrip = 0; TIFFGetField(Tiff, TIFFTAG_ROWSPERSTRIP, &RowPerStrip); const int32 NumberOfRow = RowPerStrip > Height || RowPerStrip == 0 ? Height : RowPerStrip; if (StripByteSize < NumberOfRow * Width * sizeof(DataTypeSrc) * NumberOfChannelInReadArray / ReadWriteAdapter::ChannelPerReadIndex) { // Lib tiff and this code is not able to deal with channel of different bit sizes SetError(TEXT("Tiff strips are smaller then expected. This generaly due to channels of different size which is not supported by UE")); return false; } const TCHAR* ErrorMessage = TEXT("Couldn't open Tiff strip. This is generally caused by a compression format that UE don't support."); auto ProcessDecodedStrips = [this, &ProccessDecodedData](int32 NumberOfRowReaded, TArray64& ReadedBuffer, int32 FirstLineReaded, uint8 SampleIndex) { ProccessDecodedData(Width, NumberOfRowReaded, ReadedBuffer, SampleIndex, 0, FirstLineReaded); }; Tasks.Reserve(TIFFNumberOfStrips(Tiff)); for (uint8 SampleIndex = 0; SampleIndex < NumOfPlanes; SampleIndex++) { if (RowPerStrip != 0) { for (int32 Y = 0; Y < Height; Y += RowPerStrip) { TSharedPtr, ESPMode::ThreadSafe> BufferPtr; if (!UsableBufferQueue.Dequeue(BufferPtr)) { BufferPtr = MakeShared, ESPMode::ThreadSafe>(); BufferPtr->AddUninitialized(StripByteSize); } // The last strip might be smaller int32 NumberOfRowReaded = Y + RowPerStrip > Height ? Height - Y : RowPerStrip; if (TIFFReadEncodedStrip(Tiff, TIFFComputeStrip(Tiff, Y, SampleIndex), BufferPtr->GetData(), NumberOfRowReaded * LineSizeScr) == -1) { SetError(ErrorMessage); return false; } Tasks.Add(TGraphTask::CreateTask().ConstructAndDispatchWhenReady([&UsableBufferQueue, &ProcessDecodedStrips, NumberOfRowReaded, BufferPtr, Y, SampleIndex]() { ProcessDecodedStrips(NumberOfRowReaded, *(BufferPtr.Get()), Y, SampleIndex); UsableBufferQueue.Enqueue(BufferPtr); })); } } else { TSharedPtr, ESPMode::ThreadSafe> BufferPtr; if (!UsableBufferQueue.Dequeue(BufferPtr)) { BufferPtr = MakeShared, ESPMode::ThreadSafe>(); BufferPtr->AddUninitialized(StripByteSize); } if (TIFFReadEncodedStrip(Tiff, SampleIndex, BufferPtr->GetData(), StripByteSize) == -1) { SetError(ErrorMessage); return false; } Tasks.Add(TGraphTask::CreateTask().ConstructAndDispatchWhenReady([this, &UsableBufferQueue, &ProcessDecodedStrips, BufferPtr, SampleIndex]() { ProcessDecodedStrips(Height, *(BufferPtr.Get()), 0, SampleIndex); UsableBufferQueue.Enqueue(BufferPtr); })); } } } // Write the alpha if (bAddAlpha) { ParallelFor(Width * Height, [NumOfChannelDest, &WriteArray](int32 Index) { const int64 WriteIndex = int64(NumOfChannelDest) * Index + 3; if constexpr (!TIsSame::Value) { WriteArray[WriteIndex]= TNumericLimits::Max(); } else { WriteArray[WriteIndex] = DataTypeDest(1.f); } }, IsInGameThread() ? EParallelForFlags::None : EParallelForFlags::BackgroundPriority); } ENamedThreads::Type NamedThread = IsInGameThread() ? ENamedThreads::GameThread : ENamedThreads::AnyThread; FTaskGraphInterface::Get().WaitUntilTasksComplete(Tasks, NamedThread); return true; } } #endif // WITH_LIBTIFF