@@ -1877,6 +1877,37 @@ class format_4bit : public format {
18771877 // / @endcond
18781878};
18791879
1880+ // / @cond DOXYGEN_EXCLUDE
1881+ class format_8bit_dyn ;
1882+ // / Runtime palette (<=256 sRGB entries, BGR-packed). Shared by
1883+ // / image<format_8bit,...> and dynamic_image<format_8bit_dyn>.
1884+ struct runtime_palette {
1885+ std::array<uint32_t , 256 > pal{};
1886+ size_t count_{0 };
1887+ constexpr runtime_palette () = default;
1888+ explicit runtime_palette (std::span<const std::array<uint8_t , 3 >> rgb) {
1889+ count_ = std::min (rgb.size (), size_t {256 });
1890+ for (size_t i = 0 ; i < count_; ++i) {
1891+ pal[i] = uint32_t (rgb[i][0 ]) | (uint32_t (rgb[i][1 ]) << 8 ) | (uint32_t (rgb[i][2 ]) << 16 );
1892+ }
1893+ }
1894+ [[nodiscard]] size_t size () const noexcept { return count_; }
1895+ [[nodiscard]] uint32_t at (size_t i) const noexcept { return pal[i]; }
1896+ [[nodiscard]] uint8_t nearest (int32_t r, int32_t g, int32_t b) const {
1897+ int32_t best_d = std::numeric_limits<int32_t >::max ();
1898+ uint8_t best_i = 0 ;
1899+ for (size_t i = 0 ; i < count_; ++i) {
1900+ const int32_t dr = r - int32_t ((pal[i] >> 0 ) & 0xFF );
1901+ const int32_t dg = g - int32_t ((pal[i] >> 8 ) & 0xFF );
1902+ const int32_t db = b - int32_t ((pal[i] >> 16 ) & 0xFF );
1903+ const int32_t d = dr*dr + dg*dg + db*db;
1904+ if (d < best_d) { best_d = d; best_i = uint8_t (i); if (d == 0 ) break ; }
1905+ }
1906+ return best_i;
1907+ }
1908+ };
1909+ // / @endcond
1910+
18801911/* *
18811912 * @brief 8-bit format, 256 colors total. Use as template parameter for image. Example:
18821913 *
@@ -1892,6 +1923,8 @@ template <size_t W, size_t H, bool GRAYSCALE, bool USE_SPAN>
18921923class format_8bit : public format {
18931924 public:
18941925 // / @cond DOXYGEN_EXCLUDE
1926+ using runtime_palette = ::constixel::runtime_palette;
1927+
18951928 static constexpr size_t sixel_bitset_size = 256 ;
18961929 static constexpr size_t bits_per_pixel = 8 ;
18971930 static constexpr size_t bytes_per_line = W;
@@ -2181,6 +2214,14 @@ class format_8bit : public format {
21812214 }
21822215 });
21832216 }
2217+
2218+ template <typename F>
2219+ static void sixel_with_palette (std::span<const uint8_t , image_size> data,
2220+ const runtime_palette &rp, F &&char_out);
2221+ static void blit_RGBA_with_palette (std::span<uint8_t , image_size> data,
2222+ const rect<int32_t > &r,
2223+ const uint8_t *ptr, int32_t stride,
2224+ const runtime_palette &rp);
21842225 // / @endcond
21852226};
21862227
@@ -3569,6 +3610,23 @@ class image {
35693610 : data(other) {
35703611 }
35713612
3613+ /* *
3614+ * \brief Creates an image that uses a caller-supplied runtime palette
3615+ * instead of the format's compile-time-baked palette.
3616+ * \param palette Up to 256 sRGB byte triples.
3617+ */
3618+ explicit image (std::span<const std::array<uint8_t , 3 >> palette)
3619+ requires(!USE_SPAN && requires { typename T<W, H, GRAYSCALE , USE_SPAN >::runtime_palette; })
3620+ : palette_(palette), has_custom_palette_(true ) {}
3621+
3622+ /* *
3623+ * \brief USE_SPAN + custom-palette variant.
3624+ */
3625+ image (const std::span<uint8_t , T<W, H, GRAYSCALE , USE_SPAN >::image_size> &other,
3626+ std::span<const std::array<uint8_t , 3 >> palette)
3627+ requires (USE_SPAN && requires { typename T<W, H, GRAYSCALE , USE_SPAN >::runtime_palette; })
3628+ : data(other), palette_(palette), has_custom_palette_(true ) {}
3629+
35723630 /* *
35733631 * \brief Boolean indicating that the palette is grayscale instead of color.
35743632 * \return If true, the palette is grayscale. If false a colored palette is used.
@@ -4987,6 +5045,12 @@ class image {
49875045 constixel::rect<int32_t > blitrect{.x = x, .y = y, .w = w, .h = h};
49885046 blitrect &= {.x = 0 , .y = 0 , .w = W, .h = H};
49895047 blitrect &= {.x = x, .y = y, .w = iw, .h = ih};
5048+ if constexpr (requires { typename T<W, H, GRAYSCALE , USE_SPAN >::runtime_palette; }) {
5049+ if (has_custom_palette_) {
5050+ T<W, H, GRAYSCALE , USE_SPAN >::blit_RGBA_with_palette (data, blitrect, ptr, stride, palette_);
5051+ return ;
5052+ }
5053+ }
49905054 T<W, H, GRAYSCALE , USE_SPAN >::blit_RGBA (data, blitrect, ptr, stride);
49915055 }
49925056
@@ -5114,6 +5178,12 @@ class image {
51145178 */
51155179 template <size_t S = 1 , typename F>
51165180 constexpr void sixel (F &&char_out) const {
5181+ if constexpr (requires { typename T<W, H, GRAYSCALE , USE_SPAN >::runtime_palette; }) {
5182+ if (has_custom_palette_) {
5183+ T<W, H, GRAYSCALE , USE_SPAN >::sixel_with_palette (data, palette_, std::forward<F>(char_out));
5184+ return ;
5185+ }
5186+ }
51175187 T<W, H, GRAYSCALE , USE_SPAN >::template sixel<S>(data, std::forward<F>(char_out), {0 , 0 , W, H});
51185188 }
51195189
@@ -5133,6 +5203,12 @@ class image {
51335203 */
51345204 template <size_t S = 1 , typename F>
51355205 constexpr void sixel (F &&char_out, const rect<int32_t > &rect) const {
5206+ if constexpr (requires { typename T<W, H, GRAYSCALE , USE_SPAN >::runtime_palette; }) {
5207+ if (has_custom_palette_) {
5208+ T<W, H, GRAYSCALE , USE_SPAN >::sixel_with_palette (data, palette_, std::forward<F>(char_out));
5209+ return ;
5210+ }
5211+ }
51365212 T<W, H, GRAYSCALE , USE_SPAN >::template sixel<S>(data, std::forward<F>(char_out), rect);
51375213 }
51385214
@@ -6255,10 +6331,183 @@ class image {
62556331 */
62566332 T<W, H, GRAYSCALE , USE_SPAN > format{};
62576333
6334+ /* *
6335+ * @private
6336+ */
6337+ runtime_palette palette_{};
6338+
6339+ /* *
6340+ * @private
6341+ */
6342+ bool has_custom_palette_{false };
6343+
62586344// / @endcond // DOXYGEN_EXCLUDE
62596345#endif // #ifndef __INTELLISENSE__
62606346};
62616347
6348+ /* *
6349+ * @brief 8-bit indexed format for runtime-sized images. Use as Ops parameter
6350+ * for dynamic_image. Same palette / sixel semantics as format_8bit
6351+ * but with W and H carried at runtime.
6352+ */
6353+ class format_8bit_dyn : public format {
6354+ public:
6355+ // / @cond DOXYGEN_EXCLUDE
6356+ static constexpr size_t bits_per_pixel = 8 ;
6357+ static constexpr size_t sixel_bitset_size = 256 ;
6358+ static constexpr size_t bytes_per_line (size_t w) { return w; }
6359+ static constexpr size_t image_size (size_t w, size_t h) { return bytes_per_line (w) * h; }
6360+ using runtime_palette = ::constixel::runtime_palette;
6361+
6362+ static uint8_t nearest (int32_t r, int32_t g, int32_t b, const runtime_palette *rp) {
6363+ return rp ? rp->nearest (r, g, b)
6364+ : format_8bit<1 , 1 , false , false >::quant.nearest (r, g, b);
6365+ }
6366+ static size_t palette_size (const runtime_palette *rp) { return rp ? rp->size () : 256 ; }
6367+ static uint32_t palette_at (size_t c, const runtime_palette *rp) {
6368+ return rp ? rp->at (c) : format_8bit<1 , 1 , false , false >::quant.palette ()[c];
6369+ }
6370+
6371+ static void blit_RGBA (uint8_t *dst, size_t dst_w, size_t dst_h,
6372+ const rect<int32_t > &r, const uint8_t *src, int32_t stride,
6373+ const runtime_palette *rp) {
6374+ rect<int32_t > ir{.x = 0 , .y = 0 , .w = int32_t (dst_w), .h = int32_t (dst_h)};
6375+ ir &= r;
6376+ if (ir.w <= 0 || ir.h <= 0 ) return ;
6377+ const auto rx = size_t (ir.x ), ry = size_t (ir.y );
6378+ const auto rw = size_t (ir.w ), rh = size_t (ir.h );
6379+ for (size_t y = 0 ; y < rh; ++y) {
6380+ for (size_t x = 0 ; x < rw; ++x) {
6381+ const uint8_t *px = &src[y * size_t (stride) + x * 4 ];
6382+ dst[(ry + y) * dst_w + (rx + x)] = nearest (px[0 ], px[1 ], px[2 ], rp);
6383+ }
6384+ }
6385+ }
6386+
6387+ template <typename F>
6388+ static void sixel (const uint8_t *data, size_t w, size_t h,
6389+ const runtime_palette *rp, F &&char_out) {
6390+ // \033P;1q — P2=1 (unset pixels transparent). Avoids Microsoft
6391+ // Terminal #17887 where omitted P2 fills cell-padding rows with
6392+ // arbitrary color. Visually equivalent for fully-painted images.
6393+ std::forward<F>(char_out)(char (0x1b ));
6394+ std::forward<F>(char_out)(' P' );
6395+ std::forward<F>(char_out)(' ;' );
6396+ std::forward<F>(char_out)(' 1' );
6397+ std::forward<F>(char_out)(' q' );
6398+ std::forward<F>(char_out)(' "' );
6399+ sixel_number (std::forward<F>(char_out), 1 ); std::forward<F>(char_out)(' ;' );
6400+ sixel_number (std::forward<F>(char_out), 1 ); std::forward<F>(char_out)(' ;' );
6401+ sixel_number (std::forward<F>(char_out), uint16_t (w)); std::forward<F>(char_out)(' ;' );
6402+ sixel_number (std::forward<F>(char_out), uint16_t (h));
6403+ const size_t pal_n = palette_size (rp);
6404+ for (size_t c = 0 ; c < pal_n; ++c) {
6405+ sixel_color (std::forward<F>(char_out), uint16_t (c), palette_at (c, rp));
6406+ }
6407+ palette_bitset<uint8_t , sixel_bitset_size> pset{};
6408+ std::array<uint8_t , sixel_bitset_size> stack{};
6409+ for (size_t y = 0 ; y < h; y += 6 ) {
6410+ pset.clear ();
6411+ const size_t y_end = std::min (y + 6 , h);
6412+ for (size_t yy = y; yy < y_end; ++yy)
6413+ for (size_t x = 0 ; x < w; ++x) pset.mark (data[yy * w + x]);
6414+ const size_t stack_count = pset.genstack (stack);
6415+ for (size_t s = 0 ; s < stack_count; ++s) {
6416+ const uint8_t col = stack[s];
6417+ if (col != 0 ) std::forward<F>(char_out)(' $' );
6418+ std::forward<F>(char_out)(' #' );
6419+ sixel_number (std::forward<F>(char_out), uint16_t (col));
6420+ auto bits_at = [&](size_t x) -> uint8_t {
6421+ uint8_t bits = 0 ;
6422+ for (size_t y6 = 0 ; y6 < 6 ; ++y6) {
6423+ const size_t yy = y + y6;
6424+ if (yy >= h) break ;
6425+ if (data[yy * w + x] == col) bits = uint8_t (bits | (1u << y6));
6426+ }
6427+ return bits;
6428+ };
6429+ size_t x = 0 ;
6430+ while (x < w) {
6431+ const uint8_t bits6 = bits_at (x);
6432+ size_t run = 0 ;
6433+ while (x + 1 + run < w && bits_at (x + 1 + run) == bits6 && run < 254 ) ++run;
6434+ if (run > 3 ) {
6435+ std::forward<F>(char_out)(' !' );
6436+ sixel_number (std::forward<F>(char_out), uint16_t (run + 1 ));
6437+ x += run;
6438+ }
6439+ std::forward<F>(char_out)(char (' ?' + bits6));
6440+ ++x;
6441+ }
6442+ }
6443+ std::forward<F>(char_out)(' -' );
6444+ }
6445+ sixel_end (std::forward<F>(char_out));
6446+ }
6447+ // / @endcond
6448+ };
6449+
6450+ // / @cond DOXYGEN_EXCLUDE
6451+ template <size_t W, size_t H, bool GRAYSCALE , bool USE_SPAN >
6452+ template <typename F>
6453+ void format_8bit<W, H, GRAYSCALE , USE_SPAN >::sixel_with_palette(
6454+ std::span<const uint8_t , format_8bit<W, H, GRAYSCALE , USE_SPAN >::image_size> data,
6455+ const runtime_palette &rp, F &&char_out) {
6456+ format_8bit_dyn::sixel (data.data (), W, H, &rp, std::forward<F>(char_out));
6457+ }
6458+ template <size_t W, size_t H, bool GRAYSCALE , bool USE_SPAN >
6459+ void format_8bit<W, H, GRAYSCALE , USE_SPAN >::blit_RGBA_with_palette(
6460+ std::span<uint8_t , format_8bit<W, H, GRAYSCALE , USE_SPAN >::image_size> data,
6461+ const rect<int32_t > &r, const uint8_t *ptr, int32_t stride,
6462+ const runtime_palette &rp) {
6463+ format_8bit_dyn::blit_RGBA (data.data (), W, H, r, ptr, stride, &rp);
6464+ }
6465+ // / @endcond
6466+
6467+ /* *
6468+ * @brief Runtime-sized indexed image. Sibling of image<F,W,H> for callers
6469+ * that don't know dimensions at compile time. Same operation surface.
6470+ * @tparam Ops Runtime-format ops class (e.g. format_8bit_dyn).
6471+ */
6472+ template <typename Ops>
6473+ class dynamic_image {
6474+ public:
6475+ dynamic_image (size_t w, size_t h)
6476+ : data_(Ops::image_size(w, h), uint8_t {0 }), w_(w), h_(h) {}
6477+ dynamic_image (size_t w, size_t h, std::span<const std::array<uint8_t , 3 >> palette)
6478+ : data_(Ops::image_size(w, h), uint8_t {0 }), w_(w), h_(h),
6479+ palette_ (palette), has_custom_palette_(true ) {}
6480+
6481+ [[nodiscard]] size_t width () const noexcept { return w_; }
6482+ [[nodiscard]] size_t height () const noexcept { return h_; }
6483+
6484+ void blit_RGBA (int32_t x, int32_t y, int32_t w, int32_t h,
6485+ const uint8_t *ptr, int32_t /* iw*/ , int32_t /* ih*/ , int32_t stride) {
6486+ Ops::blit_RGBA (data_.data (), w_, h_, {x, y, w, h}, ptr, stride,
6487+ has_custom_palette_ ? &palette_ : nullptr );
6488+ }
6489+
6490+ template <typename F>
6491+ void sixel (F &&char_out) const {
6492+ Ops::sixel (data_.data (), w_, h_,
6493+ has_custom_palette_ ? &palette_ : nullptr ,
6494+ std::forward<F>(char_out));
6495+ }
6496+
6497+ #ifdef CONSTIXEL_ENABLE_COUT
6498+ void sixel_to_cout () const {
6499+ sixel ([](char ch) { std::cout.put (ch); });
6500+ std::cout << ' \n ' ;
6501+ }
6502+ #endif
6503+
6504+ private:
6505+ std::vector<uint8_t > data_;
6506+ size_t w_, h_;
6507+ typename Ops::runtime_palette palette_{};
6508+ bool has_custom_palette_{false };
6509+ };
6510+
62626511} // namespace constixel
62636512
62646513#endif // CONSTIXEL_HPP_
0 commit comments