diff --git a/CMakeLists.txt b/CMakeLists.txt index 30835aa..15c2f6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,11 @@ target_link_libraries(vtex2 PRIVATE vtflib_static com fmt::fmt) target_include_directories(vtex2 PRIVATE src external) target_include_directories(com PRIVATE src external external/vtflib/lib) +if (UNIX) + # Don't build vtex2 cli as a dynamic executable + set_target_properties(vtex2 PROPERTIES LINK_FLAGS "-static-libgcc -static-libstdc++ -static") +endif() + if (BUILD_GUI) target_link_libraries(vtfview PRIVATE vtflib_static com fmt::fmt) target_include_directories(vtfview PRIVATE src external) diff --git a/src/cli/action_convert.cpp b/src/cli/action_convert.cpp index f981622..fed5dbe 100644 --- a/src/cli/action_convert.cpp +++ b/src/cli/action_convert.cpp @@ -47,6 +47,7 @@ namespace opts static int nomips; static int toDX; static int quiet; + static int swizzle; } // namespace opts static bool get_version_from_str(const std::string& str, int& major, int& minor); @@ -236,6 +237,13 @@ const OptionList& ActionConvert::get_options() const { .type(OptType::Bool) .help("Silence output messages that aren't errors") ); + + opts::swizzle = opts.add( + ActionOption() + .long_opt("--swizzle") + .type(OptType::String) + .help("Perform an in-place swizzle on the image data") + ); }; return opts; } @@ -371,12 +379,24 @@ bool ActionConvert::process_file( auto image = std::make_shared( vtfFile->GetData(0, 0, 0, 0), procChanType, 4, vtfFile->GetWidth(), vtfFile->GetHeight(), true); if (!image->process(imglib::PROC_GL_TO_DX_NORM)) { - std::cerr << "Could not process vtf\n"; return false; } } + // Swizzle the image if requested + if (opts.has(opts::swizzle)) { + uint32_t mask = imglib::swizzle_from_str(opts.get(opts::swizzle).data()); + if (mask != lwiconv::NO_SWIZZLE) { + auto image = std::make_shared( + vtfFile->GetData(0, 0, 0, 0), procChanType, 4, vtfFile->GetWidth(), vtfFile->GetHeight(), true); + if (!image->swizzle(mask)) { + std::cerr << "Could not swizzle vtf\n"; + return false; + } + } + } + // Set the properties based on user input if (!set_properties(vtfFile.get())) { std::cerr << "Could not set properties on VTF\n"; diff --git a/src/common/image.cpp b/src/common/image.cpp index d38eb2d..7bc7ef1 100644 --- a/src/common/image.cpp +++ b/src/common/image.cpp @@ -306,6 +306,8 @@ static bool process_image_internal(void* indata, int comps, int w, int h, ProcFl T* cur = data + i; if (flags & PROC_GL_TO_DX_NORM) cur[1] = FULL_VAL - cur[1]; // Invert green channel + if (flags & PROC_INVERT_ALPHA) + cur[3] = FULL_VAL - cur[3]; } return true; } @@ -401,3 +403,33 @@ size_t imglib::channel_size(ChannelType type) { return 1; } } + +bool Image::swizzle(uint32_t mask) { + switch (m_type) { + case ChannelType::UInt8: + return lwiconv::swizzle(static_cast(m_data), m_width, m_height, m_comps, mask); + case ChannelType::UInt16: + return lwiconv::swizzle(static_cast(m_data), m_width, m_height, m_comps, mask); + case ChannelType::Float: + return lwiconv::swizzle(static_cast(m_data), m_width, m_height, m_comps, mask); + default: + return false; + } +} + +uint32_t imglib::swizzle_from_str(const char* str) { + uint32_t mask = 0; + for (int i = 0; i < lwiconv::MAX_CHANNELS; ++i, ++str) { + if (!*str) break; + int v = 0; + switch (*str) { + case 'r': v = 0; break; + case 'g': v = 1; break; + case 'b': v = 2; break; + case 'a': v = 3; break; + default: return 0xFFFFFFFF; + } + mask |= v << ((lwiconv::MAX_CHANNELS-i-1)*8); + } + return mask; +} diff --git a/src/common/image.hpp b/src/common/image.hpp index 2fb4be0..16bf641 100644 --- a/src/common/image.hpp +++ b/src/common/image.hpp @@ -14,6 +14,8 @@ namespace imglib { constexpr int MAX_CHANNELS = 4; + + using lwiconv::make_swizzle; /** * Per-channel data type @@ -48,6 +50,9 @@ namespace imglib using ProcFlags = uint32_t; inline constexpr ProcFlags PROC_GL_TO_DX_NORM = (1 << 0); + inline constexpr ProcFlags PROC_INVERT_ALPHA = (1 << 1); + + uint32_t swizzle_from_str(const char* str); /** * Returns the number of bytes per pixel for the format @@ -124,6 +129,12 @@ namespace imglib * @param pdef Default pixel fill for uninitialized pixels */ bool convert(ChannelType type, int channels = -1, const lwiconv::PixelF& pdef = {0,0,0,1}); + + /** + * Perform in-place swizzle of components + * @param mask Swizzle mask. @see lwiconv::make_swizzle + */ + bool swizzle(uint32_t mask); /** * Returns the VTF format which matches up to the data we have internally here diff --git a/src/common/lwiconv.hpp b/src/common/lwiconv.hpp index 982e66f..db2237c 100644 --- a/src/common/lwiconv.hpp +++ b/src/common/lwiconv.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace lwiconv { @@ -90,6 +91,7 @@ inline void pixel_to_data(T* pout, const PixelF& p) { /** * \brief Convert buffer from one color format to another * The input and output buffers are assumed to be the same dimensions. + * in and out must not overlap. * \param in Pointer to the input buffer * \param out Pointer to the output buffer * \param w Width of the image @@ -138,4 +140,62 @@ static void convert_generic(const void* in, void* out, int w, int h, int inC, in outConv(pout, inConv(pin, channelDefaults)); } +constexpr uint32_t NO_SWIZZLE = 0x00010203; + +/** + * \brief Makes a 32-bit swizzle mask thingy + * Parameters A B C D indicate where the channel should get its data. + * So, make_swizzle(3, 2, 1, 0) would turn an RGBA image into ABGR + */ +static inline constexpr uint32_t make_swizzle(int a, int b, int c, int d) { + return (a & 0xFF) << 24 | (b & 0xFF) << 16 | (c & 0xFF) << 8 | (d & 0xFF); +} + +namespace detail { + +static inline PixelF swizzle_one(const PixelF& pixel, uint32_t mask) { + return { pixel.d[(mask >> 24) & 0xFF], pixel.d[(mask >> 16) & 0xFF], pixel.d[(mask >> 8) & 0xFF], pixel.d[mask & 0xFF] }; +} + +} + +template +static bool swizzle(T* image, int w, int h, int comps, uint32_t swizzle) { + assert(comps <= MAX_CHANNELS && comps > 0); + if (comps <= 0 || comps > MAX_CHANNELS) + return false; + + // Check that swizzle is in bounds too + for (int i = 0; i < comps; ++i) + if (((swizzle >> (i*8)) & 0xFF) > comps) + return false; + + using fnInConv = PixelF (*)(const T*, const PixelF&); + constexpr fnInConv inConvFuncs[MAX_CHANNELS] = { + detail::pixel_from_data, + detail::pixel_from_data, + detail::pixel_from_data, + detail::pixel_from_data, + }; + const fnInConv inConv = inConvFuncs[comps-1]; + + using fnOutConv = void (*)(T*, const PixelF&); + constexpr fnOutConv outConvFuncs[MAX_CHANNELS] = { + detail::pixel_to_data, + detail::pixel_to_data, + detail::pixel_to_data, + detail::pixel_to_data, + }; + const fnOutConv outConv = outConvFuncs[comps-1]; + + const size_t stride = comps * sizeof(T); + for (int m = 0; m < w*h; ++m, image += stride) { + outConv(image, detail::swizzle_one( + inConv(image, {0,0,0,0}), swizzle + )); + } + + return true; +} + } // lwiconv diff --git a/src/tests/image_tests.cpp b/src/tests/image_tests.cpp index f207260..dc267c0 100644 --- a/src/tests/image_tests.cpp +++ b/src/tests/image_tests.cpp @@ -118,3 +118,12 @@ TEST(ImageTests, Basic8To8) runTest(32, 32, 4, 4, {128, 0, 0xFF, 99}, {128, 0, 0xFF, 99}); } +TEST(ImageTests, BasicSwizzle) +{ + uint8_t img[4] = {1,2,3,4}; + ASSERT_TRUE(swizzle(img, 1, 1, 4, make_swizzle(3, 2, 1, 0))); + ASSERT_EQ(img[0], 4); + ASSERT_EQ(img[1], 3); + ASSERT_EQ(img[2], 2); + ASSERT_EQ(img[3], 1); +}