mirror of
https://github.com/microsoft/DirectXTex.git
synced 2025-07-15 22:40:14 +02:00
texassemble: gif command(#77)
This commit is contained in:
parent
4e44d972aa
commit
6058279a02
@ -29,8 +29,13 @@
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <dxgiformat.h>
|
||||
|
||||
#include <DirectXPackedVector.h>
|
||||
#include <wincodec.h>
|
||||
|
||||
#include "directxtex.h"
|
||||
|
||||
//Uncomment to add support for OpenEXR (.exr)
|
||||
@ -42,6 +47,7 @@
|
||||
#endif
|
||||
|
||||
using namespace DirectX;
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
enum COMMANDS
|
||||
{
|
||||
@ -54,6 +60,7 @@ enum COMMANDS
|
||||
CMD_H_STRIP,
|
||||
CMD_V_STRIP,
|
||||
CMD_MERGE,
|
||||
CMD_GIF,
|
||||
CMD_MAX
|
||||
};
|
||||
|
||||
@ -77,6 +84,7 @@ enum OPTIONS
|
||||
OPT_TA_MIRROR,
|
||||
OPT_TONEMAP,
|
||||
OPT_FILELIST,
|
||||
OPT_GIF_BGCOLOR,
|
||||
OPT_MAX
|
||||
};
|
||||
|
||||
@ -108,6 +116,7 @@ const SValue g_pCommands[] =
|
||||
{ L"h-strip", CMD_H_STRIP },
|
||||
{ L"v-strip", CMD_V_STRIP },
|
||||
{ L"merge", CMD_MERGE },
|
||||
{ L"gif", CMD_GIF },
|
||||
{ nullptr, 0 }
|
||||
};
|
||||
|
||||
@ -131,6 +140,7 @@ const SValue g_pOptions [] =
|
||||
{ L"mirror", OPT_TA_MIRROR },
|
||||
{ L"tonemap", OPT_TONEMAP },
|
||||
{ L"flist", OPT_FILELIST },
|
||||
{ L"bgcolor", OPT_GIF_BGCOLOR },
|
||||
{ nullptr, 0 }
|
||||
};
|
||||
|
||||
@ -486,7 +496,8 @@ namespace
|
||||
wprintf(L" cubearray create cubemap array\n");
|
||||
wprintf(L" h-cross or v-cross create a cross image from a cubemap\n");
|
||||
wprintf(L" h-strip or v-strip create a strip image from a cubemap\n");
|
||||
wprintf(L" merge create texture from rgb image and alpha image\n\n");
|
||||
wprintf(L" merge create texture from rgb image and alpha image\n");
|
||||
wprintf(L" gif create array from animated gif\n\n");
|
||||
wprintf(L" -r wildcard filename search is recursive\n");
|
||||
wprintf(L" -w <n> width\n");
|
||||
wprintf(L" -h <n> height\n");
|
||||
@ -502,6 +513,8 @@ namespace
|
||||
wprintf(L" -nologo suppress copyright message\n");
|
||||
wprintf(L" -tonemap Apply a tonemap operator based on maximum luminance\n");
|
||||
wprintf(L" -flist <filename> use text file with a list of input files (one per line)\n");
|
||||
wprintf(L"\n (gif only)\n");
|
||||
wprintf(L" -bgcolor Use background color instead of transparency\n");
|
||||
|
||||
wprintf(L"\n <format>: ");
|
||||
PrintList(13, g_pFormats);
|
||||
@ -532,6 +545,352 @@ namespace
|
||||
return SaveToWICFile(img, WIC_FLAGS_NONE, GetWICCodec(static_cast<WICCodecs>(fileType)), szOutputFile);
|
||||
}
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
DM_UNDEFINED = 0,
|
||||
DM_NONE = 1,
|
||||
DM_BACKGROUND = 2,
|
||||
DM_PREVIOUS = 3
|
||||
};
|
||||
|
||||
void FillRectangle(const Image& img, const RECT& destRect, uint32_t color)
|
||||
{
|
||||
RECT clipped =
|
||||
{
|
||||
(destRect.left < 0) ? 0 : destRect.left,
|
||||
(destRect.top < 0) ? 0 : destRect.top,
|
||||
(destRect.right > static_cast<long>(img.width)) ? static_cast<long>(img.width) : destRect.right,
|
||||
(destRect.bottom > static_cast<long>(img.height)) ? static_cast<long>(img.height) : destRect.bottom
|
||||
};
|
||||
|
||||
auto ptr = reinterpret_cast<uint8_t*>(img.pixels + clipped.top * img.rowPitch + clipped.left * sizeof(uint32_t));
|
||||
|
||||
for (long y = clipped.top; y < clipped.bottom; ++y)
|
||||
{
|
||||
auto pixelPtr = reinterpret_cast<uint32_t*>(ptr);
|
||||
for (long x = clipped.left; x < clipped.right; ++x)
|
||||
{
|
||||
*pixelPtr++ = color;
|
||||
}
|
||||
|
||||
ptr += img.rowPitch;
|
||||
}
|
||||
}
|
||||
|
||||
void BlendRectangle(const Image& composed, const Image& raw, const RECT& destRect)
|
||||
{
|
||||
using namespace DirectX::PackedVector;
|
||||
|
||||
RECT clipped =
|
||||
{
|
||||
(destRect.left < 0) ? 0 : destRect.left,
|
||||
(destRect.top < 0) ? 0 : destRect.top,
|
||||
(destRect.right > static_cast<long>(composed.width)) ? static_cast<long>(composed.width) : destRect.right,
|
||||
(destRect.bottom > static_cast<long>(composed.height)) ? static_cast<long>(composed.height) : destRect.bottom
|
||||
};
|
||||
|
||||
auto rawPtr = reinterpret_cast<uint8_t*>(raw.pixels);
|
||||
auto composedPtr = reinterpret_cast<uint8_t*>(composed.pixels + clipped.top * composed.rowPitch + clipped.left * sizeof(uint32_t));
|
||||
|
||||
for (long y = clipped.top; y < clipped.bottom; ++y)
|
||||
{
|
||||
auto srcPtr = reinterpret_cast<uint32_t*>(rawPtr);
|
||||
auto destPtr = reinterpret_cast<uint32_t*>(composedPtr);
|
||||
for (long x = clipped.left; x < clipped.right; ++x)
|
||||
{
|
||||
XMVECTOR a = XMLoadUByteN4(reinterpret_cast<const XMUBYTEN4*>(srcPtr++));
|
||||
XMVECTOR b = XMLoadUByteN4(reinterpret_cast<const XMUBYTEN4*>(destPtr));
|
||||
|
||||
XMVECTOR alpha = XMVectorSplatW(a);
|
||||
|
||||
XMVECTOR blended = XMVectorMultiply(a, alpha) + XMVectorMultiply(b, XMVectorSubtract(g_XMOne, alpha));
|
||||
|
||||
blended = XMVectorSelect(g_XMIdentityR3, blended, g_XMSelect1110);
|
||||
|
||||
XMStoreUByteN4(reinterpret_cast<XMUBYTEN4*>(destPtr++), blended);
|
||||
}
|
||||
|
||||
rawPtr += raw.rowPitch;
|
||||
composedPtr += composed.rowPitch;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT LoadAnimatedGif(const wchar_t* szFile, std::vector<std::unique_ptr<ScratchImage>>& loadedImages, bool usebgcolor)
|
||||
{
|
||||
// https://code.msdn.microsoft.com/windowsapps/Windows-Imaging-Component-65abbc6a/
|
||||
// http://www.imagemagick.org/Usage/anim_basics/#dispose
|
||||
|
||||
bool iswic2;
|
||||
auto pWIC = GetWICFactory(iswic2);
|
||||
if (!pWIC)
|
||||
return E_NOINTERFACE;
|
||||
|
||||
ComPtr<IWICBitmapDecoder> decoder;
|
||||
HRESULT hr = pWIC->CreateDecoderFromFilename(szFile, 0, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
{
|
||||
GUID containerFormat;
|
||||
hr = decoder->GetContainerFormat(&containerFormat);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (memcmp(&containerFormat, &GUID_ContainerFormatGif, sizeof(GUID)) != 0)
|
||||
{
|
||||
// This function only works for GIF
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
ComPtr<IWICMetadataQueryReader> metareader;
|
||||
hr = decoder->GetMetadataQueryReader(metareader.GetAddressOf());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
PROPVARIANT propValue;
|
||||
PropVariantInit(&propValue);
|
||||
|
||||
// Get background color
|
||||
UINT bgColor = 0;
|
||||
if (usebgcolor)
|
||||
{
|
||||
// Most browsers just ignore the background color metadata and always use transparency
|
||||
hr = metareader->GetMetadataByName(L"/logscrdesc/GlobalColorTableFlag", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
bool hasTable = (propValue.vt == VT_BOOL && propValue.boolVal);
|
||||
PropVariantClear(&propValue);
|
||||
|
||||
if (hasTable)
|
||||
{
|
||||
hr = metareader->GetMetadataByName(L"/logscrdesc/BackgroundColorIndex", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (propValue.vt == VT_UI1)
|
||||
{
|
||||
uint8_t index = propValue.bVal;
|
||||
|
||||
ComPtr<IWICPalette> palette;
|
||||
hr = pWIC->CreatePalette(palette.GetAddressOf());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
hr = decoder->CopyPalette(palette.Get());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
WICColor rgColors[256];
|
||||
UINT actualColors = 0;
|
||||
hr = palette->GetColors(_countof(rgColors), rgColors, &actualColors);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (index < actualColors)
|
||||
{
|
||||
bgColor = rgColors[index];
|
||||
}
|
||||
}
|
||||
PropVariantClear(&propValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get global frame size
|
||||
UINT width = 0;
|
||||
UINT height = 0;
|
||||
|
||||
hr = metareader->GetMetadataByName(L"/logscrdesc/Width", &propValue);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (propValue.vt != VT_UI2)
|
||||
return E_FAIL;
|
||||
|
||||
width = propValue.uiVal;
|
||||
PropVariantClear(&propValue);
|
||||
|
||||
hr = metareader->GetMetadataByName(L"/logscrdesc/Height", &propValue);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (propValue.vt != VT_UI2)
|
||||
return E_FAIL;
|
||||
|
||||
height = propValue.uiVal;
|
||||
PropVariantClear(&propValue);
|
||||
|
||||
UINT fcount;
|
||||
hr = decoder->GetFrameCount(&fcount);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
UINT disposal = DM_UNDEFINED;
|
||||
RECT rct = {};
|
||||
|
||||
UINT previousFrame = 0;
|
||||
for (UINT iframe = 0; iframe < fcount; ++iframe)
|
||||
{
|
||||
std::unique_ptr<ScratchImage> frameImage(new (std::nothrow) ScratchImage);
|
||||
if (!frameImage)
|
||||
return E_OUTOFMEMORY;
|
||||
|
||||
if (disposal == DM_PREVIOUS)
|
||||
{
|
||||
hr = frameImage->InitializeFromImage(*loadedImages[previousFrame]->GetImage(0, 0, 0));
|
||||
}
|
||||
else if (iframe > 0)
|
||||
{
|
||||
hr = frameImage->InitializeFromImage(*loadedImages[iframe-1]->GetImage(0, 0, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
hr = frameImage->Initialize2D(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1);
|
||||
}
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
auto composedImage = frameImage->GetImage(0, 0, 0);
|
||||
|
||||
if (!iframe)
|
||||
{
|
||||
RECT fullRct = { 0, 0, static_cast<long>(width), static_cast<long>(height) };
|
||||
FillRectangle(*composedImage, fullRct, bgColor);
|
||||
}
|
||||
else if (disposal == DM_BACKGROUND)
|
||||
{
|
||||
FillRectangle(*composedImage, rct, bgColor);
|
||||
}
|
||||
|
||||
ComPtr<IWICBitmapFrameDecode> frame;
|
||||
hr = decoder->GetFrame(iframe, frame.GetAddressOf());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
WICPixelFormatGUID pixelFormat;
|
||||
hr = frame->GetPixelFormat(&pixelFormat);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (memcmp(&pixelFormat, &GUID_WICPixelFormat8bppIndexed, sizeof(GUID)) != 0)
|
||||
{
|
||||
// GIF is always loaded as this format
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
ComPtr<IWICMetadataQueryReader> frameMeta;
|
||||
hr = frame->GetMetadataQueryReader(frameMeta.GetAddressOf());
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = frameMeta->GetMetadataByName(L"/imgdesc/Left", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
rct.left = static_cast<long>(propValue.uiVal);
|
||||
}
|
||||
PropVariantClear(&propValue);
|
||||
}
|
||||
|
||||
hr = frameMeta->GetMetadataByName(L"/imgdesc/Top", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
rct.top = static_cast<long>(propValue.uiVal);
|
||||
}
|
||||
PropVariantClear(&propValue);
|
||||
}
|
||||
|
||||
hr = frameMeta->GetMetadataByName(L"/imgdesc/Width", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
rct.right = static_cast<long>(propValue.uiVal) + rct.left;
|
||||
}
|
||||
PropVariantClear(&propValue);
|
||||
}
|
||||
|
||||
hr = frameMeta->GetMetadataByName(L"/imgdesc/Height", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
rct.bottom = static_cast<long>(propValue.uiVal) + rct.top;
|
||||
}
|
||||
PropVariantClear(&propValue);
|
||||
}
|
||||
|
||||
disposal = DM_UNDEFINED;
|
||||
hr = frameMeta->GetMetadataByName(L"/grctlext/Disposal", &propValue);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = (propValue.vt == VT_UI1 ? S_OK : E_FAIL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
disposal = propValue.bVal;
|
||||
}
|
||||
PropVariantClear(&propValue);
|
||||
}
|
||||
}
|
||||
|
||||
UINT w, h;
|
||||
hr = frame->GetSize(&w, &h);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
ScratchImage rawFrame;
|
||||
hr = rawFrame.Initialize2D(DXGI_FORMAT_B8G8R8A8_UNORM, w, h, 1, 1);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
ComPtr<IWICFormatConverter> FC;
|
||||
hr = pWIC->CreateFormatConverter(FC.GetAddressOf());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
hr = FC->Initialize(frame.Get(), GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeMedianCut);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
auto img = rawFrame.GetImage(0, 0, 0);
|
||||
|
||||
hr = FC->CopyPixels(0, static_cast<UINT>(img->rowPitch), static_cast<UINT>(img->slicePitch), img->pixels);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (!iframe)
|
||||
{
|
||||
Rect fullRect(0, 0, img->width, img->height);
|
||||
|
||||
hr = CopyRectangle(*img, fullRect, *composedImage, TEX_FILTER_DEFAULT, rct.left, rct.top);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
}
|
||||
else
|
||||
{
|
||||
BlendRectangle(*composedImage, *img, rct);
|
||||
}
|
||||
|
||||
if (disposal == DM_UNDEFINED || disposal == DM_NONE)
|
||||
{
|
||||
previousFrame = iframe;
|
||||
}
|
||||
|
||||
loadedImages.emplace_back(std::move(frameImage));
|
||||
}
|
||||
|
||||
PropVariantClear(&propValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -581,10 +940,11 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
case CMD_H_STRIP:
|
||||
case CMD_V_STRIP:
|
||||
case CMD_MERGE:
|
||||
case CMD_GIF:
|
||||
break;
|
||||
|
||||
default:
|
||||
wprintf(L"Must use one of: cube, volume, array, cubearray,\n h-cross, v-cross, h-strip, v-strip, or merge\n\n");
|
||||
wprintf(L"Must use one of: cube, volume, array, cubearray,\n h-cross, v-cross, h-strip, v-strip\n merge, gif\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -778,6 +1138,14 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
inFile.close();
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GIF_BGCOLOR:
|
||||
if (dwCommand != CMD_GIF)
|
||||
{
|
||||
wprintf(L"-bgcolor only applies to gif command\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (wcspbrk(pArg, L"?*") != nullptr)
|
||||
@ -814,9 +1182,10 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
case CMD_V_CROSS:
|
||||
case CMD_H_STRIP:
|
||||
case CMD_V_STRIP:
|
||||
case CMD_GIF:
|
||||
if (conversion.size() > 1)
|
||||
{
|
||||
wprintf(L"ERROR: cross/strip output only accepts 1 input file\n");
|
||||
wprintf(L"ERROR: cross/strip/gif output only accepts 1 input file\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
@ -835,6 +1204,29 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
|
||||
std::vector<std::unique_ptr<ScratchImage>> loadedImages;
|
||||
|
||||
if (dwCommand == CMD_GIF)
|
||||
{
|
||||
wchar_t ext[_MAX_EXT];
|
||||
wchar_t fname[_MAX_FNAME];
|
||||
_wsplitpath_s(conversion.front().szSrc, nullptr, 0, nullptr, 0, fname, _MAX_FNAME, ext, _MAX_EXT);
|
||||
|
||||
wprintf(L"reading %ls", conversion.front().szSrc);
|
||||
fflush(stdout);
|
||||
|
||||
if (!*szOutputFile)
|
||||
{
|
||||
_wmakepath_s(szOutputFile, nullptr, nullptr, fname, L".dds");
|
||||
}
|
||||
|
||||
hr = LoadAnimatedGif(conversion.front().szSrc, loadedImages, (dwOptions & (1 << OPT_GIF_BGCOLOR)) != 0);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
wprintf(L" FAILED (%x)\n", hr);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto pConv = conversion.begin(); pConv != conversion.end(); ++pConv)
|
||||
{
|
||||
wchar_t ext[_MAX_EXT];
|
||||
@ -946,7 +1338,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#ifdef USE_OPENEXR
|
||||
#ifdef USE_OPENEXR
|
||||
else if (_wcsicmp(ext, L".exr") == 0)
|
||||
{
|
||||
hr = LoadFromEXRFile(pConv->szSrc, &info, *image);
|
||||
@ -956,7 +1348,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// WIC shares the same filter values for mode and dither
|
||||
@ -1260,7 +1652,8 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
}
|
||||
|
||||
images += info.arraySize;
|
||||
loadedImages.push_back(std::move(image));
|
||||
loadedImages.emplace_back(std::move(image));
|
||||
}
|
||||
}
|
||||
|
||||
switch (dwCommand)
|
||||
@ -1285,6 +1678,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
case CMD_V_CROSS:
|
||||
case CMD_H_STRIP:
|
||||
case CMD_V_STRIP:
|
||||
case CMD_GIF:
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1544,6 +1938,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
||||
break;
|
||||
|
||||
case CMD_ARRAY:
|
||||
case CMD_GIF:
|
||||
hr = result.InitializeArrayFromImages(&imageArray[0], imageArray.size(), (dwOptions & (1 << OPT_USE_DX10)) != 0);
|
||||
break;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user