Browse Source

TextureTools: new utilities for sampling textures on a CPU.

I need this to sample a color map for debug visualization in the UI
library. Thus so far it's just 1D, and with 8-bit input. Other variants
might get added in the future if needed.
pull/680/head
Vladimír Vondruš 7 months ago
parent
commit
e9907a80ec
  1. 3
      doc/changelog.dox
  2. 12
      src/Magnum/DebugTools/ColorMap.h
  3. 4
      src/Magnum/TextureTools/CMakeLists.txt
  4. 97
      src/Magnum/TextureTools/Sample.cpp
  5. 87
      src/Magnum/TextureTools/Sample.h
  6. 2
      src/Magnum/TextureTools/Test/CMakeLists.txt
  7. 227
      src/Magnum/TextureTools/Test/SampleTest.cpp

3
doc/changelog.dox

@ -507,6 +507,9 @@ See also:
- New @ref TextureTools::atlasTextureCoordinateTransformation() helper for - New @ref TextureTools::atlasTextureCoordinateTransformation() helper for
creating an appropriate texture coordinate transformation matrix for creating an appropriate texture coordinate transformation matrix for
textures placed into an atlas textures placed into an atlas
- New @ref TextureTools::sampleLinear(), @ref TextureTools::sampleSrgb() and
@ref TextureTools::sampleSrgbAlpha() utilities for sampling 1D textures
such as colormaps on a CPU
- Added a @ref TextureTools::DistanceFieldGL::operator()() overload taking a - Added a @ref TextureTools::DistanceFieldGL::operator()() overload taking a
@ref GL::Framebuffer instead of a @ref GL::Texture as an output for an @ref GL::Framebuffer instead of a @ref GL::Texture as an output for an
easier ability to download the resulting image on OpenGL ES platforms; easier ability to download the resulting image on OpenGL ES platforms;

12
src/Magnum/DebugTools/ColorMap.h

