Add FromMemory and LoadMemory to Texture for loading images from memory

This commit is contained in:
2026-08-10 14:58:09 +02:00
parent fff611e7d2
commit a7baf9ca37
7 changed files with 169 additions and 79 deletions
+31
View File
@@ -186,3 +186,34 @@ Texture* Texture::FromFile(const StringView& path, bool generateMips)
}
return texture;
}
bool Texture::LoadMemory(Span<byte> data, ImageFormat format, bool generateMips)
{
if (!IsVirtual())
{
LOG(Error, "Loading image from file is supported only for virtual textures.");
return true;
}
TextureData textureData;
if (TextureTool::ImportTexture(data, format, textureData))
{
return true;
}
auto initData = New<InitData>();
initData->FromTextureData(textureData, generateMips);
return Init(initData);
}
Texture* Texture::FromMemory(Span<byte> data, ImageFormat format, bool generateMips)
{
auto texture = Content::CreateVirtualAsset<Texture>();
if (texture->LoadMemory(data, format, generateMips))
{
texture->DeleteObject();
texture = nullptr;
}
return texture;
}
+19
View File
@@ -49,6 +49,25 @@ public:
/// <returns>The loaded texture (virtual asset) or null if fails.</returns>
API_FUNCTION() static Texture* FromFile(const StringView& path, bool generateMips = false);
/// <summary>
/// Loads the texture from the image memory. Supported data formats depend on a runtime platform. All platform support loading PNG, BMP, TGA, HDR and JPEG files.
/// </summary>
/// <remarks>Valid only for virtual assets.</remarks>
/// <param name="data">The source image file data (eg. raw PNG file contents).</param>
/// <param name="format">The source image file format (from extension).</param>
/// <param name="generateMips">True if generate mipmaps for the imported texture.</param>
/// <returns>True if fails, otherwise false.</returns>
API_FUNCTION() bool LoadMemory(Span<byte> data, ImageFormat format, bool generateMips = false);
/// <summary>
/// Loads the texture from the image memory and creates the virtual texture asset for it. Supported data formats depend on a runtime platform. All platform support loading PNG, BMP, TGA, HDR and JPEG files.
/// </summary>
/// <param name="data">The source image file data (eg. raw PNG file contents).</param>
/// <param name="format">The source image file format (from extension).</param>
/// <param name="generateMips">True if generate mipmaps for the imported texture.</param>
/// <returns>The loaded texture (virtual asset) or null if fails.</returns>
API_FUNCTION() static Texture* FromMemory(Span<byte> data, ImageFormat format, bool generateMips = false);
public:
// [TextureBase]
#if USE_EDITOR
@@ -8,6 +8,35 @@
class GPUTexture;
class Task;
/// <summary>
/// Enumeration of different image formats.
/// </summary>
API_ENUM() enum class ImageFormat
{
// .dds
DDS,
// .tga
TGA,
// .png
PNG,
// .bmp
BMP,
// .gif
GIF,
// .tiff
TIFF,
// .jpg/.jpeg
JPEG,
// .hdr
HDR,
// .raw
RAW,
// .exr
EXR,
// Custom
Internal,
};
/// <summary>
/// Interface for objects that can manage streamable texture
/// </summary>
@@ -6,7 +6,6 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Config/GraphicsSettings.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Platform/ConditionVariable.h"
#include "Engine/Graphics/RenderTools.h"
@@ -290,21 +289,13 @@ bool TextureTool::ExportTextureDirectXTex(ImageType type, const StringView& path
return false;
}
HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
HRESULT LoadFromRAWFile(Span<byte> bytes, DirectX::ScratchImage& image)
{
// Assume 16-bit, grayscale .RAW file in little-endian byte order
// Load raw bytes from file
Array<byte> data;
if (File::ReadAllBytes(path, data))
{
LOG(Warning, "Failed to load file data.");
return ERROR_PATH_NOT_FOUND;
}
// Check size
const auto size = (int32)Math::Sqrt(data.Count() / 2.0f);
if (data.Count() != size * size * 2)
const auto size = (int32)Math::Sqrt(bytes.Length() / 2.0f);
if (bytes.Length() != size * size * 2)
{
LOG(Warning, "Invalid RAW file data size or format. Use 16-bit .RAW file in little-endian byte order (square dimensions).");
return ERROR_BAD_FORMAT;
@@ -315,25 +306,24 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
img.format = DXGI_FORMAT_R16_UNORM;
img.width = size;
img.height = size;
img.rowPitch = data.Count() / size;
img.slicePitch = data.Count();
img.rowPitch = bytes.Length() / size;
img.slicePitch = bytes.Length();
// Link data
img.pixels = data.Get();
img.pixels = bytes.Get();
// Init
return image.InitializeFromImage(img);
}
HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
HRESULT LoadFromEXRFile(Span<byte> bytes, DirectX::ScratchImage& image)
{
#if USE_EDITOR
// Load exr file
AnsiPathTempFile tempFile(path);
float* pixels;
int width, height;
const char* err = nullptr;
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
int ret = LoadEXRFromMemory(&pixels, &width, &height, bytes.Get(), bytes.Length(), &err);
if (ret != TINYEXR_SUCCESS)
{
if (err)
@@ -368,7 +358,7 @@ HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
#endif
}
bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
bool TextureTool::ImportTextureDirectXTex(ImageType type, Span<byte> bytes, TextureData& textureData, bool& hasAlpha)
{
// Load image data
DirectX::ScratchImage image;
@@ -380,22 +370,22 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::TIFF:
case ImageType::JPEG:
case ImageType::PNG:
result = DirectX::LoadFromWICFile(*path, DirectX::WIC_FLAGS_NONE, nullptr, image);
result = DirectX::LoadFromWICMemory(bytes.Get(), bytes.Length(), DirectX::WIC_FLAGS_NONE, nullptr, image);
break;
case ImageType::DDS:
result = DirectX::LoadFromDDSFile(*path, DirectX::DDS_FLAGS_NONE, nullptr, image);
result = DirectX::LoadFromDDSMemory(bytes.Get(), bytes.Length(), DirectX::DDS_FLAGS_NONE, nullptr, image);
break;
case ImageType::TGA:
result = DirectX::LoadFromTGAFile(*path, nullptr, image);
result = DirectX::LoadFromTGAMemory(bytes.Get(), bytes.Length(), nullptr, image);
break;
case ImageType::HDR:
result = DirectX::LoadFromHDRFile(*path, nullptr, image);
result = DirectX::LoadFromHDRMemory(bytes.Get(), bytes.Length(), nullptr, image);
break;
case ImageType::RAW:
result = LoadFromRAWFile(path, image);
result = LoadFromRAWFile(bytes, image);
break;
case ImageType::EXR:
result = LoadFromEXRFile(path, image);
result = LoadFromEXRFile(bytes, image);
break;
default:
result = DXGI_ERROR_INVALID_CALL;
@@ -581,7 +571,7 @@ HRESULT CustomGenerateMipMaps(const DirectX::Image* srcImages, size_t nimages, c
return S_OK;
}
bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha)
bool TextureTool::ImportTextureDirectXTex(ImageType type, Span<byte> bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha)
{
#define SET_CURRENT_IMG(x) currentImage = &x
#define GET_TMP_IMG() (currentImage != &image1 ? image1 : image2)
@@ -599,22 +589,22 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::TIFF:
case ImageType::JPEG:
case ImageType::PNG:
result = DirectX::LoadFromWICFile(*path, DirectX::WIC_FLAGS_NONE, nullptr, image1);
result = DirectX::LoadFromWICMemory(bytes.Get(), bytes.Length(), DirectX::WIC_FLAGS_NONE, nullptr, image1);
break;
case ImageType::DDS:
result = DirectX::LoadFromDDSFile(*path, DirectX::DDS_FLAGS_NONE, nullptr, image1);
result = DirectX::LoadFromDDSMemory(bytes.Get(), bytes.Length(), DirectX::DDS_FLAGS_NONE, nullptr, image1);
break;
case ImageType::TGA:
result = DirectX::LoadFromTGAFile(*path, nullptr, image1);
result = DirectX::LoadFromTGAMemory(bytes.Get(), bytes.Length(), nullptr, image1);
break;
case ImageType::HDR:
result = DirectX::LoadFromHDRFile(*path, nullptr, image1);
result = DirectX::LoadFromHDRMemory(bytes.Get(), bytes.Length(), nullptr, image1);
break;
case ImageType::RAW:
result = LoadFromRAWFile(path, image1);
result = LoadFromRAWFile(bytes, image1);
break;
case ImageType::EXR:
result = LoadFromEXRFile(path, image1);
result = LoadFromEXRFile(bytes, image1);
break;
case ImageType::Internal:
{
@@ -724,7 +714,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
}
if (options.GenerateMipMaps && !isPowerOfTwo)
{
LOG(Warning, "Cannot generate mip maps for texture '{}' that size is not power of two. Use Resize or Max Size to change dimensions.", StringUtils::GetFileName(path), width, height);
LOG(Warning, "Cannot generate mip maps for texture that size is not power of two ({}x{}). Use Resize or Max Size to change dimensions.", width, height);
}
// Allocate memory for texture data
@@ -987,7 +977,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
}
if (FAILED(result))
{
errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {1:x}"), *path, static_cast<uint32>(result));
errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {0:x}"), (uint32)result);
return true;
}
SET_CURRENT_IMG(tmpImg);
@@ -1002,7 +992,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
result = tmpImg.Initialize(info);
if (FAILED(result))
{
errorMsg = String::Format(TEXT("Failed initialize image, error: {1:x}"), *path, static_cast<uint32>(result));
errorMsg = String::Format(TEXT("Failed initialize image, error: {0:x}"), (uint32)result);
return true;
}
@@ -1014,7 +1004,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
result = ScaleMipMapsAlphaForCoverage(img, info.mipLevels, info, item, options.PreserveAlphaCoverageReference, tmpImg);
if (FAILED(result))
{
errorMsg = String::Format(TEXT("Failed to scale mip maps alpha for coverage, error: {1:x}"), *path, static_cast<uint32>(result));
errorMsg = String::Format(TEXT("Failed to scale mip maps alpha for coverage, error: {0:x}"), (uint32)result);
return true;
}
}
@@ -7,6 +7,7 @@
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Serialization/JsonWriter.h"
#include "Engine/Serialization/JsonTools.h"
@@ -224,12 +225,20 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData
if (GetImageType(path, type))
return true;
// Load raw bytes from file
Array<byte> bytes;
if (File::ReadAllBytes(path, bytes))
{
LOG(Warning, "Failed to load file data.");
return true;
}
// Import
bool hasAlpha = false;
#if COMPILE_WITH_DIRECTXTEX
const auto failed = ImportTextureDirectXTex(type, path, textureData, hasAlpha);
const auto failed = ImportTextureDirectXTex(type, ToSpan(bytes), textureData, hasAlpha);
#elif COMPILE_WITH_STB
const auto failed = ImportTextureStb(type, path, textureData, hasAlpha);
const auto failed = ImportTextureStb(type, ToSpan(bytes), textureData, hasAlpha);
#else
const auto failed = true;
LOG(Warning, "Importing textures is not supported on this platform.");
@@ -269,6 +278,14 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData
return true;
}
// Load raw bytes from file
Array<byte> bytes;
if (File::ReadAllBytes(path, bytes))
{
LOG(Warning, "Failed to load file data.");
return true;
}
// Clamp values
options.MaxSize = Math::Clamp(options.MaxSize, 1, GPU_MAX_TEXTURE_SIZE);
options.SizeX = Math::Clamp(options.SizeX, 1, GPU_MAX_TEXTURE_SIZE);
@@ -277,9 +294,9 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData
// Import
bool hasAlpha = false;
#if COMPILE_WITH_DIRECTXTEX
const auto failed = ImportTextureDirectXTex(type, path, textureData, options, errorMsg, hasAlpha);
const auto failed = ImportTextureDirectXTex(type, ToSpan(bytes), textureData, options, errorMsg, hasAlpha);
#elif COMPILE_WITH_STB
const auto failed = ImportTextureStb(type, path, textureData, options, errorMsg, hasAlpha);
const auto failed = ImportTextureStb(type, ToSpan(bytes), textureData, options, errorMsg, hasAlpha);
#else
const auto failed = true;
LOG(Warning, "Importing textures is not supported on this platform.");
@@ -300,6 +317,22 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData
return failed;
}
bool TextureTool::ImportTexture(const Span<byte>& data, ImageType type, TextureData& textureData)
{
PROFILE_CPU();
PROFILE_MEM(GraphicsTextures);
bool hasAlpha = false;
#if COMPILE_WITH_DIRECTXTEX
const auto failed = ImportTextureDirectXTex(type, data, textureData, hasAlpha);
#elif COMPILE_WITH_STB
const auto failed = ImportTextureStb(type, data, textureData, hasAlpha);
#else
const auto failed = true;
LOG(Warning, "Importing textures is not supported on this platform.");
#endif
return failed;
}
bool TextureTool::ExportTexture(const StringView& path, const TextureData& textureData)
{
PROFILE_CPU();
+17 -21
View File
@@ -136,6 +136,8 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
typedef ImageFormat ImageType;
public:
#if USE_EDITOR
/// <summary>
@@ -147,7 +149,7 @@ public:
#endif
/// <summary>
/// Imports the texture.
/// Imports the texture from file.
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="textureData">The output data.</param>
@@ -155,7 +157,7 @@ public:
static bool ImportTexture(const StringView& path, TextureData& textureData);
/// <summary>
/// Imports the texture.
/// Imports the texture from file with custom options.
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="textureData">The output data.</param>
@@ -164,6 +166,15 @@ public:
/// <returns>True if fails, otherwise false.</returns>
static bool ImportTexture(const StringView& path, TextureData& textureData, Options options, String& errorMsg);
/// <summary>
/// Imports the texture from memory.
/// </summary>
/// <param name="data">The file data.</param>
/// <param name="type">The file type.</param>
/// <param name="textureData">The output data.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool ImportTexture(const Span<byte>& data, ImageType type, TextureData& textureData);
/// <summary>
/// Exports the texture.
/// </summary>
@@ -222,35 +233,20 @@ public:
#endif
private:
enum class ImageType
{
DDS,
TGA,
PNG,
BMP,
GIF,
TIFF,
JPEG,
HDR,
RAW,
EXR,
Internal,
};
static bool GetImageType(const StringView& path, ImageType& type);
static bool Transform(TextureData& texture, const Function<void(Color&)>& transformation);
#if COMPILE_WITH_DIRECTXTEX
static bool ExportTextureDirectXTex(ImageType type, const StringView& path, const TextureData& textureData);
static bool ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha);
static bool ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
static bool ImportTextureDirectXTex(ImageType type, Span<byte> bytes, TextureData& textureData, bool& hasAlpha);
static bool ImportTextureDirectXTex(ImageType type, Span<byte> bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
static bool ConvertDirectXTex(TextureData& dst, const TextureData& src, const PixelFormat dstFormat);
static bool ResizeDirectXTex(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
#endif
#if COMPILE_WITH_STB
static bool ExportTextureStb(ImageType type, const StringView& path, const TextureData& textureData);
static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha);
static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
static bool ImportTextureStb(ImageType type, Span<byte> bytes, TextureData& textureData, bool& hasAlpha);
static bool ImportTextureStb(ImageType type, Span<byte> bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
static bool ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat);
static bool ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight);
static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
@@ -339,15 +339,8 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
return result != 0;
}
bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
bool TextureTool::ImportTextureStb(ImageType type, Span<byte> bytes, TextureData& textureData, bool& hasAlpha)
{
Array<byte> fileData;
if (File::ReadAllBytes(path, fileData))
{
LOG(Warning, "Failed to read data from file.");
return true;
}
switch (type)
{
case ImageType::PNG:
@@ -358,13 +351,12 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
case ImageType::TGA:
{
int width, height, components;
stbi_uc* stbData = stbi_load_from_memory(fileData.Get(), fileData.Count(), &width, &height, &components, 4);
stbi_uc* stbData = stbi_load_from_memory(bytes.Get(), bytes.Length(), &width, &height, &components, 4);
if (!stbData)
{
LOG(Warning, "Failed to load image. {0}", String(stbi_failure_reason()));
return false;
}
fileData.Resize(0);
// Setup texture data
textureData.Width = width;
@@ -399,8 +391,8 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
// Assume 16-bit, grayscale .RAW file in little-endian byte order
// Check size
const auto size = (int32)Math::Sqrt(fileData.Count() / 2.0f);
if (fileData.Count() != size * size * 2)
const auto size = (int32)Math::Sqrt(bytes.Length() / 2.0f);
if (bytes.Length() != size * size * 2)
{
LOG(Warning, "Invalid RAW file data size or format. Use 16-bit .RAW file in little-endian byte order (square dimensions).");
return true;
@@ -414,10 +406,10 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
textureData.Items.Resize(1);
textureData.Items[0].Mips.Resize(1);
auto& mip = textureData.Items[0].Mips[0];
mip.RowPitch = fileData.Count() / size;
mip.DepthPitch = fileData.Count();
mip.RowPitch = bytes.Length() / size;
mip.DepthPitch = bytes.Length();
mip.Lines = size;
mip.Data.Copy(fileData);
mip.Data.Copy(bytes);
break;
}
@@ -559,7 +551,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
return false;
}
bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha)
bool TextureTool::ImportTextureStb(ImageType type, Span<byte> bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha)
{
// Load image data
if (type == ImageType::Internal)
@@ -576,7 +568,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
else
{
stbi_set_flip_vertically_on_load_thread(options.FlipY);
bool failed = ImportTextureStb(type, path, textureData, hasAlpha);
bool failed = ImportTextureStb(type, bytes, textureData, hasAlpha);
stbi_set_flip_vertically_on_load_thread(false);
if (failed)
{
@@ -629,7 +621,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
}
if (options.GenerateMipMaps && !isPowerOfTwo)
{
LOG(Warning, "Cannot generate mip maps for texture '{}' that size is not power of two. Use Resize or Max Size to change dimensions.", StringUtils::GetFileName(path), width, height);
LOG(Warning, "Cannot generate mip maps for texture that size is not power of two ({}x{}). Use Resize or Max Size to change dimensions.", width, height);
}
// Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing)