From 57713adfcbc17cfbab2efa4a67a7d157a72d6b23 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Fri, 31 Jul 2026 16:23:31 +0000 Subject: [PATCH] Use host-order pixel words on big-endian machines argb_t stored its channels as a fixed byte sequence (B,G,R,A), i.e. little-endian-packed ARGB8888. The ecosystem the pixmap ultimately feeds - pixman, cairo and every wl_shm consumer built on them - treats ARGB8888 as a host-order 32-bit word instead, so on big-endian machines (macOS PowerPC) every image came out with scrambled channels. Flip the channel order of argb_t on big-endian targets so the struct always represents a host-order ARGB word. All internal code accesses channels by name, so it is unaffected; the logical uint32_t conversions keep their 0xAARRGGBB meaning; the cairo path (SVG) is fixed for free since CAIRO_FORMAT_ARGB32 is native-order; and the libtiff path is fixed for free too, since TIFFReadRGBAImage fills host-order words and abgr_to_argb swaps channels by name. Decoders that ask their library for an explicit byte order are switched to the host-order equivalent on big-endian: libjpeg-turbo JCS_EXT_BGRA -> JCS_EXT_ARGB, libpng bgr+filler-after -> swap-alpha+filler-before (reader) and bgr -> swap-alpha (writer), libwebp MODE_BGRA -> MODE_ARGB, libavif AVIF_RGB_FORMAT_BGRA -> AVIF_RGB_FORMAT_ARGB. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01G6m1eevhAns3j85HMZYKna --- src/color.hpp | 38 ++++++++++++++++++++++++++++---------- src/formats/avif.cpp | 4 ++++ src/formats/jpeg.cpp | 4 ++++ src/formats/png.cpp | 11 +++++++++++ src/formats/webp.cpp | 4 ++++ 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/color.hpp b/src/color.hpp index 25b5144..b24a5ad 100644 --- src/color.hpp +++ src/color.hpp @@ -8,27 +8,45 @@ #include #include -/** ARGB color (BGRA in little endian). */ +// The ecosystem this pixmap feeds (pixman, cairo, and every wl_shm +// consumer built on them) treats ARGB8888 as a *host-order* 32-bit +// word, not a fixed byte sequence; on big-endian machines the +// in-memory channel order therefore flips. +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define ARGB_HOST_BIG_ENDIAN 1 +#elif defined(__BIG_ENDIAN__) +#define ARGB_HOST_BIG_ENDIAN 1 +#endif + +/** ARGB color (a host-order 32-bit ARGB word: BGRA bytes on + * little-endian machines, ARGB bytes on big-endian ones). */ struct argb_t { using channel = uint8_t; +#ifdef ARGB_HOST_BIG_ENDIAN + channel a = 0; // Alpha channel + channel r = 0; // Red channel + channel g = 0; // Green channel + channel b = 0; // Blue channel +#else channel b = 0; // Blue channel channel g = 0; // Green channel channel r = 0; // Red channel channel a = 0; // Alpha channel +#endif argb_t() = default; /** * Constructor. - * @param color color in little-endian + * @param color color as a logical 0xAARRGGBB value */ constexpr argb_t(const uint32_t color) - : b(color & max) - , g((color >> 8) & max) - , r((color >> 16) & max) - , a((color >> 24) & max) { + this->a = (color >> 24) & max; + this->r = (color >> 16) & max; + this->g = (color >> 8) & max; + this->b = color & max; } /** @@ -37,11 +55,11 @@ struct argb_t { */ constexpr argb_t(const channel a, const channel r, const channel g, const channel b) - : b(b) - , g(g) - , r(r) - , a(a) { + this->a = a; + this->r = r; + this->g = g; + this->b = b; } /** diff --git a/src/formats/avif.cpp b/src/formats/avif.cpp index 1a39d3d..552eb75 100644 --- src/formats/avif.cpp +++ src/formats/avif.cpp @@ -104,7 +104,11 @@ private: avifRGBImageSetDefaults(&rgb, avif->image); rgb.depth = 8; +#ifdef ARGB_HOST_BIG_ENDIAN + rgb.format = AVIF_RGB_FORMAT_ARGB; +#else rgb.format = AVIF_RGB_FORMAT_BGRA; +#endif rc = avifRGBImageAllocatePixels(&rgb); if (rc != AVIF_RESULT_OK) { diff --git a/src/formats/jpeg.cpp b/src/formats/jpeg.cpp index aea1a3e..493370f 100644 --- src/formats/jpeg.cpp +++ src/formats/jpeg.cpp @@ -49,7 +49,11 @@ public: case JCS_UNKNOWN: break; default: +#ifdef ARGB_HOST_BIG_ENDIAN + jpg.out_color_space = JCS_EXT_ARGB; +#else jpg.out_color_space = JCS_EXT_BGRA; +#endif break; } #endif // LIBJPEG_TURBO_VERSION diff --git a/src/formats/png.cpp b/src/formats/png.cpp index 1d421c7..c38afb2 100644 --- src/formats/png.cpp +++ src/formats/png.cpp @@ -79,10 +79,17 @@ public: if (bit_depth == 16) { png_set_strip_16(png); } +#ifdef ARGB_HOST_BIG_ENDIAN + png_set_filler(png, 0xff, PNG_FILLER_BEFORE); + png_set_packing(png); + png_set_packswap(png); + png_set_swap_alpha(png); +#else png_set_filler(png, 0xff, PNG_FILLER_AFTER); png_set_packing(png); png_set_packswap(png); png_set_bgr(png); +#endif png_set_expand(png); png_read_update_info(png, png); @@ -161,7 +168,11 @@ public: png_set_IHDR(png, png, pm.width(), pm.height(), 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); +#ifdef ARGB_HOST_BIG_ENDIAN + png_set_swap_alpha(png); +#else png_set_bgr(png); +#endif #ifdef PNG_TEXT_SUPPORTED // save meta info as text diff --git a/src/formats/webp.cpp b/src/formats/webp.cpp index 3761ad0..c33d922 100644 --- src/formats/webp.cpp +++ src/formats/webp.cpp @@ -38,7 +38,11 @@ public: // setup decoder WebPAnimDecoderOptions webp_opts; WebPAnimDecoderOptionsInit(&webp_opts); +#ifdef ARGB_HOST_BIG_ENDIAN + webp_opts.color_mode = MODE_ARGB; +#else webp_opts.color_mode = MODE_BGRA; +#endif webp_opts.use_threads = true; // open decoder