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)
This commit is contained in:
Philip Rideout
2018-12-07 15:21:12 -08:00
parent 76b15e895a
commit 8dda07bf2c
4 changed files with 60 additions and 28 deletions

View File

@@ -164,23 +164,23 @@ std::unique_ptr<uint8_t[]> 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 <typename T>
// 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 <typename T, int N = 3>
std::unique_ptr<uint8_t[]> 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<uint8_t[]> dst(new uint8_t[w * h * 3 * sizeof(T)]);
size_t channels = image.getChannels();
assert(channels >= N);
std::unique_ptr<uint8_t[]> dst(new uint8_t[w * h * N * sizeof(T)]);
T* d = reinterpret_cast<T*>(dst.get());
for (size_t y = 0; y < h; ++y) {
for (size_t x = 0; x < w; ++x, d += 3) {
auto src = image.get<float3>((uint32_t) x, (uint32_t) y);
float3 l(saturate(*src) * std::numeric_limits<T>::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<T>::max();
d[n] = T(target);
}
}
}

View File

@@ -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 */

View File

@@ -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

View File

@@ -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<uint8_t, 1>(image);
} else if (g_linearized) {
data = fromLinearToRGB<uint8_t>(image);
if (componentCount == 3) {
data = fromLinearToRGB<uint8_t, 3>(image);
} else {
data = fromLinearToRGB<uint8_t, 4>(image);
}
} else {
data = fromLinearTosRGB<uint8_t>(image);
if (componentCount == 3) {
data = fromLinearTosRGB<uint8_t, 3>(image);
} else {
data = fromLinearTosRGB<uint8_t, 4>(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;