@ -140,14 +140,16 @@ See @ref building, @ref cmake and @ref debug-tools for more information.
@endparblock @endparblock
For all color maps the returned data is the sRGB colorspace. Desired usage is For all color maps the returned data is the sRGB colorspace. Desired GPU usage
by uploading to a texture with linear filtering, depending on the use case with is by uploading to a texture with linear filtering, depending on the use case
either clamp or repeat wrapping. For a sRGB workflow don't forget to set the with either clamp or repeat wrapping. For a sRGB workflow don't forget to set
texture format to sRGB, to ensure the values are interpreted and interpolated the texture format to sRGB, to ensure the values are interpreted and
done correctly. interpolated done correctly.
@snippet DebugTools-gl.cpp ColorMap @snippet DebugTools-gl.cpp ColorMap
For CPU-side usage see @ref TextureTools::sampleLinear() and
@ref TextureTools::sampleSrgb().
*/ */
namespace ColorMap { namespace ColorMap {

4
src/Magnum/TextureTools/CMakeLists.txt

@ -41,10 +41,12 @@ set(CMAKE_FOLDER "Magnum/TextureTools")
find_package(Corrade REQUIRED PluginManager) find_package(Corrade REQUIRED PluginManager)
set(MagnumTextureTools_GracefulAssert_SRCS set(MagnumTextureTools_GracefulAssert_SRCS
Atlas.cpp) Atlas.cpp
Sample.cpp)
set(MagnumTextureTools_HEADERS set(MagnumTextureTools_HEADERS
Atlas.h Atlas.h
Sample.h
TextureTools.h TextureTools.h
visibility.h) visibility.h)

97
src/Magnum/TextureTools/Sample.cpp

@ -0,0 +1,97 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023, 2024, 2025
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "Sample.h"
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Utility/Assert.h>
#include "Magnum/Math/Color.h"
namespace Magnum { namespace TextureTools {
namespace {
template<class In, class Out, class In2, Out(*unpacker)(const In2&)> Out sampleInternal(
#ifndef CORRADE_NO_ASSERT
const char* messagePrefix,
#endif
const Containers::StridedArrayView1D<const In>& texture, Float factor)
{
CORRADE_ASSERT(!texture.isEmpty(),
messagePrefix << "expected texture to have at least one element", {});
CORRADE_ASSERT(factor >= 0.0f && factor <= 1.0f,
messagePrefix << "expected factor to be within the [0, 1] range but got" << factor, {});
/* If we're exactly at the end or the texture has just a single element,
return the last element */
if(factor == 1.0f || texture.size() == 1)
return unpacker(texture.back());
/* Otherwise it's an interpolation of two values */
const Float sample = factor*(texture.size() - 1);
const UnsignedInt index = sample;
return Math::lerp(
unpacker(texture[index]),
unpacker(texture[index + 1]),
sample - index);
}
}
Color3 sampleLinear(const Containers::StridedArrayView1D<const Vector3ub>& texture, Float factor) {
return sampleInternal<Vector3ub, Color3, Math::Vector<3, UnsignedByte>, Math::unpack>(
#ifndef CORRADE_NO_ASSERT
"TextureTools::sampleLinear():",
#endif
texture, factor);
}
Color4 sampleLinear(const Containers::StridedArrayView1D<const Vector4ub>& texture, Float factor) {
return sampleInternal<Vector4ub, Color4, Math::Vector<4, UnsignedByte>, Math::unpack>(
#ifndef CORRADE_NO_ASSERT
"TextureTools::sampleLinear():",
#endif
texture, factor);
}
Color3 sampleSrgb(const Containers::StridedArrayView1D<const Vector3ub>& texture, Float factor) {
return sampleInternal<Vector3ub, Color3, Math::Vector3<UnsignedByte>, Color3::fromSrgb>(
#ifndef CORRADE_NO_ASSERT
"TextureTools::sampleSrgb():",
#endif
texture, factor);
}
Color4 sampleSrgbAlpha(const Containers::StridedArrayView1D<const Vector4ub>& texture, Float factor) {
return sampleInternal<Vector4ub, Color4, Math::Vector4<UnsignedByte>, Color4::fromSrgbAlpha>(
#ifndef CORRADE_NO_ASSERT
"TextureTools::sampleSrgbAlpha():",
#endif
texture, factor);
}
}}

87
src/Magnum/TextureTools/Sample.h

@ -0,0 +1,87 @@
#ifndef Magnum_TextureTools_Sample_h
#define Magnum_TextureTools_Sample_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023, 2024, 2025
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
* @brief Function @ref Magnum::TextureTools::sampleLinear(), @ref Magnum::TextureTools::sampleSrgb(), @ref Magnum::TextureTools::sampleSrgbAlpha()
* @m_since_latest
*/
#include "Magnum/Magnum.h"
#include "Magnum/TextureTools/visibility.h"
namespace Magnum { namespace TextureTools {
/**
@brief Sample a 1D RGB texture with linear interpolation
@m_since_latest
Expects that @p texture has at least one element and @p factor is in a
@f$ [0, 1] @f$ range. For a factor of @cpp 0.0f @ce returns the first element
of @p texture, for @cpp 1.0f @ce returns the last, values in between are a
linear interpolation of two nearest elements that are first unpacked to a
floating-point type.
Note that the @p texture is interpreted as having linear colors. Use
@ref sampleSrgb() if you want to perform conversion from sRGB values instead.
@see @ref Math::lerp(const T&, const T&, U), @ref Math::unpack(const Integral&)
*/
MAGNUM_TEXTURETOOLS_EXPORT Color3 sampleLinear(const Containers::StridedArrayView1D<const Vector3ub>& texture, Float factor);
/**
@brief Sample a 1D RGBA texture with linear interpolation
@m_since_latest
Like @ref sampleLinear(const Containers::StridedArrayView1D<const Vector3ub>&, Float)
but with a four-component input.
*/
MAGNUM_TEXTURETOOLS_EXPORT Color4 sampleLinear(const Containers::StridedArrayView1D<const Vector4ub>& texture, Float factor);
/**
@brief Sample a 1D RGB texture with sRGB interpolation
@m_since_latest
Compared to @ref sampleLinear(const Containers::StridedArrayView1D<const Vector3ub>&, Float)
treats the input values as sRGB and applies @ref Color3::fromSrgb() instead of
@ref Math::unpack(const Integral&).
*/
MAGNUM_TEXTURETOOLS_EXPORT Color3 sampleSrgb(const Containers::StridedArrayView1D<const Vector3ub>& texture, Float factor);
/**
@brief Sample a 1D RGBA texture with sRGB interpolation
@m_since_latest
Compared to @ref sampleLinear(const Containers::StridedArrayView1D<const Vector3ub>&, Float)
treats the input RGB channels as sRGB and applies @ref Color4::fromSrgbAlpha()
instead of @ref Math::unpack(const Integral&). The alpha channel is treated by
that function as linear.
*/
MAGNUM_TEXTURETOOLS_EXPORT Color4 sampleSrgbAlpha(const Containers::StridedArrayView1D<const Vector4ub>& texture, Float factor);
}}
#endif

2
src/Magnum/TextureTools/Test/CMakeLists.txt

@ -88,6 +88,8 @@ else()
endif() endif()
endif() endif()
corrade_add_test(TextureToolsSampleTest SampleTest.cpp LIBRARIES MagnumTextureToolsTestLib)
if(MAGNUM_TARGET_GL) if(MAGNUM_TARGET_GL)
corrade_add_test(TextureToolsDistanceFieldGL_Test DistanceFieldGL_Test.cpp LIBRARIES MagnumTextureTools) corrade_add_test(TextureToolsDistanceFieldGL_Test DistanceFieldGL_Test.cpp LIBRARIES MagnumTextureTools)

227
src/Magnum/TextureTools/Test/SampleTest.cpp

@ -0,0 +1,227 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023, 2024, 2025
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/String.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/String.h>
#include <Corrade/Utility/Format.h>
#include "Magnum/Math/Color.h"
#include "Magnum/TextureTools/Sample.h"
namespace Magnum { namespace TextureTools { namespace Test { namespace {
struct SampleTest: TestSuite::Tester {
explicit SampleTest();
void sample1DSingleElementLinearRgb();
void sample1DSingleElementLinearRgba();
void sample1DSingleElementSrgb();
void sample1DSingleElementSrgbAlpha();
void sample1DLinearRgb();
void sample1DLinearRgba();
void sample1DSrgb();
void sample1DSrgbAlpha();
void sample1DInvalid();
};
using namespace Math::Literals;
SampleTest::SampleTest() {
addTests({&SampleTest::sample1DSingleElementLinearRgb,
&SampleTest::sample1DSingleElementLinearRgba,
&SampleTest::sample1DSingleElementSrgb,
&SampleTest::sample1DSingleElementSrgbAlpha,
&SampleTest::sample1DLinearRgb,
&SampleTest::sample1DLinearRgba,
&SampleTest::sample1DSrgb,
&SampleTest::sample1DSrgbAlpha,
&SampleTest::sample1DInvalid});
}
void SampleTest::sample1DSingleElementLinearRgb() {
/* Should work also with just the Vector base, not just Color3 */
Vector3ub texture[]{
0xff3366_rgb
};
/* All factors return the same value */
CORRADE_COMPARE(sampleLinear(texture, 0.0f), 0xff3366_rgbf);
CORRADE_COMPARE(sampleLinear(texture, 0.6375f), 0xff3366_rgbf);
CORRADE_COMPARE(sampleLinear(texture, 1.0f), 0xff3366_rgbf);
}
void SampleTest::sample1DSingleElementLinearRgba() {
/* Should work also with just the Vector base, not just Color3 */
Vector4ub texture[]{
0xff336699_rgba
};
/* All factors return the same value */
CORRADE_COMPARE(sampleLinear(texture, 0.0f), 0xff336699_rgbaf);
CORRADE_COMPARE(sampleLinear(texture, 0.6375f), 0xff336699_rgbaf);
CORRADE_COMPARE(sampleLinear(texture, 1.0f), 0xff336699_rgbaf);
}
void SampleTest::sample1DSingleElementSrgb() {
/* Should work also with just the Vector base, not just Color3 */
Vector3ub texture[]{
0xff3366_srgb
};
/* All factors return the same value, converted from sRGB */
CORRADE_COMPARE(sampleSrgb(texture, 0.0f), 0xff3366_srgbf);
CORRADE_COMPARE(sampleSrgb(texture, 0.6375f), 0xff3366_srgbf);
CORRADE_COMPARE(sampleSrgb(texture, 1.0f), 0xff3366_srgbf);
}
void SampleTest::sample1DSingleElementSrgbAlpha() {
/* Should work also with just the Vector base, not just Color3 */
Vector4ub texture[]{
0xff336699_srgba
};
/* All factors return the same value, converted from sRGB */
CORRADE_COMPARE(sampleSrgbAlpha(texture, 0.0f), 0xff336699_srgbaf);
CORRADE_COMPARE(sampleSrgbAlpha(texture, 0.6375f), 0xff336699_srgbaf);
CORRADE_COMPARE(sampleSrgbAlpha(texture, 1.0f), 0xff336699_srgbaf);
}
/* Not const because slice() wouldn't work in that case due to the const
variant being constexpr and thus not returning a reinterpreted reference
(lol ffs) */
Color4ub Texture[]{
0xff336699_rgba, /* 0.0 */
0xdeadbeef_rgba, /* 0.25 */
0x2200eeff_rgba, /* 0.5 */
0xaaccaa33_rgba, /* 0.75 */
0x996633ff_rgba, /* 1.0 */
};
void SampleTest::sample1DLinearRgb() {
Containers::StridedArrayView1D<const Color3ub> texture = Containers::stridedArrayView(Texture).slice(&Color4ub::rgb);
/* These should return exact values */
CORRADE_COMPARE(sampleLinear(texture, 0.0f), 0xff3366_rgbf);
CORRADE_COMPARE(sampleLinear(texture, 0.25f), 0xdeadbe_rgbf);
/* This one should not attempt to lerp with the sentinel value */
/** @todo once a variant with float input exists, put NaNs in a sentinel */
CORRADE_COMPARE(sampleLinear(texture, 1.0f), 0x996633_rgbf);
/* This is an exact 25% / 75% interpolation between element 2 and 3 */
CORRADE_COMPARE(sampleLinear(texture, 0.5f + 0.0625f), 0x4433dd_rgbf);
CORRADE_COMPARE(sampleLinear(texture, 0.75f - 0.0625f), 0x8899bb_rgbf);
}
void SampleTest::sample1DLinearRgba() {
/* These should return exact values */
CORRADE_COMPARE(sampleLinear(Texture, 0.0f), 0xff336699_rgbaf);
CORRADE_COMPARE(sampleLinear(Texture, 0.25f), 0xdeadbeef_rgbaf);
CORRADE_COMPARE(sampleLinear(Texture, 1.0f), 0x996633ff_rgbaf);
/* This is an exact 25% / 75% interpolation between element 2 and 3 */
CORRADE_COMPARE(sampleLinear(Texture, 0.5f + 0.0625f), 0x4433ddcc_rgbaf);
CORRADE_COMPARE(sampleLinear(Texture, 0.75f - 0.0625f), 0x8899bb66_rgbaf);
}
void SampleTest::sample1DSrgb() {
Containers::StridedArrayView1D<const Color3ub> texture = Containers::stridedArrayView(Texture).slice(&Color4ub::rgb);
/* These should return exact values, converted from sRGB */
CORRADE_COMPARE(sampleSrgb(texture, 0.0f), 0xff3366_srgbf);
CORRADE_COMPARE(sampleSrgb(texture, 0.25f), 0xdeadbe_srgbf);
CORRADE_COMPARE(sampleSrgb(texture, 1.0f), 0x996633_srgbf);
/* This is an exact 25% / 75% interpolation between element 2 and 3, but
with sRGB conversion happening first */
CORRADE_COMPARE(sampleSrgb(texture, 0.5f + 0.0625f), Math::lerp(0x2200ee_srgbf, 0xaaccaa_srgbf, 0.25f));
CORRADE_COMPARE(sampleSrgb(texture, 0.75f - 0.0625f), Math::lerp(0x2200ee_srgbf, 0xaaccaa_srgbf, 0.75f));
}
void SampleTest::sample1DSrgbAlpha() {
/* These should return exact values */
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 0.0f), 0xff336699_srgbaf);
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 0.25f), 0xdeadbeef_srgbaf);
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 1.0f), 0x996633ff_srgbaf);
/* This is an exact 25% / 75% interpolation between element 2 and 3, but
with sRGB conversion for the RGB channels happening first */
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 0.5f + 0.0625f), Math::lerp(0x2200eeff_srgbaf, 0xaaccaa33_srgbaf, 0.25f));
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 0.75f - 0.0625f), Math::lerp(0x2200eeff_srgbaf, 0xaaccaa33_srgbaf, 0.75f));
/* The literals should handle alpha as linear but verifying it also
separately just in case -- the channel should have the same value as in
the sample1DLinearRgba() test */
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 0.5f + 0.0625f).a(),
(Math::unpack<Float, UnsignedByte>(0xcc)));
CORRADE_COMPARE(sampleSrgbAlpha(Texture, 0.75f - 0.0625f).a(),
(Math::unpack<Float, UnsignedByte>(0x66)));
}
void SampleTest::sample1DInvalid() {
CORRADE_SKIP_IF_NO_ASSERT();
Color3ub rgb[1];
Color4ub rgba[1];
Containers::String out;
Error redirectError{&out};
sampleLinear(Containers::ArrayView<const Color3ub>{}, 0.0f);
sampleLinear(Containers::ArrayView<const Color4ub>{}, 0.0f);
sampleSrgb(Containers::ArrayView<const Color3ub>{}, 0.0f);
sampleSrgbAlpha(Containers::ArrayView<const Color4ub>{}, 0.0f);
sampleLinear(rgb, -0.125f);
sampleLinear(rgba, 1.125f);
sampleSrgb(rgb, -Constants::inf());
sampleSrgbAlpha(rgba, Constants::nan());
/* MSVC (w/o clang-cl) before 2019 shows -nan(ind) */
#if defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG_CL) && _MSC_VER < 1920
const char* nan = "-nan(ind)";
#else
const char* nan = "nan";
#endif
CORRADE_COMPARE_AS(out, Utility::format(
"TextureTools::sampleLinear(): expected texture to have at least one element\n"
"TextureTools::sampleLinear(): expected texture to have at least one element\n"
"TextureTools::sampleSrgb(): expected texture to have at least one element\n"
"TextureTools::sampleSrgbAlpha(): expected texture to have at least one element\n"
"TextureTools::sampleLinear(): expected factor to be within the [0, 1] range but got -0.125\n"
"TextureTools::sampleLinear(): expected factor to be within the [0, 1] range but got 1.125\n"
"TextureTools::sampleSrgb(): expected factor to be within the [0, 1] range but got -inf\n"
"TextureTools::sampleSrgbAlpha(): expected factor to be within the [0, 1] range but got {}\n", nan),
TestSuite::Compare::String);
}
}}}}
CORRADE_TEST_MAIN(Magnum::TextureTools::Test::SampleTest)
Loading…
Cancel
Save