From 8dda07bf2c19f9ebd8ab499ca2c3849d7b1f031f Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Fri, 7 Dec 2018 15:21:12 -0800 Subject: [PATCH] mipgen: support generation of 4-band KTX files This is useful because our Vulkan backend currently does not accept 3-band texture data. (although we intend to fix that soon) --- libs/image/include/image/ColorTransform.h | 22 ++++----- libs/image/include/image/ImageOps.h | 3 ++ libs/image/src/ImageOps.cpp | 8 ++++ tools/mipgen/src/main.cpp | 55 ++++++++++++++++------- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/libs/image/include/image/ColorTransform.h b/libs/image/include/image/ColorTransform.h index 7474258ffd..3d373c5e10 100644 --- a/libs/image/include/image/ColorTransform.h +++ b/libs/image/include/image/ColorTransform.h @@ -164,23 +164,23 @@ std::unique_ptr fromLinearTosRGB(const LinearImage& image) { return dst; } -// Creates a 3-channel RGB u8 image from a f32 image. -// The source image can have three or more channels, but only the first three are honored. -template +// Creates a N-channel RGB u8 image from a f32 image. +// The source image can have three or more channels, but only the first N are honored. +template std::unique_ptr fromLinearToRGB(const LinearImage& image) { using math::float3; size_t w = image.getWidth(); size_t h = image.getHeight(); - UTILS_UNUSED_IN_RELEASE size_t channels = image.getChannels(); - assert(channels >= 3); - std::unique_ptr dst(new uint8_t[w * h * 3 * sizeof(T)]); + size_t channels = image.getChannels(); + assert(channels >= N); + std::unique_ptr dst(new uint8_t[w * h * N * sizeof(T)]); T* d = reinterpret_cast(dst.get()); for (size_t y = 0; y < h; ++y) { - for (size_t x = 0; x < w; ++x, d += 3) { - auto src = image.get((uint32_t) x, (uint32_t) y); - float3 l(saturate(*src) * std::numeric_limits::max()); - for (size_t i = 0; i < 3; i++) { - d[i] = T(l[i]); + float const* p = image.getPixelRef(0, y); + for (size_t x = 0; x < w; ++x, p += channels, d += N) { + for (int n = 0; n < N; n++) { + float target = math::saturate(p[n]) * std::numeric_limits::max(); + d[n] = T(target); } } } diff --git a/libs/image/include/image/ImageOps.h b/libs/image/include/image/ImageOps.h index e62a206f3a..5d1cd28a14 100644 --- a/libs/image/include/image/ImageOps.h +++ b/libs/image/include/image/ImageOps.h @@ -56,6 +56,9 @@ LinearImage cropRegion(const LinearImage& image, uint32_t l, uint32_t t, uint32_ // Lexicographically compares two images, similar to memcmp. int compare(const LinearImage& a, const LinearImage& b, float epsilon = 0.0f); +// Sets all pixels in all channels to the given value. +void clearToValue(LinearImage& img, float value); + } // namespace image #endif /* IMAGE_LINEARIMAGE_H */ diff --git a/libs/image/src/ImageOps.cpp b/libs/image/src/ImageOps.cpp index 6ad88e62ec..7edb16ff59 100644 --- a/libs/image/src/ImageOps.cpp +++ b/libs/image/src/ImageOps.cpp @@ -236,4 +236,12 @@ int compare(const LinearImage& a, const LinearImage& b, float epsilon) { [epsilon](float x, float y) { return x < y - epsilon; }); } +void clearToValue(LinearImage& image, float value) { + const uint32_t nvals = image.getWidth() * image.getHeight() * image.getChannels(); + float* data = image.getPixelRef(); + for (uint32_t index = 0; index < nvals; ++index) { + data[index] = value; + } +} + } // namespace image diff --git a/tools/mipgen/src/main.cpp b/tools/mipgen/src/main.cpp index 557403fac0..416f8b99cd 100644 --- a/tools/mipgen/src/main.cpp +++ b/tools/mipgen/src/main.cpp @@ -40,6 +40,7 @@ static bool g_formatSpecified = false; static bool g_createGallery = false; static string g_compression = ""; static Filter g_filter = Filter::DEFAULT; +static bool g_addAlpha = false; static bool g_stripAlpha = false; static bool g_grayscale = false; static bool g_ktxContainer = false; @@ -74,6 +75,8 @@ Options: --kernel=[box|nearest|hermite|gaussian|normals|mitchell|lanczos|min], -k [filter] specify filter kernel type (defaults to lanczos) the "normals" filter may automatically change the compression scheme + --add-alpha + if the source image has 3 channels, this adds a fourth channel filled with 1.0 --strip-alpha ignore the alpha component of the input image --compression=COMPRESSION, -c COMPRESSION @@ -134,7 +137,7 @@ static void license() { } static int handleArguments(int argc, char* argv[]) { - static constexpr const char* OPTSTR = "hLlgpf:c:k:s"; + static constexpr const char* OPTSTR = "hLlgpf:c:k:sa"; static const struct option OPTIONS[] = { { "help", no_argument, 0, 'h' }, { "license", no_argument, 0, 'L' }, @@ -145,6 +148,7 @@ static int handleArguments(int argc, char* argv[]) { { "compression", required_argument, 0, 'c' }, { "kernel", required_argument, 0, 'k' }, { "strip-alpha", no_argument, 0, 's' }, + { "add-alpha", no_argument, 0, 'a' }, { 0, 0, 0, 0 } // termination of the option list }; @@ -180,6 +184,9 @@ static int handleArguments(int argc, char* argv[]) { case 's': g_stripAlpha = true; break; + case 'a': + g_addAlpha = true; + break; case 'f': if (arg == "png") { g_format = ImageEncoder::Format::PNG; @@ -249,6 +256,14 @@ int main(int argc, char* argv[]) { auto b = extractChannel(sourceImage, 2); sourceImage = combineChannels({r, g, b}); } + if (g_addAlpha && sourceImage.getChannels() == 3) { + auto r = extractChannel(sourceImage, 0); + auto g = extractChannel(sourceImage, 1); + auto b = extractChannel(sourceImage, 2); + auto a = LinearImage(sourceImage.getWidth(), sourceImage.getHeight(), 1); + clearToValue(a, 1.0f); + sourceImage = combineChannels({r, g, b, a}); + } if (g_grayscale) { sourceImage = extractChannel(sourceImage, 0); } @@ -269,26 +284,26 @@ int main(int argc, char* argv[]) { // bundle, we want to include level 0, so add 1 to the KTX level count. KtxBundle container(1 + miplevels.size(), 1, false); auto& info = container.info(); - size_t componentCount = 3; + CompressionConfig config {}; info = { .endianness = KtxBundle::ENDIAN_DEFAULT, .glType = KtxBundle::UNSIGNED_BYTE, .glTypeSize = 1, - .glFormat = KtxBundle::RGB, - .glInternalFormat = KtxBundle::RGB8, - .glBaseInternalFormat = KtxBundle::RGB, .pixelWidth = sourceImage.getWidth(), .pixelHeight = sourceImage.getHeight(), .pixelDepth = 0, }; - if (g_grayscale) { - info.glTypeSize = 1; - info.glFormat = - info.glInternalFormat = - info.glBaseInternalFormat = KtxBundle::LUMINANCE; - componentCount = 1; + size_t componentCount = sourceImage.getChannels(); + if (componentCount == 1) { + info.glFormat = info.glBaseInternalFormat = KtxBundle::RED; + info.glInternalFormat = KtxBundle::R8; + } else if (componentCount == 3) { + info.glFormat = info.glBaseInternalFormat = KtxBundle::RGB; + info.glInternalFormat = KtxBundle::RGB8; + } else if (componentCount == 4) { + info.glFormat = info.glBaseInternalFormat = KtxBundle::RGBA; + info.glInternalFormat = KtxBundle::RGBA8; } - CompressionConfig config {}; if (!g_compression.empty()) { bool valid = parseOptionString(g_compression, &config); if (!valid) { @@ -298,9 +313,7 @@ int main(int argc, char* argv[]) { // The KTX spec says the following for compressed textures: glTypeSize should 1, // glFormat should be 0, and glBaseInternalFormat should be RED, RG, RGB, or RGBA. // The glInternalFormat field is the only field that specifies the actual format. - info.glTypeSize = 1; info.glFormat = 0; - info.glBaseInternalFormat = KtxBundle::RGBA; } uint32_t mip = 0; auto addLevel = [&](LinearImage image) { @@ -327,9 +340,17 @@ int main(int argc, char* argv[]) { } else if (g_grayscale) { data = fromLinearTosRGB(image); } else if (g_linearized) { - data = fromLinearToRGB(image); + if (componentCount == 3) { + data = fromLinearToRGB(image); + } else { + data = fromLinearToRGB(image); + } } else { - data = fromLinearTosRGB(image); + if (componentCount == 3) { + data = fromLinearTosRGB(image); + } else { + data = fromLinearTosRGB(image); + } } container.setBlob({mip++, 0, 0}, data.get(), image.getWidth() * image.getHeight() * container.info().glTypeSize * componentCount); @@ -351,7 +372,7 @@ int main(int argc, char* argv[]) { puts("Writing image files to disk..."); char path[256]; uint32_t mip = 1; // start at 1 because 0 is the original image - for (auto image: miplevels) { + for (auto image : miplevels) { int result = snprintf(path, sizeof(path), outputPattern.c_str(), mip++); if (result < 0 || result >= sizeof(path)) { cerr << "Output pattern is too long." << endl;