Skip to content

Commit ce3d759

Browse files
committed
Better handle signed and integer output types.
When running the CLI, if the original input image is a UNorm type and the output texture type is SNorm, the values will be re-mapped from [0, 1] to [-1, 1] before conversion. In the case of Int and UInt outputs, the value will be converted to the full integer range. Conversion to EAC signed formats now take into account the fact that it assumes the inputs are in the range [0, 1]. Worked around bugs in FreeImage conversions: * RGB5 and RGB565 to RGBF handles the values as a 24 BPP image. * Grayscale values converted to double don't normalize the values. Updated astc-encoder to 5.3.0. Incremented version to 2.9.0.
1 parent 63be6f0 commit ce3d759

File tree

11 files changed

+648
-37
lines changed

11 files changed

+648
-37
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ if (CUTTLEFISH_OUTPUT_DIR)
7777
endif()
7878

7979
set(CUTTLEFISH_MAJOR_VERSION 2)
80-
set(CUTTLEFISH_MINOR_VERSION 8)
81-
set(CUTTLEFISH_PATCH_VERSION 1)
80+
set(CUTTLEFISH_MINOR_VERSION 9)
81+
set(CUTTLEFISH_PATCH_VERSION 0)
8282
set(CUTTLEFISH_VERSION ${CUTTLEFISH_MAJOR_VERSION}.${CUTTLEFISH_MINOR_VERSION}.${CUTTLEFISH_PATCH_VERSION})
8383

8484
set(CUTTLEFISH_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})

lib/ISPCTextureCompressor

lib/astc-encoder

Submodule astc-encoder updated 43 files

lib/include/cuttlefish/Texture.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2022 Aaron Barany
2+
* Copyright 2017-2025 Aaron Barany
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -331,6 +331,23 @@ class CUTTLEFISH_EXPORT Texture
331331
*/
332332
static FileType fileType(const char* fileName);
333333

334+
/**
335+
* @brief Adjusts the value range for an image.
336+
*
337+
* This will adjust values based on reasonable expectations from the original image format to
338+
* the texture type. The current situations considered are:
339+
* - Input images with a UNorm value range will be converted to [-1, 1] range when used with
340+
* SNorm type. This will convert to a float type if not already one.
341+
* - Input images with a UNorm value range will be converted to the appropriate integer range.
342+
*
343+
* @param[inout] image The image to adjust the values for.
344+
* @param type The target texture type the image will be converted to.
345+
* @param origImageFormat The original image format before any conversions for internal
346+
* processing. If set to Invalid, the image's current format will be used.
347+
*/
348+
static void adjustImageValueRange(Image& image, Type type,
349+
Image::Format origImageFormat = Image::Format::Invalid);
350+
334351
Texture();
335352

