590 lines
19 KiB
C++
590 lines
19 KiB
C++
// Copyright (C) 2021-2022, Dmitry Maluev (dmaluev@gmail.com). All rights reserved.
|
|
#define VERUS_INCLUDE_COMPRESSONATOR
|
|
#include <verus.h>
|
|
|
|
using namespace verus;
|
|
|
|
class TextureTool
|
|
{
|
|
struct CursorGuard
|
|
{
|
|
CONSOLE_CURSOR_INFO _defaultCursorInfo;
|
|
CursorGuard()
|
|
{
|
|
GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &_defaultCursorInfo);
|
|
CONSOLE_CURSOR_INFO info = {};
|
|
info.dwSize = 100;
|
|
info.bVisible = FALSE;
|
|
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
|
|
}
|
|
~CursorGuard()
|
|
{
|
|
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &_defaultCursorInfo);
|
|
}
|
|
};
|
|
|
|
enum class TexMode : int
|
|
{
|
|
automatic,
|
|
color,
|
|
nonColor,
|
|
a,
|
|
n,
|
|
x,
|
|
all
|
|
};
|
|
|
|
String _pathname;
|
|
TexMode _texMode = TexMode::automatic;
|
|
bool _asIs = false;
|
|
bool _edgePaddingUsingAlpha = false;
|
|
bool _edgePaddingUsingFaces = false;
|
|
bool _deleteMip = false;
|
|
|
|
static CMP_BOOL FeedbackProc(CMP_FLOAT progress, CMP_DWORD_PTR pUser1, CMP_DWORD_PTR pUser2);
|
|
|
|
public:
|
|
TextureTool();
|
|
~TextureTool();
|
|
|
|
void Main(VERUS_MAIN_DEFAULT_ARGS);
|
|
void ParseCommandLine(VERUS_MAIN_DEFAULT_ARGS);
|
|
void PrintUsage();
|
|
void ConvertTexture(TexMode currentTexMode);
|
|
void ComputeEdgePadding();
|
|
};
|
|
|
|
CMP_BOOL TextureTool::FeedbackProc(CMP_FLOAT progress, CMP_DWORD_PTR pUser1, CMP_DWORD_PTR pUser2)
|
|
{
|
|
const int edge = static_cast<int>(progress * 0.5f + 0.5f);
|
|
wchar_t buffer[80];
|
|
wcscpy(buffer, _T("\rCompressing ["));
|
|
size_t offset = wcslen(buffer);
|
|
VERUS_FOR(i, 50)
|
|
{
|
|
if (i < edge)
|
|
buffer[i + offset] = _T('=');
|
|
else
|
|
buffer[i + offset] = _T(' ');
|
|
}
|
|
buffer[offset + 50] = 0;
|
|
wsprintf(buffer + wcslen(buffer), _T("] %3d%%"), static_cast<int>(progress + 0.5f));
|
|
wprintf(buffer);
|
|
fflush(stdout);
|
|
return false;
|
|
}
|
|
|
|
TextureTool::TextureTool()
|
|
{
|
|
}
|
|
|
|
TextureTool::~TextureTool()
|
|
{
|
|
}
|
|
|
|
void TextureTool::Main(VERUS_MAIN_DEFAULT_ARGS)
|
|
{
|
|
if (argc <= 1)
|
|
{
|
|
PrintUsage();
|
|
return;
|
|
}
|
|
ParseCommandLine(argc, argv);
|
|
|
|
if (_edgePaddingUsingAlpha || _edgePaddingUsingFaces)
|
|
{
|
|
ComputeEdgePadding();
|
|
}
|
|
else if (_deleteMip)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
std::wcout << _T("Mode: ");
|
|
switch (_texMode)
|
|
{
|
|
case TexMode::automatic: std::wcout << _T("automatic"); break;
|
|
case TexMode::color: std::wcout << _T("color"); break;
|
|
case TexMode::nonColor: std::wcout << _T("non-color"); break;
|
|
case TexMode::a: std::wcout << _T("a"); break;
|
|
case TexMode::n: std::wcout << _T("n"); break;
|
|
case TexMode::x: std::wcout << _T("x"); break;
|
|
case TexMode::all: std::wcout << _T("all"); break;
|
|
}
|
|
std::wcout << std::endl;
|
|
|
|
if (TexMode::automatic == _texMode)
|
|
{
|
|
const String ext = Str::GetExtension(_C(_pathname));
|
|
if (Str::EndsWith(_C(_pathname), _C(".X." + ext)))
|
|
ConvertTexture(TexMode::nonColor);
|
|
else
|
|
ConvertTexture(TexMode::color);
|
|
}
|
|
else if (TexMode::all == _texMode)
|
|
{
|
|
const String pathname = _pathname;
|
|
const String ext = Str::GetExtension(_C(pathname));
|
|
|
|
ConvertTexture(TexMode::a);
|
|
_pathname = pathname;
|
|
Str::ReplaceExtension(_pathname, ".N.png");
|
|
ConvertTexture(TexMode::n);
|
|
_pathname = pathname;
|
|
Str::ReplaceExtension(_pathname, _C(".X." + ext));
|
|
ConvertTexture(TexMode::x);
|
|
}
|
|
else
|
|
{
|
|
ConvertTexture(_texMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextureTool::ParseCommandLine(VERUS_MAIN_DEFAULT_ARGS)
|
|
{
|
|
VERUS_FOR(i, argc)
|
|
{
|
|
if (!strcmp(argv[i], "--tex-auto"))
|
|
_texMode = TexMode::automatic;
|
|
else if (!strcmp(argv[i], "--tex-color"))
|
|
_texMode = TexMode::color;
|
|
else if (!strcmp(argv[i], "--tex-non-color"))
|
|
_texMode = TexMode::nonColor;
|
|
else if (!strcmp(argv[i], "--tex-a"))
|
|
_texMode = TexMode::a;
|
|
else if (!strcmp(argv[i], "--tex-n"))
|
|
_texMode = TexMode::n;
|
|
else if (!strcmp(argv[i], "--tex-x"))
|
|
_texMode = TexMode::x;
|
|
else if (!strcmp(argv[i], "--tex-all"))
|
|
_texMode = TexMode::all;
|
|
else if (!strcmp(argv[i], "--as-is"))
|
|
_asIs = true;
|
|
else if (!strcmp(argv[i], "--edge-padding-using-alpha") || !strcmp(argv[i], "-epua"))
|
|
_edgePaddingUsingAlpha = true;
|
|
else if (!strcmp(argv[i], "--edge-padding-using-faces") || !strcmp(argv[i], "-epuf"))
|
|
_edgePaddingUsingFaces = true;
|
|
else if (!strcmp(argv[i], "--delete-mip"))
|
|
_deleteMip = true;
|
|
else if (i > 0 && i < argc - 1)
|
|
{
|
|
std::wcout << _T("WARNING: Unknown argument ") << Str::Utf8ToWide(argv[i]) << std::endl;
|
|
std::cin.ignore();
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
int argCount = 0;
|
|
LPWSTR* argArray = CommandLineToArgvW(GetCommandLine(), &argCount);
|
|
_pathname = Str::WideToUtf8(argArray[argCount - 1]);
|
|
LocalFree(argArray);
|
|
#endif
|
|
}
|
|
|
|
void TextureTool::PrintUsage()
|
|
{
|
|
std::wcout << _T("Usage: TextureTool [options] <file>") << std::endl;
|
|
std::wcout << std::endl;
|
|
std::wcout << _T("TextureTool 2. Powered by Compressonator.") << std::endl;
|
|
std::wcout << _T("Preferred output formats are DDS/BC5 for normal map and DDS/BC7 for everything else.") << std::endl;
|
|
std::wcout << _T("Create a batch file with similar content to convert multiple files:") << std::endl;
|
|
std::wcout << _T(" for %%f in (*.*) do TextureTool %%f") << std::endl;
|
|
std::wcout << std::endl;
|
|
std::wcout << _T("Options:") << std::endl;
|
|
std::wcout << _T(" --tex-auto Use filename to select between color and non-color data (default).") << std::endl;
|
|
std::wcout << _T(" --tex-color Convert texture with color (sRGB).") << std::endl;
|
|
std::wcout << _T(" --tex-non-color Convert texture with non-color data (linear).") << std::endl;
|
|
std::wcout << _T(" --tex-a Convert {Albedo.rgb, Alpha} texture.") << std::endl;
|
|
std::wcout << _T(" --tex-n Convert Normal Map texture.") << std::endl;
|
|
std::wcout << _T(" --tex-x Convert {Occlusion, Roughness, Metallic, Emission} texture.") << std::endl;
|
|
std::wcout << _T(" --tex-all Convert all textures.") << std::endl;
|
|
std::wcout << _T(" --as-is Don't use block compression.") << std::endl;
|
|
std::wcout << _T(" --edge-padding-using-alpha, -epua Add edge padding to an image with alpha.") << std::endl;
|
|
std::wcout << _T(" --edge-padding-using-faces, -epuf Add edge padding to an image, take alpha from Faces.png.") << std::endl;
|
|
std::wcout << _T(" --delete-mip Delete topmost mip level.") << std::endl;
|
|
}
|
|
|
|
void TextureTool::ConvertTexture(TexMode currentTexMode)
|
|
{
|
|
std::wcout << _T("Converting texture: ");
|
|
switch (currentTexMode)
|
|
{
|
|
case TexMode::color: std::wcout << _T("COLOR"); break;
|
|
case TexMode::nonColor: std::wcout << _T("NON-COLOR"); break;
|
|
case TexMode::a: std::wcout << _T("A (Albedo, Alpha)"); break;
|
|
case TexMode::n: std::wcout << _T("N (Normal Map)"); break;
|
|
case TexMode::x: std::wcout << _T("X (Occlusion, Roughness, Metallic, Emission)"); break;
|
|
default:
|
|
std::wcerr << _T("ERROR: Unknown mode.") << std::endl;
|
|
throw std::exception();
|
|
}
|
|
std::wcout << std::endl;
|
|
std::wcout << _T("Location: ") << Str::Utf8ToWide(_pathname) << std::endl;
|
|
|
|
CMP_ERROR cmpError = CMP_OK;
|
|
|
|
CMP_MipSet srcMipSet = {};
|
|
cmpError = CMP_LoadTexture(_C(_pathname), &srcMipSet);
|
|
if (CMP_OK != cmpError)
|
|
{
|
|
std::wcerr << _T("ERROR: CMP_LoadTexture(); ") << cmpError << std::endl;
|
|
throw std::exception();
|
|
}
|
|
|
|
if (srcMipSet.m_nMipLevels <= 1)
|
|
CMP_GenerateMIPLevels(&srcMipSet, 0);
|
|
|
|
CMP_MipLevel* pMipLevel = nullptr;
|
|
CMP_GetMipLevel(&pMipLevel, &srcMipSet, 0, 0);
|
|
const BYTE* pData = pMipLevel->m_pbData;
|
|
const int pixelCount = srcMipSet.m_nWidth * srcMipSet.m_nHeight;
|
|
|
|
bool normalMapToRoughness = false;
|
|
const int bytesPerPixel = sizeof(UINT32);
|
|
Vector<UINT32> vMip[4];
|
|
vMip[0].resize(pixelCount);
|
|
vMip[1].resize(pixelCount);
|
|
if (TexMode::x == currentTexMode)
|
|
{
|
|
String pathname = _pathname;
|
|
Str::ReplaceExtension(pathname, "");
|
|
Str::ReplaceExtension(pathname, ".N.TempR.tga");
|
|
if (IO::FileSystem::FileExist(_C(pathname)))
|
|
{
|
|
IO::Image image;
|
|
image.Init(_C(pathname));
|
|
if (image._width == srcMipSet.m_nWidth && image._height == srcMipSet.m_nHeight)
|
|
{
|
|
vMip[2].resize(pixelCount);
|
|
vMip[3].resize(pixelCount);
|
|
memcpy(vMip[3].data(), image._p, vMip[3].size() * bytesPerPixel);
|
|
normalMapToRoughness = true;
|
|
}
|
|
else
|
|
{
|
|
std::wcout << _T("WARNING: Normal map for roughness has different size.") << std::endl;
|
|
std::cin.ignore();
|
|
}
|
|
}
|
|
|
|
if (!normalMapToRoughness)
|
|
{
|
|
std::wcout << _T("WARNING: Normal map for roughness not found.") << std::endl;
|
|
std::cin.ignore();
|
|
}
|
|
}
|
|
|
|
const bool sRGB = (TexMode::color == currentTexMode) || (TexMode::a == currentTexMode);
|
|
bool normWarning = false;
|
|
int mip = 0;
|
|
|
|
bool finish = false;
|
|
do
|
|
{
|
|
const int mipW = Math::Max(1, srcMipSet.m_nWidth >> mip);
|
|
const int mipH = Math::Max(1, srcMipSet.m_nHeight >> mip);
|
|
|
|
std::wcout << _T("Processing mip ") << std::setw(2) << mip << _T(" [");
|
|
const int length = Math::HighestBit(mipW) + 1;
|
|
VERUS_FOR(i, length)
|
|
std::wcout << _T("=");
|
|
std::wcout << _T("]") << std::endl;
|
|
|
|
// Switch between in and out:
|
|
const int inIndex = (mip & 0x1);
|
|
const int outIndex = ((mip + 1) & 0x1);
|
|
|
|
VERUS_FOR(i, mipH)
|
|
{
|
|
const int offsetH = i * mipW;
|
|
VERUS_FOR(j, mipW)
|
|
{
|
|
int offset = offsetH;
|
|
UINT32 color = 0;
|
|
UINT32 color2 = 0;
|
|
|
|
if (mip) // Generate mip level 1+:
|
|
{
|
|
offset += j;
|
|
|
|
const int srcMipW = mipW << 1;
|
|
const int srcMipH = mipH << 1;
|
|
const int srcMipEdgeW = srcMipW - 1;
|
|
const int srcMipEdgeH = srcMipH - 1;
|
|
|
|
const int coordA[2] = { Math::Clamp((j << 1) + 0, 0, srcMipEdgeW), Math::Clamp((i << 1) + 0, 0, srcMipEdgeH) };
|
|
const int coordB[2] = { Math::Clamp((j << 1) + 1, 0, srcMipEdgeW), Math::Clamp((i << 1) + 0, 0, srcMipEdgeH) };
|
|
const int coordC[2] = { Math::Clamp((j << 1) + 0, 0, srcMipEdgeW), Math::Clamp((i << 1) + 1, 0, srcMipEdgeH) };
|
|
const int coordD[2] = { Math::Clamp((j << 1) + 1, 0, srcMipEdgeW), Math::Clamp((i << 1) + 1, 0, srcMipEdgeH) };
|
|
|
|
const int offsetA = coordA[1] * srcMipW + coordA[0];
|
|
const int offsetB = coordB[1] * srcMipW + coordB[0];
|
|
const int offsetC = coordC[1] * srcMipW + coordC[0];
|
|
const int offsetD = coordD[1] * srcMipW + coordD[0];
|
|
|
|
BYTE computedRoughness = 0;
|
|
if (TexMode::n == currentTexMode || normalMapToRoughness) // N or X:
|
|
{
|
|
// Compute average normal:
|
|
const int inIndexEx = normalMapToRoughness ? inIndex + 2 : inIndex;
|
|
Vector4 data[4];
|
|
float floats[4];
|
|
Convert::ColorInt32ToFloat(vMip[inIndexEx][offsetA], floats, false); data[0] = Vector4::MakeFromPointer(floats);
|
|
Convert::ColorInt32ToFloat(vMip[inIndexEx][offsetB], floats, false); data[1] = Vector4::MakeFromPointer(floats);
|
|
Convert::ColorInt32ToFloat(vMip[inIndexEx][offsetC], floats, false); data[2] = Vector4::MakeFromPointer(floats);
|
|
Convert::ColorInt32ToFloat(vMip[inIndexEx][offsetD], floats, false); data[3] = Vector4::MakeFromPointer(floats);
|
|
|
|
VERUS_FOR(i, 4) // To [-1 to 1]:
|
|
{
|
|
data[i] = data[i] * 2.f - Vector4::Replicate(1);
|
|
data[i].setW(0);
|
|
}
|
|
const Vector4 averageNormal = VMath::normalize(data[0] + data[1] + data[2] + data[3]);
|
|
Vector4 computedData = averageNormal;
|
|
computedData = computedData * 0.5f + Vector4::Replicate(0.5f); // To [0 to 1]:
|
|
computedData.setW(0);
|
|
color = Convert::ColorFloatToInt32(computedData.ToPointer(), false);
|
|
|
|
// Compute roughness from normal:
|
|
const bool saveRoughnessToAlpha = TexMode::n == currentTexMode && 1 == mip;
|
|
if (normalMapToRoughness || saveRoughnessToAlpha)
|
|
{
|
|
color2 = color;
|
|
float averageRoughness = 0;
|
|
VERUS_FOR(i, 4)
|
|
{
|
|
const float d = VMath::dot(averageNormal, data[i]);
|
|
const float bias = 0.85f;
|
|
const float scale = 1.f / (1.f - 0.85f);
|
|
averageRoughness += 1.f - Math::Clamp<float>((d - bias) * scale, 0, 1);
|
|
}
|
|
averageRoughness = Math::Clamp<float>(averageRoughness * 0.25f, 0, 1);
|
|
computedRoughness = Convert::UnormToUint8(averageRoughness);
|
|
|
|
if (saveRoughnessToAlpha)
|
|
{
|
|
BYTE* pChannels = reinterpret_cast<BYTE*>(&color);
|
|
pChannels[3] = computedRoughness;
|
|
}
|
|
}
|
|
}
|
|
if (TexMode::n != currentTexMode)
|
|
{
|
|
// Compute average value:
|
|
Vector4 data[4];
|
|
float floats[4];
|
|
Convert::ColorInt32ToFloat(vMip[inIndex][offsetA], floats, sRGB); data[0] = Vector4::MakeFromPointer(floats);
|
|
Convert::ColorInt32ToFloat(vMip[inIndex][offsetB], floats, sRGB); data[1] = Vector4::MakeFromPointer(floats);
|
|
Convert::ColorInt32ToFloat(vMip[inIndex][offsetC], floats, sRGB); data[2] = Vector4::MakeFromPointer(floats);
|
|
Convert::ColorInt32ToFloat(vMip[inIndex][offsetD], floats, sRGB); data[3] = Vector4::MakeFromPointer(floats);
|
|
|
|
Vector4 computedData = (data[0] + data[1] + data[2] + data[3]) * 0.25f;
|
|
color = Convert::ColorFloatToInt32(computedData.ToPointer(), sRGB);
|
|
|
|
if (normalMapToRoughness) // For X:
|
|
{
|
|
BYTE* pChannels = reinterpret_cast<BYTE*>(&color);
|
|
pChannels[1] = Math::Max(pChannels[1], computedRoughness);
|
|
}
|
|
}
|
|
}
|
|
else // Handle topmost mip level:
|
|
{
|
|
offset += j;
|
|
UINT32 input = 0;
|
|
memcpy(&input, &pData[offset * bytesPerPixel], bytesPerPixel);
|
|
// Normalize source normal map:
|
|
if (TexMode::n == currentTexMode)
|
|
{
|
|
Vector4 data;
|
|
float floats[4];
|
|
Convert::ColorInt32ToFloat(input, floats, false); data = Vector4::MakeFromPointer(floats);
|
|
data = data * 2.f - Vector4::Replicate(1); // To [-1 to 1]:
|
|
data.setW(0);
|
|
const float length = VMath::length(data);
|
|
if (abs(length - 1.f) >= 0.01f)
|
|
normWarning = true;
|
|
data /= length;
|
|
data = data * 0.5f + Vector4::Replicate(0.5f); // To [0 to 1]:
|
|
data.setW(0);
|
|
input = Convert::ColorFloatToInt32(data.ToPointer(), false);
|
|
}
|
|
// Add computed roughness from normal map:
|
|
if (normalMapToRoughness)
|
|
{
|
|
const BYTE computedRoughness = vMip[outIndex + 2][offset] >> 24;
|
|
BYTE* pChannels = reinterpret_cast<BYTE*>(&input);
|
|
pChannels[1] = Math::Max(pChannels[1], computedRoughness);
|
|
}
|
|
color = input;
|
|
}
|
|
|
|
vMip[outIndex][offset] = color;
|
|
if (mip && normalMapToRoughness)
|
|
vMip[outIndex + 2][offset] = color2;
|
|
}
|
|
}
|
|
|
|
// Copy to CMP_MipLevel:
|
|
CMP_MipLevel* pDstMipLevel = nullptr;
|
|
CMP_GetMipLevel(&pDstMipLevel, &srcMipSet, mip, 0);
|
|
memcpy(pDstMipLevel->m_pbData, vMip[outIndex].data(), mipW * mipH * bytesPerPixel);
|
|
|
|
finish = (mipW == 1) && (mipH == 1);
|
|
mip++; // Next level, switch between in and out.
|
|
|
|
if (2 == mip && TexMode::n == currentTexMode)
|
|
{
|
|
String pathname = _pathname;
|
|
Str::ReplaceExtension(pathname, ".TempR.tga");
|
|
IO::FileSystem::SaveImage(_C(pathname), vMip[outIndex].data(), mipW, mipH);
|
|
std::wcout << _T("Saved normal map for roughness as: ") << Str::Utf8ToWide(pathname) << std::endl;
|
|
}
|
|
} while (!finish);
|
|
|
|
if (normWarning)
|
|
{
|
|
std::wcout << _T("WARNING: Source normal map was not normalized.") << std::endl;
|
|
std::cin.ignore();
|
|
}
|
|
|
|
Str::ReplaceExtension(_pathname, ".dds");
|
|
if (_asIs)
|
|
{
|
|
cmpError = CMP_SaveTexture(_C(_pathname), &srcMipSet);
|
|
if (CMP_OK != cmpError)
|
|
{
|
|
std::wcerr << _T("ERROR: CMP_SaveTexture(); ") << cmpError << std::endl;
|
|
throw std::exception();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CMP_MipSet dstMipSet = {};
|
|
|
|
KernelOptions kernelOptions = {};
|
|
if (TexMode::n == currentTexMode)
|
|
{
|
|
kernelOptions.format = CMP_FORMAT_BC5;
|
|
kernelOptions.fquality = 0.8f; // Use 80% for BC5.
|
|
}
|
|
else
|
|
{
|
|
kernelOptions.format = CMP_FORMAT_BC7;
|
|
kernelOptions.fquality = 0.6f; // Use 60% for BC7.
|
|
}
|
|
|
|
{
|
|
CursorGuard cursorGuard;
|
|
cmpError = CMP_ProcessTexture(&srcMipSet, &dstMipSet, kernelOptions, FeedbackProc);
|
|
std::wcout << std::endl;
|
|
}
|
|
if (CMP_OK != cmpError)
|
|
{
|
|
std::wcerr << _T("ERROR: CMP_ProcessTexture(), ") << cmpError << std::endl;
|
|
throw std::exception();
|
|
}
|
|
|
|
cmpError = CMP_SaveTexture(_C(_pathname), &dstMipSet);
|
|
if (CMP_OK != cmpError)
|
|
{
|
|
std::wcerr << _T("ERROR: CMP_SaveTexture(); ") << cmpError << std::endl;
|
|
throw std::exception();
|
|
}
|
|
|
|
CMP_FreeMipSet(&dstMipSet);
|
|
}
|
|
CMP_FreeMipSet(&srcMipSet);
|
|
|
|
std::wcout << _T("Saved as: ") << Str::Utf8ToWide(_pathname) << std::endl;
|
|
}
|
|
|
|
void TextureTool::ComputeEdgePadding()
|
|
{
|
|
if (!IO::FileSystem::FileExist(_C(_pathname)))
|
|
{
|
|
std::wcerr << _T("ERROR: File not found: ") << Str::Utf8ToWide(_pathname) << std::endl;
|
|
throw std::exception();
|
|
}
|
|
IO::Image image;
|
|
image.Init(_C(_pathname));
|
|
|
|
IO::Image imageFaces;
|
|
if (_edgePaddingUsingFaces)
|
|
{
|
|
Str::ReplaceFilename(_pathname, "Faces.png");
|
|
if (!IO::FileSystem::FileExist(_C(_pathname)))
|
|
{
|
|
std::wcerr << _T("ERROR: File not found: ") << Str::Utf8ToWide(_pathname) << std::endl;
|
|
throw std::exception();
|
|
}
|
|
imageFaces.Init(_C(_pathname));
|
|
if (image._width != imageFaces._width || image._height != imageFaces._height)
|
|
{
|
|
std::wcerr << _T("ERROR: Faces image has different size.") << std::endl;
|
|
throw std::exception();
|
|
}
|
|
}
|
|
|
|
if (_edgePaddingUsingAlpha)
|
|
Utils::ComputeEdgePadding(image._p, image._pixelStride, image._p + 3, image._pixelStride, image._width, image._height, 0, 3);
|
|
else if (_edgePaddingUsingFaces)
|
|
Utils::ComputeEdgePadding(image._p, image._pixelStride, imageFaces._p, imageFaces._pixelStride, image._width, image._height, 0, 4);
|
|
|
|
Str::ReplaceFilename(_pathname, "ComputedEdgePadding.tga");
|
|
IO::FileSystem::SaveImage(_C(_pathname), reinterpret_cast<UINT32*>(image._p), image._width, image._height, IO::ImageFormat::tga, image._pixelStride);
|
|
}
|
|
|
|
void DeleteMipmap(CWSZ pathname)
|
|
{
|
|
const String pathnameIn = Str::WideToUtf8(pathname);
|
|
const String pathnameOut = pathnameIn + "-del-mip.dds";
|
|
IO::File file, fileOut;
|
|
if (file.Open(_C(pathnameIn)) && file.GetSize() > sizeof(IO::DDSHeader) && fileOut.Open(_C(pathnameOut), "wb"))
|
|
{
|
|
IO::DDSHeader header;
|
|
file >> header;
|
|
if (!header.Validate())
|
|
{
|
|
std::wcerr << _T("ERROR: Invalid DDS header: ") << Str::Utf8ToWide(pathnameIn) << std::endl;
|
|
throw std::exception();
|
|
}
|
|
if (header.IsBC1() && header._mipMapCount > 1)
|
|
{
|
|
const int levelSize = IO::DDSHeader::ComputeBcLevelSize(header._width, header._height, true);
|
|
|
|
header._mipMapCount--;
|
|
header._width >>= 1;
|
|
header._height >>= 1;
|
|
header._pitchOrLinearSize = IO::DDSHeader::ComputeBcLevelSize(header._width, header._height, true);
|
|
|
|
fileOut << header;
|
|
file.Seek(sizeof(IO::DDSHeader) + levelSize, SEEK_SET);
|
|
Vector<BYTE> vData;
|
|
vData.resize(file.GetSize() - levelSize - sizeof(IO::DDSHeader));
|
|
file.Read(vData.data(), vData.size());
|
|
fileOut.Write(vData.data(), vData.size());
|
|
}
|
|
else
|
|
{
|
|
std::wcerr << _T("ERROR: Must be DXT1 with mipmaps") << std::endl;
|
|
throw std::exception();
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(VERUS_MAIN_DEFAULT_ARGS)
|
|
{
|
|
try
|
|
{
|
|
TextureTool textureTool;
|
|
textureTool.Main(argc, argv);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::wcerr << _T("EXCEPTION: ") << e.what() << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|