example/std-imagedumper: add background-color flag
diff --git a/example/stb-imagedumper/stb-imagedumper.c b/example/stb-imagedumper/stb-imagedumper.c index 440df43..0e0b381 100644 --- a/example/stb-imagedumper/stb-imagedumper.c +++ b/example/stb-imagedumper/stb-imagedumper.c
@@ -156,11 +156,12 @@ // ---- // // This program uses the STB API, not the wuffs_aux C++ API. We only ever pass -// 1 (STBI_grey) or 3 (STBI_rgb), not 0, 2 or 4, as the desired_channels -// argument to stbi_load_etc functions, so we only need ALLOW_Y and ALLOW_RGB. +// 1 (STBI_grey) or 4 (STBI_rgb_alpha), not 0, 2 or 3, as the desired_channels +// argument to stbi_load_etc functions, so we only need ALLOW_Y and +// ALLOW_RGBA_NONPREMUL. #define WUFFS_CONFIG__DST_PIXEL_FORMAT__ENABLE_ALLOWLIST #define WUFFS_CONFIG__DST_PIXEL_FORMAT__ALLOW_Y -#define WUFFS_CONFIG__DST_PIXEL_FORMAT__ALLOW_RGB +#define WUFFS_CONFIG__DST_PIXEL_FORMAT__ALLOW_RGBA_NONPREMUL // Defining this enables Wuffs' reimplementation of the STB library's API. #define WUFFS_CONFIG__ENABLE_DROP_IN_REPLACEMENT__STB @@ -170,6 +171,10 @@ // program to generate a stand-alone C file. #include "../../release/c/wuffs-unsupported-snapshot.c" +#define PARSE_COLOR_CONFIG__STATIC_FUNCTIONS +#define PARSE_COLOR_IMPLEMENTATION +#include "../../snippet/parse-color.c" + #endif // defined(USE_THE_REAL_STBI) // ---------------- @@ -625,6 +630,10 @@ bool demo; bool natural_size; int resize; + + // background_color is in 0xAABBGGRR order, the same as STB (assuming + // little-endian). + uint32_t background_color; } g_flags = {0}; static const char* g_usage = @@ -634,6 +643,7 @@ " -a or -ascii-art (use ASCII art instead of ANSI color codes)\n" " -B or -braille-art-dark-mode (use white-on-black Braille art)\n" " -b or -braille-art-light-mode (use black-on-white Braille art)\n" + " -c=orange or -background-color=ff9800 (set a background color)\n" " -demo (dump the built-in demonstration images)\n" " -n or -natural-size (use each image's natural size); this\n" " overrides -resize=N\n" @@ -684,6 +694,18 @@ g_flags.natural_size = true; continue; } + if (!strncmp(arg, "c=", 2) || !strncmp(arg, "background-color=", 17)) { + while (*arg++ != '=') { + } + int64_t c = parse_color(arg, strlen(arg)); + if (c < 0) { + return g_usage; + } + g_flags.background_color = wuffs_base__color_swap_u32_argb_abgr( + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + (uint32_t)(c))); + continue; + } if (!strncmp(arg, "resize=", 7)) { while (*arg++ != '=') { } @@ -753,7 +775,7 @@ if (!inout_w || !inout_h || (dst_wh <= 0) || (0x4000 < dst_wh) || // (*inout_w <= 0) || (0xffffff < *inout_w) || // (*inout_h <= 0) || (0xffffff < *inout_h) || // - ((bytes_per_pixel != 1) && (bytes_per_pixel != 3))) { + ((bytes_per_pixel != 1) && (bytes_per_pixel != 4))) { printf("main: resize failed: invalid argument\n"); return NULL; } @@ -791,6 +813,7 @@ uint64_t tot0 = 0; uint64_t tot1 = 0; uint64_t tot2 = 0; + uint64_t tot3 = 0; uint64_t totw = 0; uint64_t ty = dy * sh; @@ -811,10 +834,11 @@ size_t i = (((size_t)sy * (size_t)sw) + (size_t)sx) * 1; tot0 += weight * src_pixels[i + 0]; } else { - size_t i = (((size_t)sy * (size_t)sw) + (size_t)sx) * 3; + size_t i = (((size_t)sy * (size_t)sw) + (size_t)sx) * 4; tot0 += weight * src_pixels[i + 0]; tot1 += weight * src_pixels[i + 1]; tot2 += weight * src_pixels[i + 2]; + tot3 += weight * src_pixels[i + 3]; } } } @@ -825,6 +849,7 @@ *dst++ = (unsigned char)(((tot0 * 2) + totw) / (totw * 2)); *dst++ = (unsigned char)(((tot1 * 2) + totw) / (totw * 2)); *dst++ = (unsigned char)(((tot2 * 2) + totw) / (totw * 2)); + *dst++ = (unsigned char)(((tot3 * 2) + totw) / (totw * 2)); } } } @@ -835,6 +860,55 @@ return dst_pixels; } +static inline uint32_t // +composite_premul_nonpremul_u32_axxx(uint32_t dst_premul, + uint32_t src_nonpremul) { + // Extract 16-bit color components. + uint32_t da = 0x101 * (0xFF & (dst_premul >> 24)); + uint32_t dr = 0x101 * (0xFF & (dst_premul >> 16)); + uint32_t dg = 0x101 * (0xFF & (dst_premul >> 8)); + uint32_t db = 0x101 * (0xFF & (dst_premul >> 0)); + uint32_t sa = 0x101 * (0xFF & (src_nonpremul >> 24)); + uint32_t sr = 0x101 * (0xFF & (src_nonpremul >> 16)); + uint32_t sg = 0x101 * (0xFF & (src_nonpremul >> 8)); + uint32_t sb = 0x101 * (0xFF & (src_nonpremul >> 0)); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 8-bit color. + da >>= 8; + dr >>= 8; + dg >>= 8; + db >>= 8; + + // Combine components. + return (db << 0) | (dg << 8) | (dr << 16) | (da << 24); +} + +static void // +apply_background_color(uint32_t bg_color_premul, + unsigned char* pixels, + int w, + int h) { + for (int64_t n = ((int64_t)(w)) * ((int64_t)(h)); n--;) { + uint32_t fg_color_nonpremul = + wuffs_base__peek_u32le__no_bounds_check(pixels); + if ((fg_color_nonpremul >> 24) != 0xFFu) { + wuffs_base__poke_u32le__no_bounds_check( + pixels, composite_premul_nonpremul_u32_axxx(bg_color_premul, + fg_color_nonpremul)); + } + pixels += 4; + } +} + // ---------------- #define MAX_INCL_DIMENSION 10000 @@ -855,8 +929,9 @@ int channels_in_file = 0; int bytes_per_pixel = (g_flags.ascii_art || g_flags.braille_art_dark_mode || g_flags.braille_art_light_mode) - ? STBI_grey // 1. - : STBI_rgb; // 3. + ? STBI_grey // 1. + : STBI_rgb_alpha; // 4. + unsigned char* src_pixels = NULL; #if defined(WUFFS_EXAMPLE_USE_TIMERS) @@ -975,7 +1050,8 @@ fwrite(buffer, sizeof(char), dst - buffer, stdout); } - } else { + } else if (bytes_per_pixel == 4) { + apply_background_color(g_flags.background_color, pixels, w, h); unsigned char* src = pixels; for (int y = 0; y < h; y += 2) { char* dst = buffer; @@ -990,21 +1066,21 @@ dst += sprintf(dst, "\x1B[38;2;%d;%d;%dm\xE2\x96\x80", // upper0, upper1, upper2); } else { - uint8_t lower0 = (uint8_t)src[(w * 3) + 0]; - uint8_t lower1 = (uint8_t)src[(w * 3) + 1]; - uint8_t lower2 = (uint8_t)src[(w * 3) + 2]; + uint8_t lower0 = (uint8_t)src[(w * bytes_per_pixel) + 0]; + uint8_t lower1 = (uint8_t)src[(w * bytes_per_pixel) + 1]; + uint8_t lower2 = (uint8_t)src[(w * bytes_per_pixel) + 2]; dst += sprintf(dst, "\x1B[38;2;%d;%d;%dm\x1B[48;2;%d;%d;%dm\xE2\x96\x80", // upper0, upper1, upper2, lower0, lower1, lower2); } - src += 3; + src += bytes_per_pixel; } memcpy(dst, "\x1B[0m\n", 5); dst += 5; fwrite(buffer, sizeof(char), dst - buffer, stdout); if ((y + 1) < h) { - src += w * 3; + src += w * bytes_per_pixel; } } }
diff --git a/snippet/parse-color.c b/snippet/parse-color.c new file mode 100644 index 0000000..4e9d9d1 --- /dev/null +++ b/snippet/parse-color.c
@@ -0,0 +1,195 @@ +// Copyright 2026 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// ---------------- + +// This is a small, self-contained, single-file C library to parse an RGB or +// ARGB color as hex (like "b0279c") or a well-known name (like "purple"). +// +// To use this file as a "foo.c"-like implementation, instead of a "foo.h"-like +// header, #define PARSE_COLOR_IMPLEMENTATION before #include'ing or compiling +// it. +// +// As an option, you may also #define PARSE_COLOR_CONFIG__STATIC_FUNCTIONS to +// make these functions have static storage. This can help the compiler ignore +// or discard unused code, which can produce faster compiles and smaller +// binaries. + +#ifndef PARSE_COLOR_INCLUDE_GUARD +#define PARSE_COLOR_INCLUDE_GUARD + +#if defined(PARSE_COLOR_CONFIG__STATIC_FUNCTIONS) +#define PARSE_COLOR__MAYBE_STATIC static +#else +#define PARSE_COLOR__MAYBE_STATIC +#endif + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +// parse_color parses a string that's a hex color (like "#ff8" or "ffb0279c") +// or a well-known name (like "black", "purple" or "darkred"). Well-known names +// are an ad-hoc set based on the Material Design color names, optionally +// prefixed by "dark" or "light". +// +// On success, it returns a non-negative integer (that fits into a uint32_t, +// but cast as an int64_t) that is 0xAARRGGBB, using non-premultiplied alpha. +// +// On failure, it returns a negative int64_t. +// +// Parsing is case-insensitive. This function is thread-safe. +PARSE_COLOR__MAYBE_STATIC int64_t // +parse_color(const char* s_ptr, size_t s_len); + +// -------- + +#ifdef PARSE_COLOR_IMPLEMENTATION + +PARSE_COLOR__MAYBE_STATIC int64_t // +parse_color(const char* s_ptr, size_t s_len) { + if ((s_len <= 0) || (16 <= s_len)) { + return -1; + } + + bool starts_with_hash = (*s_ptr == '#'); + if (starts_with_hash) { + s_ptr++; + s_len--; + if (s_len <= 0) { + return -1; + } + } + + // Canonicalize ASCII to lower case. + char buf[16]; + for (size_t i = 0; i < s_len; i++) { + if (((int8_t)(s_ptr[i])) < 0) { + return -1; + } + buf[i] = s_ptr[i] | 0x20; + } + + // Map well known names to Material Design "500 shade" colors. + if (!starts_with_hash) { + const char* b_ptr = &buf[0]; + size_t b_len = s_len; + int adjustment = 0; + if ((b_len > 4) && !memcmp(buf, "dark", 4)) { + adjustment = 1; + b_ptr += 4; + b_len -= 4; + } else if ((b_len > 5) && !memcmp(buf, "light", 5)) { + adjustment = 2; + b_ptr += 5; + b_len -= 5; + } + + static const int num_offsets = 18; + static const uint32_t offsets[18] = { + 0x00000000u, // + 0x05000000u, // black + 0x0AFFFFFFu, // white + 0x0DF44336u, // red + 0x11E91E63u, // pink + 0x179C27B0u, // purple + 0x1D3F51B5u, // indigo + 0x212196F3u, // blue + 0x2500BCD4u, // cyan + 0x29009688u, // teal + 0x2E4CAF50u, // green + 0x32CDDC39u, // lime + 0x38FFEB3Bu, // yellow + 0x3DFFC107u, // amber + 0x43FF9800u, // orange + 0x48795548u, // brown + 0x4C9E9E9Eu, // gray + 0x509E9E9Eu, // grey + }; + + static const char names[] = + "blackwhiteredpinkpurpleindigobluecyantealgreenlimeyellowamberorangebro" + "wngraygrey"; + + for (int i = 1; i < num_offsets; i++) { + size_t name_len = (offsets[i] >> 24) - (offsets[i - 1] >> 24); + if (name_len != b_len) { + continue; + } + const char* p = b_ptr; + const char* p_end = &buf[s_len]; + const char* q = &names[offsets[i - 1] >> 24]; + + while (1) { + if (p == p_end) { + uint32_t r = 0xFFu & (offsets[i] >> 16); + uint32_t g = 0xFFu & (offsets[i] >> 8); + uint32_t b = 0xFFu & (offsets[i] >> 0); + if (adjustment == 1) { + r /= 2; + g /= 2; + b /= 2; + } else if (adjustment == 2) { + r = 0xFFu - ((0xFFu - r) / 2); + g = 0xFFu - ((0xFFu - g) / 2); + b = 0xFFu - ((0xFFu - b) / 2); + } + return ((int64_t)(0xFF000000u | (r << 16) | (g << 8) | b)); + + } else if (*p++ != *q++) { + break; + } + } + } + } + + uint32_t ret = 0u; + uint32_t multiplier = 0u; + int shift = 0; + + if (s_len == 3) { + ret = 0xFFu; + multiplier = 0x11u; + shift = 8; + } else if (s_len == 4) { + ret = 0u; + multiplier = 0x11u; + shift = 8; + } else if (s_len == 6) { + ret = 0xFFu; + multiplier = 0x01u; + shift = 4; + } else if (s_len == 8) { + ret = 0u; + multiplier = 0x01u; + shift = 4; + } else { + return -1; + } + + for (size_t i = 0; i < s_len; i++) { + char c = buf[i]; + if (('0' <= c) && (c <= '9')) { + c -= '0'; + } else if (('a' <= c) && (c <= 'f')) { + c -= 'a' - 10; + } else { + return -1; + } + ret <<= shift; + ret |= multiplier * ((uint32_t)(c)); + } + + return ((int64_t)(ret)); +} + +#endif // PARSE_COLOR_IMPLEMENTATION +#endif // PARSE_COLOR_INCLUDE_GUARD