336353
/**

lib/src/EtcConverter.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 Aaron Barany
2+
* Copyright 2017-2025 Aaron Barany
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -129,6 +129,19 @@ void EtcConverter::process(unsigned int x, unsigned int y, ThreadData*)
129129
pixels[index] = scanline[i];
130130
}
131131

132+
// Signed formats expect inputs in the range [0, 1].
133+
if (m_format == Etc::Image::Format::SIGNED_R11 || m_format == Etc::Image::Format::SIGNED_RG11)
134+
{
135+
for (unsigned int j = 0, index = 0; j < blockDim; ++j)
136+
{
137+
for (unsigned int i = 0; i < blockDim; ++i, ++index)
138+
{
139+
pixels[index].r = pixels[index].r*0.5f + 0.5f;
140+
pixels[index].g = pixels[index].g*0.5f + 0.5f;
141+
}
142+
}
143+
}
144+
132145
Etc::Image etcImage(reinterpret_cast<float*>(pixels), limitX - x*blockDim, limitY - y*blockDim,
133146
m_metric);
134147
etcImage.Encode(m_format, m_metric, m_effort, 1, 1);

lib/src/Image.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2024 Aaron Barany
2+
* Copyright 2017-2025 Aaron Barany
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -96,7 +96,7 @@ Image::Format getFormat(FIBITMAP* image)
9696
case FIT_RGB16:
9797
return Image::Format::RGB16;
9898
case FIT_RGBA16:
99-
return Image::Format::RGBA16;;
99+
return Image::Format::RGBA16;
100100
case FIT_RGBF:
101101
return Image::Format::RGBF;
102102
case FIT_RGBAF:
@@ -194,6 +194,18 @@ bool isGrayscaleFormat(Image::Format format)
194194
}
195195
}
196196

197+
bool isRGB16BPPFormat(Image::Format format)
198+
{
199+
switch (format)
200+
{
201+
case Image::Format::RGB5:
202+
case Image::Format::RGB565:
203+
return true;
204+
default:
205+
return false;
206+
}
207+
}
208+
197209
unsigned int readIStream(void* buffer, unsigned int size, unsigned int count, fi_handle handle)
198210
{
199211
auto stream = reinterpret_cast<std::istream*>(handle);
@@ -1176,8 +1188,9 @@ Image Image::convert(Format dstFormat, bool convertGrayscale) const
11761188
break;
11771189
case Format::RGBF:
11781190
// NOTE: Don't use FreeImage conversion for float to float conversion to avoid
1179-
// clamping HDR images.
1180-
if (!needFloatConvertFallback(srcFormat))
1191+
// clamping HDR images. FreeImage conversion is also incorrect for 16-BPP RGB
1192+
// formats.
1193+
if (!needFloatConvertFallback(srcFormat) && !isRGB16BPPFormat(srcFormat))
11811194
{
11821195
image.m_impl.reset(Impl::create(
11831196
FreeImage_ConvertToRGBF(m_impl->image), m_impl->colorSpace, dstFormat));
@@ -1229,8 +1242,10 @@ Image Image::convert(Format dstFormat, bool convertGrayscale) const
12291242
break;
12301243
case Format::Double:
12311244
// NOTE: Don't use FreeImage conversion for float to float conversion to avoid
1232-
// clamping HDR images. Also need to use fallback if not converting to grayscale.
1233-
if ((convertLinearGrayscale || isGrayscaleFormat(srcFormat)) &&
1245+
// clamping HDR images. Also need to use fallback if not converting to grayscale or
1246+
// the source is already grayscale, in which case FreeImage doesn't normalize the
1247+
// values.
1248+
if (convertLinearGrayscale && !isGrayscaleFormat(srcFormat) &&
12341249
!needFloatConvertFallback(srcFormat))
12351250
{
12361251
image.m_impl.reset(Impl::create(

lib/src/S3tcConverter.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2023 Aaron Barany
2+
* Copyright 2017-2025 Aaron Barany
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -406,7 +406,7 @@ void Bc4Converter::compressBlock(void* block, ColorRGBAf* blockColors)
406406
for (unsigned int i = 0; i < blockPixels; ++i)
407407
{
408408
colorBlock[i] =
409-
static_cast<std::uint8_t>(std::round(clamp(blockColors[i].r, -1.0f, 1.0f)*0x7F));
409+
static_cast<std::int8_t>(std::round(clamp(blockColors[i].r, -1.0f, 1.0f)*0x7F));
410410
}
411411

412412
assert(m_compressonatorOptions);
@@ -459,9 +459,9 @@ void Bc5Converter::compressBlock(void* block, ColorRGBAf* blockColors)
459459
for (unsigned int i = 0; i < blockPixels; ++i)
460460
{
461461
colorBlock[0][i] =
462-
static_cast<std::uint8_t>(std::round(clamp(blockColors[i].r, -1.0f, 1.0f)*0x7F));
462+
static_cast<std::int8_t>(std::round(clamp(blockColors[i].r, -1.0f, 1.0f)*0x7F));
463463
colorBlock[1][i] =
464-
static_cast<std::uint8_t>(std::round(clamp(blockColors[i].g, -1.0f, 1.0f)*0x7F));
464+
static_cast<std::int8_t>(std::round(clamp(blockColors[i].g, -1.0f, 1.0f)*0x7F));
465465
}
466466

467467
assert(m_compressonatorOptions);

lib/src/Texture.cpp

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2022 Aaron Barany
2+
* Copyright 2017-2025 Aaron Barany
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
#include <cmath>
3030
#include <cstring>
3131
#include <fstream>
32+
#include <limits>
3233
#include <thread>
3334
#include <vector>
3435
#include <sstream>
@@ -769,6 +770,135 @@ Texture::FileType Texture::fileType(const char* fileName)
769770
return FileType::Auto;
770771
}
771772

773+
void Texture::adjustImageValueRange(Image& image, Type type, Image::Format origImageFormat)
774+
{
775+
if (!image.isValid())
776+
return;
777+
778+
if (origImageFormat == Image::Format::Invalid)
779+
origImageFormat = image.format();
780+
781+
if (type == Type::SNorm || type == Type::UInt || type == Type::Int)
782+
{
783+
switch (origImageFormat)
784+
{
785+
case Image::Format::Gray8:
786+
case Image::Format::Gray16:
787+
case Image::Format::RGB5:
788+
case Image::Format::RGB565:
789+
case Image::Format::RGB8:
790+
case Image::Format::RGB16:
791+
case Image::Format::RGBA8:
792+
case Image::Format::RGBA16:
793+
// Remap [0, 1] to [-1, 1].
794+
unsigned int channelCount;
795+
switch (image.format())
796+
{
797+
case Image::Format::Gray8:
798+
case Image::Format::Gray16:
799+
case Image::Format::Double:
800+
channelCount = 1;
801+
image = image.convert(Image::Format::Float);
802+
break;
803+
case Image::Format::RGB5:
804+
case Image::Format::RGB565:
805+
case Image::Format::RGB8:
806+
case Image::Format::RGB16:
807+
case Image::Format::Complex:
808+
channelCount = 3;
809+
image = image.convert(Image::Format::RGBF);
810+
break;
811+
case Image::Format::RGBF:
812+
channelCount = 3;
813+
break;
814+
case Image::Format::RGBA8:
815+
case Image::Format::RGBA16:
816+
channelCount = 4;
817+
image = image.convert(Image::Format::RGBAF);
818+
break;
819+
case Image::Format::RGBAF:
820+
channelCount = 4;
821+
break;
822+
case Image::Format::Float:
823+
channelCount = 1;
824+
break;
825+
default:
826+
return;
827+
}
828+
829+
if (type == Type::SNorm)
830+
{
831+
for (unsigned int y = 0; y < image.height(); ++y)
832+
{
833+
auto scanline = reinterpret_cast<float*>(image.scanline(y));
834+
for (unsigned int x = 0; x < image.width()*channelCount; ++x)
835+
scanline[x] = scanline[x]*2.0f - 1.0f;
836+
}
837+
}
838+
else
839+
{
840+
float multiply[4] = {};
841+
float offset[4] = {};
842+
switch (origImageFormat)
843+
{
844+
case Image::Format::Gray8:
845+
case Image::Format::RGB8:
846+
case Image::Format::RGBA8:
847+
for (unsigned int i = 0; i < 4; ++i)
848+
{
849+
multiply[i] = std::numeric_limits<std::uint8_t>::max();
850+
if (type == Type::Int)
851+
offset[i] = std::numeric_limits<int8_t>::min();
852+
}
853+
break;
854+
case Image::Format::Gray16:
855+
case Image::Format::RGB16:
856+
case Image::Format::RGBA16:
857+
for (unsigned int i = 0; i < 4; ++i)
858+
{
859+
multiply[i] = std::numeric_limits<std::uint16_t>::max();
860+
if (type == Type::Int)
861+
offset[i] = std::numeric_limits<int16_t>::min();
862+
}
863+
break;
864+
case Image::Format::RGB5:
865+
multiply[0] = multiply[1] = multiply[2] = float((1 << 5) - 1);
866+
if (type == Type::Int)
867+
offset[0] = offset[1] = offset[2] = -float(1 << 4);
868+
break;
869+
case Image::Format::RGB565:
870+
multiply[0] = multiply[2] = float((1 << 5) - 1);
871+
multiply[1] = float((1 << 6) - 1);
872+
if (type == Type::Int)
873+
{
874+
offset[0] = offset[2] = -float(1 << 4);
875+
offset[1] = -float(1 << 5);
876+
}
877+
break;
878+
default:
879+
return;
880+
}
881+
882+
for (unsigned int y = 0; y < image.height(); ++y)
883+
{
884+
auto scanline = reinterpret_cast<float*>(image.scanline(y));
885+
for (unsigned int x = 0; x < image.width(); ++x)
886+
{
887+
for (unsigned int c = 0; c < channelCount; ++c)
888+
{
889+
scanline[x*channelCount + c] = std::round(
890+
scanline[x*channelCount + c]*multiply[c] + offset[c]);
891+
}
892+
}
893+
}
894+
}
895+
return;
896+
default:
897+
break;
898+
}
899+
}
900+
}
901+
772902
Texture::Texture() = default;
773903

774904
Texture::~Texture() = default;

0 commit comments

Comments
 (0)