From cbfe089506155921ae20aae551c1aa5951ff0d43 Mon Sep 17 00:00:00 2001 From: blattersturm Date: Sat, 4 Feb 2023 22:52:31 +0100 Subject: [PATCH] feat(natives/five): SET_RUNTIME_TEXTURE_IMAGE command Another end user request: https://forum.cfx.re/t/5015014 --- .../src/RuntimeAssetNatives.cpp | 209 ++++++++++++++---- ext/native-decls/SetRuntimeTextureImage.md | 22 ++ 2 files changed, 186 insertions(+), 45 deletions(-) create mode 100644 ext/native-decls/SetRuntimeTextureImage.md diff --git a/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp b/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp index 9f20c9ebe5..8dbf434323 100644 --- a/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp +++ b/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp @@ -75,6 +75,8 @@ class RuntimeTex bool SetPixelData(const void* data, size_t length); + bool LoadImage(const char* fileName); + void Commit(); inline void SetReferenceData(fwRefContainer reference) @@ -358,20 +360,16 @@ RuntimeTex* RuntimeTxd::CreateTextureFromDui(const char* name, const char* duiHa #pragma comment(lib, "windowscodecs.lib") -ComPtr g_imagingFactory; +static ComPtr g_imagingFactory; -RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fileName) +static ComPtr ImageToBitmapSource(std::string_view fileName) { - if (!m_txd) - { - return nullptr; - } - if (!g_imagingFactory) { HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory1, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (void**)g_imagingFactory.GetAddressOf()); } + ComPtr source; ComPtr stream; std::string fileNameString(fileName); @@ -382,7 +380,7 @@ RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fil if (f == std::string::npos) { - return nullptr; + return {}; } fileNameString = fileNameString.substr(f + 7); @@ -391,14 +389,16 @@ RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fil UrlDecode(fileNameString, decodedURL, false); decodedURL.erase(std::remove_if(decodedURL.begin(), decodedURL.end(), [](char c) - { - return std::isspace(c, std::locale::classic()); - }), decodedURL.end()); + { + return std::isspace(c, std::locale::classic()); + }), + decodedURL.end()); size_t length = decodedURL.length(); size_t paddingNeeded = 4 - (length % 4); - if ((paddingNeeded == 1 || paddingNeeded == 2) && decodedURL[length - 1] != '=') { + if ((paddingNeeded == 1 || paddingNeeded == 2) && decodedURL[length - 1] != '=') + { decodedURL.resize(length + paddingNeeded, '='); } @@ -412,12 +412,12 @@ RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fil if (!FX_SUCCEEDED(fx::GetCurrentScriptRuntime(&runtime))) { - return nullptr; + return {}; } fx::Resource* resource = reinterpret_cast(runtime->GetParentObject()); - stream = vfs::CreateComStream(vfs::OpenRead(resource->GetPath() + "/" + fileName)); + stream = vfs::CreateComStream(vfs::OpenRead(fmt::sprintf("%s/%s", resource->GetPath(), fileName))); } ComPtr decoder; @@ -432,51 +432,169 @@ RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fil if (SUCCEEDED(hr)) { - ComPtr source; - ComPtr convertedSource; + // try to convert to a pixel format we like + frame.As(&source); + } + } - UINT width = 0, height = 0; + return source; +} - frame->GetSize(&width, &height); +RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fileName) +{ + if (!m_txd) + { + return nullptr; + } - // try to convert to a pixel format we like - frame.As(&source); + if (auto source = ImageToBitmapSource(fileName)) + { + ComPtr convertedSource; + + UINT width = 0, height = 0; + source->GetSize(&width, &height); + + // try to convert to a pixel format we like + HRESULT hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppBGRA, source.Get(), convertedSource.GetAddressOf()); + + if (SUCCEEDED(hr)) + { + source = convertedSource; + } + + // create a pixel data buffer + std::unique_ptr pixelData(new uint32_t[width * height]); + + hr = source->CopyPixels(nullptr, width * 4, width * height * 4, reinterpret_cast(pixelData.get())); + + if (SUCCEEDED(hr)) + { + rage::grcTextureReference reference; + memset(&reference, 0, sizeof(reference)); + reference.width = width; + reference.height = height; + reference.depth = 1; + reference.stride = width * 4; + reference.format = 11; // should correspond to DXGI_FORMAT_B8G8R8A8_UNORM + reference.pixelData = (uint8_t*)pixelData.get(); + + auto tex = std::make_shared(rage::grcTextureFactory::getInstance()->createImage(&reference, nullptr), pixelData.get(), width * height * 4); + m_txd->Add(name, tex->GetTexture()); + + m_textures[name] = tex; + + scrBindAddSafePointer(tex.get()); + return tex.get(); + } + } + + return nullptr; +} + +bool RuntimeTex::LoadImage(const char* fileName) +{ + if (!m_texture) + { + return false; + } + + auto completion = [this](const ComPtr& bitmapSource) + { + auto source = bitmapSource; + ComPtr convertedSource; - hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppBGRA, source.Get(), convertedSource.GetAddressOf()); + // try to convert to a pixel format we like + HRESULT hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppBGRA, source.Get(), convertedSource.GetAddressOf()); - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) + { + source = convertedSource; + } + + // set up the pixel data buffer + UINT width = 0, height = 0; + source->GetSize(&width, &height); + + size_t length = (size_t(width) * size_t(height) * 4); + + if (length == m_backingPixels.size()) + { + if (SUCCEEDED(source->CopyPixels(nullptr, width * 4, width * height * 4, reinterpret_cast(&m_backingPixels[0])))) { - source = convertedSource; + Commit(); + + return true; } + } - // create a pixel data buffer - std::unique_ptr pixelData(new uint32_t[width * height]); + return false; + }; - hr = source->CopyPixels(nullptr, width * 4, width * height * 4, reinterpret_cast(pixelData.get())); + if (auto source = ImageToBitmapSource(fileName)) + { + UINT width = 0, height = 0; + source->GetSize(&width, &height); - if (SUCCEEDED(hr)) + if (width == m_texture->GetWidth() && height == m_texture->GetHeight()) + { + return completion(source); + } + else + { + struct Item { - rage::grcTextureReference reference; - memset(&reference, 0, sizeof(reference)); - reference.width = width; - reference.height = height; - reference.depth = 1; - reference.stride = width * 4; - reference.format = 11; // should correspond to DXGI_FORMAT_B8G8R8A8_UNORM - reference.pixelData = (uint8_t*)pixelData.get(); - - auto tex = std::make_shared(rage::grcTextureFactory::getInstance()->createImage(&reference, nullptr), pixelData.get(), width * height * 4); - m_txd->Add(name, tex->GetTexture()); - - m_textures[name] = tex; - - scrBindAddSafePointer(tex.get()); - return tex.get(); - } + ComPtr bitmap; + int targetWidth; + int targetHeight; + decltype(completion) cb; + + Item(ComPtr&& bitmap, int targetWidth, int targetHeight, decltype(completion)&& cb) + : bitmap(std::move(bitmap)), targetWidth(targetWidth), targetHeight(targetHeight), cb(std::move(cb)) + { + + } + + void Work() + { + ComPtr scaler; + + if (SUCCEEDED(g_imagingFactory->CreateBitmapScaler(&scaler))) + { + if (SUCCEEDED(scaler->Initialize( + bitmap.Get(), + targetWidth, + targetHeight, + WICBitmapInterpolationModeFant))) + { + ComPtr outBitmap; + scaler.As(&outBitmap); + + OnNextMainFrame([cb = std::move(cb), bitmap = std::move(outBitmap)]() + { + cb(bitmap); + }); + } + } + } + + static DWORD WINAPI StaticWork(LPVOID arg) + { + auto self = static_cast(arg); + self->Work(); + delete self; + + return 0; + } + }; + + auto workItem = new Item(std::move(source), m_texture->GetWidth(), m_texture->GetHeight(), std::move(completion)); + QueueUserWorkItem(&Item::StaticWork, workItem, 0); + + return true; } } - return nullptr; + return false; } #define VFS_GET_RAGE_PAGE_FLAGS 0x20001 @@ -1006,6 +1124,7 @@ static InitFunction initFunction([]() .AddMethod("GET_RUNTIME_TEXTURE_PITCH", &RuntimeTex::GetPitch) .AddMethod("SET_RUNTIME_TEXTURE_PIXEL", &RuntimeTex::SetPixel) .AddMethod("SET_RUNTIME_TEXTURE_ARGB_DATA", &RuntimeTex::SetPixelData) + .AddMethod("SET_RUNTIME_TEXTURE_IMAGE", &RuntimeTex::LoadImage) .AddMethod("COMMIT_RUNTIME_TEXTURE", &RuntimeTex::Commit); fx::ScriptEngine::RegisterNativeHandler("REGISTER_ARCHETYPES", [](fx::ScriptContext& context) diff --git a/ext/native-decls/SetRuntimeTextureImage.md b/ext/native-decls/SetRuntimeTextureImage.md new file mode 100644 index 0000000000..d6a3e8a592 --- /dev/null +++ b/ext/native-decls/SetRuntimeTextureImage.md @@ -0,0 +1,22 @@ +--- +ns: CFX +apiset: client +--- +## SET_RUNTIME_TEXTURE_IMAGE + +```c +BOOL SET_RUNTIME_TEXTURE_IMAGE(long tex, char* fileName); +``` + +Replaces the pixel data in a runtime texture with the image data from a file in the current resource, or a data URL. + +If the bitmap is a different size compared to the existing texture, it will be resampled. + +This command may end up executed asynchronously, and only update the texture data at a later time. + +## Parameters +* **tex**: A runtime texture handle. +* **fileName**: The file name of an image to load, or a base64 "data:" URL. This should preferably be a PNG, and has to be specified as a `file` in the resource manifest. + +## Return value +TRUE for success, FALSE for failure. \ No newline at end of file