You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
FImage is now the standard preferred type for a bag of pixels FImageView can point at pixels without owning an allocation ERawImageFormat (FImage) converts to ETextureSourceFormat FImageUtils provides generic load/save and get/set from FImage major cleanup in the ImageWrappers new preferred API is through ImageWrapperModule Compress/Decompress SetRaw/GetRaw functions cleaned up to not have undefined behavior on unexpected formats ImageWrapper output added for HDR,BMP,TGA RGBA32F format added and supported throughout import/export EditorFactories import/export made more generic, most image types handled the same way using FImage now Deprecate old TSF RGBA order pixel formats Fix many crashes or bad handling of unusual pixel formats Pixel access functions should be used instead of switches on pixel type #preflight 6230ade7e65a7e65d68a187c #rb julien.stjean,martins.mozeiko,dan.thompson,fabian.giesen [CL 19397199 by charles bloom in ue5-main branch]
398 lines
11 KiB
C++
398 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "TgaImageSupport.h"
|
|
#include "ImageWrapperPrivate.h"
|
|
|
|
namespace TgaImageSupportImpl
|
|
{
|
|
bool DecompressTGA_RLE_32bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint32* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint8* ImageData = (uint8*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
|
|
const uint8* TGAEnd = (uint8*)TGA + TGABufferLenght;
|
|
|
|
uint32 Pixel = 0;
|
|
int32 RLERun = 0;
|
|
int32 RAWRun = 0;
|
|
|
|
for(int32 Y = TGA->Height-1; Y >=0; Y--) // Y-flipped.
|
|
{
|
|
for(int32 X = 0;X < TGA->Width;X++)
|
|
{
|
|
if( RLERun > 0 )
|
|
{
|
|
RLERun--; // reuse current Pixel data.
|
|
}
|
|
else if( RAWRun == 0 ) // new raw pixel or RLE-run.
|
|
{
|
|
if ( TGAEnd < ImageData )
|
|
{
|
|
return false;
|
|
}
|
|
uint8 RLEChunk = *(ImageData++);
|
|
if( RLEChunk & 0x80 )
|
|
{
|
|
RLERun = ( RLEChunk & 0x7F ) + 1;
|
|
RAWRun = 1;
|
|
}
|
|
else
|
|
{
|
|
RAWRun = ( RLEChunk & 0x7F ) + 1;
|
|
}
|
|
|
|
if ( TGAEnd < ImageData + RAWRun * 4 )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// Retrieve new pixel data - raw run or single pixel for RLE stretch.
|
|
if( RAWRun > 0 )
|
|
{
|
|
Pixel = *(uint32*)ImageData; // RGBA 32-bit dword.
|
|
ImageData += 4;
|
|
RAWRun--;
|
|
RLERun--;
|
|
}
|
|
// Store.
|
|
*( (TextureData + Y*TGA->Width)+X ) = Pixel;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DecompressTGA_RLE_24bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint32* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint8* ImageData = (uint8*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
|
|
const uint8* TGAEnd = (uint8*)TGA + TGABufferLenght;
|
|
|
|
uint8 Pixel[4] = {};
|
|
int32 RLERun = 0;
|
|
int32 RAWRun = 0;
|
|
|
|
for(int32 Y = TGA->Height-1; Y >=0; Y--) // Y-flipped.
|
|
{
|
|
for(int32 X = 0;X < TGA->Width;X++)
|
|
{
|
|
if( RLERun > 0 )
|
|
{
|
|
RLERun--; // reuse current Pixel data.
|
|
}
|
|
else if( RAWRun == 0 ) // new raw pixel or RLE-run.
|
|
{
|
|
if ( TGAEnd < ImageData )
|
|
{
|
|
return false;
|
|
}
|
|
uint8 RLEChunk = *(ImageData++);
|
|
if( RLEChunk & 0x80 )
|
|
{
|
|
RLERun = ( RLEChunk & 0x7F ) + 1;
|
|
RAWRun = 1;
|
|
}
|
|
else
|
|
{
|
|
RAWRun = ( RLEChunk & 0x7F ) + 1;
|
|
}
|
|
|
|
if ( TGAEnd < ImageData + RAWRun * 3 )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// Retrieve new pixel data - raw run or single pixel for RLE stretch.
|
|
if( RAWRun > 0 )
|
|
{
|
|
Pixel[0] = *(ImageData++);
|
|
Pixel[1] = *(ImageData++);
|
|
Pixel[2] = *(ImageData++);
|
|
Pixel[3] = 255;
|
|
RAWRun--;
|
|
RLERun--;
|
|
}
|
|
// Store.
|
|
*( (TextureData + Y*TGA->Width)+X ) = *(uint32*)&Pixel;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DecompressTGA_RLE_16bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint32* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint16* ImageData = (uint16*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
|
|
const uint16* TGAEnd = (uint16*)TGA + TGABufferLenght / 2;
|
|
|
|
uint32 TexturePixel = 0;
|
|
int32 RLERun = 0;
|
|
int32 RAWRun = 0;
|
|
|
|
for(int32 Y = TGA->Height-1; Y >=0; Y--) // Y-flipped.
|
|
{
|
|
for( int32 X=0;X<TGA->Width;X++ )
|
|
{
|
|
if( RLERun > 0 )
|
|
{
|
|
RLERun--; // reuse current Pixel data.
|
|
}
|
|
else if( RAWRun == 0 ) // new raw pixel or RLE-run.
|
|
{
|
|
if ( TGAEnd < ImageData )
|
|
{
|
|
return false;
|
|
}
|
|
uint8 RLEChunk = *((uint8*)ImageData);
|
|
ImageData = (uint16*)(((uint8*)ImageData)+1);
|
|
if( RLEChunk & 0x80 )
|
|
{
|
|
RLERun = ( RLEChunk & 0x7F ) + 1;
|
|
RAWRun = 1;
|
|
}
|
|
else
|
|
{
|
|
RAWRun = ( RLEChunk & 0x7F ) + 1;
|
|
}
|
|
|
|
if ( TGAEnd < ImageData + RAWRun )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// Retrieve new pixel data - raw run or single pixel for RLE stretch.
|
|
if( RAWRun > 0 )
|
|
{
|
|
const uint16 FilePixel = *(ImageData++);
|
|
RAWRun--;
|
|
RLERun--;
|
|
|
|
// Convert file format A1R5G5B5 into pixel format B8G8R8B8
|
|
TexturePixel = (FilePixel & 0x001F) << 3;
|
|
TexturePixel |= (FilePixel & 0x03E0) << 6;
|
|
TexturePixel |= (FilePixel & 0x7C00) << 9;
|
|
TexturePixel |= (FilePixel & 0x8000) << 16;
|
|
}
|
|
// Store.
|
|
*( (TextureData + Y*TGA->Width)+X ) = TexturePixel;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DecompressTGA_32bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint32* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint32* const ImageData = (uint32*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
|
|
if ( (uint8*)TGA + TGABufferLenght < (uint8*)(ImageData + TGA->Width * TGA->Height) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for(int32 Y = 0;Y < TGA->Height;Y++)
|
|
{
|
|
FMemory::Memcpy(TextureData + Y * TGA->Width,ImageData + (TGA->Height - Y - 1) * TGA->Width,TGA->Width * 4);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DecompressTGA_24bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint32* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint8* const ImageData = (uint8*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
uint8 Pixel[4];
|
|
|
|
if ( (uint8*)TGA + TGABufferLenght < ImageData + TGA->Width * TGA->Height * 3 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for(int32 Y = 0; Y < TGA->Height; Y++)
|
|
{
|
|
for(int32 X = 0; X < TGA->Width; X++)
|
|
{
|
|
Pixel[0] = *(( ImageData+( TGA->Height-Y-1 )*TGA->Width*3 )+X*3+0);
|
|
Pixel[1] = *(( ImageData+( TGA->Height-Y-1 )*TGA->Width*3 )+X*3+1);
|
|
Pixel[2] = *(( ImageData+( TGA->Height-Y-1 )*TGA->Width*3 )+X*3+2);
|
|
Pixel[3] = 255;
|
|
*((TextureData+Y*TGA->Width)+X) = *(uint32*)&Pixel;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DecompressTGA_16bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint32* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint16* ImageData = (uint16*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
uint16 FilePixel = 0;
|
|
uint32 TexturePixel = 0;
|
|
|
|
if ( (uint16*)((uint8*)TGA + TGABufferLenght) < ImageData + TGA->Height * TGA->Width )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int32 Y = TGA->Height - 1; Y >= 0; Y--)
|
|
{
|
|
for (int32 X = 0; X < TGA->Width; X++)
|
|
{
|
|
FilePixel = *ImageData++;
|
|
// Convert file format A1R5G5B5 into pixel format B8G8R8A8
|
|
TexturePixel = (FilePixel & 0x001F) << 3;
|
|
TexturePixel |= (FilePixel & 0x03E0) << 6;
|
|
TexturePixel |= (FilePixel & 0x7C00) << 9;
|
|
TexturePixel |= (FilePixel & 0x8000) << 16;
|
|
// Store.
|
|
*((TextureData + Y*TGA->Width) + X) = TexturePixel;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DecompressTGA_8bpp( const FTGAFileHeader* TGA, const int64 TGABufferLenght, uint8* TextureData )
|
|
{
|
|
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
|
|
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
|
|
const uint8* const ImageData = (uint8*) (ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
|
|
|
|
if ( (uint8*)TGA + TGABufferLenght < ImageData + TGA->Width * TGA->Height )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 RevY = 0;
|
|
for (int32 Y = TGA->Height-1; Y >= 0; --Y)
|
|
{
|
|
const uint8* ImageCol = ImageData + (Y * TGA->Width);
|
|
uint8* TextureCol = TextureData + (RevY++ * TGA->Width);
|
|
FMemory::Memcpy(TextureCol, ImageCol, TGA->Width);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool DecompressTGA_helper( const FTGAFileHeader* TgaHeader, const int64 TGABufferLenght, uint32*& TextureData, const int32 TextureDataSize )
|
|
{
|
|
bool bSuccess = false;
|
|
if ( TgaHeader->ImageTypeCode == 10 ) // 10 = RLE compressed
|
|
{
|
|
check( TextureDataSize == TgaHeader->Width * TgaHeader->Height * 4 );
|
|
|
|
// RLE compression: CHUNKS: 1 -byte header, high bit 0 = raw, 1 = compressed
|
|
// bits 0-6 are a 7-bit count; count+1 = number of raw pixels following, or rle pixels to be expanded.
|
|
if(TgaHeader->BitsPerPixel == 32)
|
|
{
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_RLE_32bpp(TgaHeader, TGABufferLenght, TextureData);
|
|
}
|
|
else if( TgaHeader->BitsPerPixel == 24 )
|
|
{
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_RLE_24bpp(TgaHeader, TGABufferLenght, TextureData);
|
|
}
|
|
else if( TgaHeader->BitsPerPixel == 16 )
|
|
{
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_RLE_16bpp(TgaHeader, TGABufferLenght, TextureData);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG( LogImageWrapper, Error, TEXT("TgaHeader uses an unsupported rle-compressed bit-depth: %u"), TgaHeader->BitsPerPixel );
|
|
return false;
|
|
}
|
|
}
|
|
else if(TgaHeader->ImageTypeCode == 2) // 2 = Uncompressed RGB
|
|
{
|
|
check( TextureDataSize == TgaHeader->Width * TgaHeader->Height * 4 );
|
|
|
|
if(TgaHeader->BitsPerPixel == 32)
|
|
{
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_32bpp(TgaHeader, TGABufferLenght, TextureData);
|
|
}
|
|
else if(TgaHeader->BitsPerPixel == 16)
|
|
{
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_16bpp(TgaHeader, TGABufferLenght, TextureData);
|
|
}
|
|
else if(TgaHeader->BitsPerPixel == 24)
|
|
{
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_24bpp(TgaHeader, TGABufferLenght, TextureData);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG( LogImageWrapper, Error, TEXT("TgaHeader uses an unsupported bit-depth: %u"), TgaHeader->BitsPerPixel );
|
|
return false;
|
|
}
|
|
}
|
|
// Support for alpha stored as pseudo-color 8-bit TgaHeader
|
|
else if(TgaHeader->ColorMapType == 1 && TgaHeader->ImageTypeCode == 1 && TgaHeader->BitsPerPixel == 8)
|
|
{
|
|
check( TextureDataSize == TgaHeader->Width * TgaHeader->Height * 1 );
|
|
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_8bpp(TgaHeader, TGABufferLenght, (uint8*)TextureData);
|
|
}
|
|
// standard grayscale
|
|
else if(TgaHeader->ColorMapType == 0 && TgaHeader->ImageTypeCode == 3 && TgaHeader->BitsPerPixel == 8)
|
|
{
|
|
check( TextureDataSize == TgaHeader->Width * TgaHeader->Height * 1 );
|
|
|
|
bSuccess = TgaImageSupportImpl::DecompressTGA_8bpp(TgaHeader, TGABufferLenght, (uint8*)TextureData);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG( LogImageWrapper, Error, TEXT("TgaHeader is an unsupported type: %u"), TgaHeader->ImageTypeCode );
|
|
return false;
|
|
}
|
|
|
|
if (!bSuccess)
|
|
{
|
|
UE_LOG(LogImageWrapper, Error, TEXT("The TGA file is invalid or corrupted"));
|
|
return false;
|
|
}
|
|
|
|
// Flip the image data if the flip bits are set in the TgaHeader header.
|
|
const bool bFlipX = (TgaHeader->ImageDescriptor & 0x10) ? 1 : 0;
|
|
const bool bFlipY = (TgaHeader->ImageDescriptor & 0x20) ? 1 : 0;
|
|
if ( bFlipX || bFlipY )
|
|
{
|
|
TArray<uint8> FlippedData;
|
|
FlippedData.AddUninitialized(TextureDataSize);
|
|
|
|
int32 NumBlocksX = TgaHeader->Width;
|
|
int32 NumBlocksY = TgaHeader->Height;
|
|
int32 BlockBytes = TgaHeader->BitsPerPixel == 8 ? 1 : 4;
|
|
|
|
check( TextureDataSize == NumBlocksX * NumBlocksY * BlockBytes );
|
|
|
|
uint8* MipData = (uint8*)TextureData;
|
|
|
|
for( int32 Y = 0; Y < NumBlocksY;Y++ )
|
|
{
|
|
for( int32 X = 0; X < NumBlocksX; X++ )
|
|
{
|
|
int32 DestX = bFlipX ? (NumBlocksX - X - 1) : X;
|
|
int32 DestY = bFlipY ? (NumBlocksY - Y - 1) : Y;
|
|
FMemory::Memcpy(
|
|
&FlippedData[(DestX + DestY * NumBlocksX) * BlockBytes],
|
|
&MipData[(X + Y * NumBlocksX) * BlockBytes],
|
|
BlockBytes
|
|
);
|
|
}
|
|
}
|
|
|
|
FMemory::Memcpy( MipData, FlippedData.GetData(), FlippedData.Num() );
|
|
}
|
|
|
|
return true;
|
|
} |