diff -Nru chafa-1.2.1/acinclude.m4 chafa-1.12.4/acinclude.m4 --- chafa-1.2.1/acinclude.m4 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/acinclude.m4 2022-11-12 01:18:35.000000000 +0000 @@ -8,7 +8,7 @@ [ # check for the presence of the XML catalog AC_ARG_WITH([xml-catalog], - AC_HELP_STRING([--with-xml-catalog=CATALOG], + AS_HELP_STRING([--with-xml-catalog=CATALOG], [path to xml catalog to use]),, [with_xml_catalog=/etc/xml/catalog]) jh_found_xmlcatalog=true @@ -56,3 +56,55 @@ [$4]) fi ]) + +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff -Nru chafa-1.2.1/AUTHORS chafa-1.12.4/AUTHORS --- chafa-1.2.1/AUTHORS 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/AUTHORS 2022-11-12 01:18:35.000000000 +0000 @@ -1,5 +1,5 @@ Chafa is principally written and maintained by Hans Petter Jansson -. Feel free to contact him with any questions and +. Feel free to contact him with any questions and comments. There is a growing list of contributors; these can be found in the @@ -7,12 +7,23 @@ git log --pretty="format:%an <%ae>" | sort -f | uniq -Per 2019-08-08, this yields the following (sans duplicates): +Per 2022-03-18, this yields the following (sans duplicates): alkahest +begasus +Biswapriyo Nath +Daniel Eklöf +data-man +Emanuel Haupt Felix Yan -Hans Petter Jansson +Hans Petter Jansson Michael Vetter +Mikel Olasagasti Uranga Mo Zhou Ricardo Arguello Robert-André Mauchin +Roman Wagner +Sotiris Papatheodorou +Sudhakar Verma +Tim Gates +Øyvind Kolås diff -Nru chafa-1.2.1/autogen.sh chafa-1.12.4/autogen.sh --- chafa-1.2.1/autogen.sh 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/autogen.sh 2022-11-12 01:18:35.000000000 +0000 @@ -13,17 +13,8 @@ MISSING_TOOLS= -GTKDOCIZE=$(which gtkdocize 2>/dev/null) -if test -z $GTKDOCIZE; then - echo "You don't have gtk-doc installed, and thus won't be able to generate the documentation." - rm -f gtk-doc.make - cat > gtk-doc.make < /dev/null 2>&1 || { MISSING_TOOLS="${MISSING_TOOLS}autoconf " @@ -40,48 +31,83 @@ DIE=1 } +(pkg-config --version) < /dev/null > /dev/null 2>&1 || { + MISSING_TOOLS="${MISSING_TOOLS}pkg-config " + DIE=1 +} + +if test "$DIE" -eq 1; then + ${MY_ECHO} + ${MY_ECHO} -e "Missing mandatory tools:\e[1;31m $MISSING_TOOLS" + ${MY_ECHO} -e "\e[0m" + ${MY_ECHO} "These are required for building Chafa from its git repository." + ${MY_ECHO} "You should be able to install them using your operating system's" + ${MY_ECHO} "package manager (apt-get, yum, zypper or similar). Alternately" + ${MY_ECHO} "they can be obtained directly from GNU: https://ftp.gnu.org/gnu/" + ${MY_ECHO} + ${MY_ECHO} "If you can't provide these tools, you may still be able to" + ${MY_ECHO} "build Chafa from a tarball release: https://hpjansson.org/chafa/releases/" + ${MY_ECHO} +fi + if test "$DIE" -eq 1; then - echo - echo "The following tools were not found: $MISSING_TOOLS" - echo - echo "These are required for building Chafa from its git repository." - echo "You should be able to install them using your operating system's" - echo "package manager (apt-get, yum, zypper or similar). Alternately" - echo "they can be obtained directly from GNU: http://ftp.gnu.org/gnu/" - echo exit 1 fi test $TEST_TYPE $FILE || { - echo - echo "You must run this script in the top-level $PROJECT directory" - echo + ${MY_ECHO} + ${MY_ECHO} "You must run this script in the top-level $PROJECT directory." + ${MY_ECHO} exit 1 } -if test -z "$*"; then - echo - echo "I am going to run ./configure with no arguments - if you wish " - echo "to pass any to it, please specify them on the $0 command line." - echo +if test x$NOCONFIGURE = x && test -z "$*"; then + ${MY_ECHO} + ${MY_ECHO} "I am going to run ./configure with no arguments - if you wish " + ${MY_ECHO} "to pass any to it, please specify them on the $0 command line." + ${MY_ECHO} fi am_opt="--include-deps --add-missing" -echo "Running libtoolize..." +${MY_ECHO} "Running libtoolize..." libtoolize --force --copy -echo "Running aclocal..." +GTKDOCIZE=$(which gtkdocize 2>/dev/null) + +if test -z $GTKDOCIZE; then + ${MY_ECHO} -e "Missing optional tool:\e[1;33m gtk-doc" + ${MY_ECHO} -e "\e[0m" + ${MY_ECHO} "Without this, no developer documentation will be generated." + ${MY_ECHO} + rm -f gtk-doc.make + cat > gtk-doc.make < /dev/null 2>&1 && autoheader -echo "Running automake..." +${MY_ECHO} "Running automake..." automake -a $am_opt -echo "Running autoconf..." +${MY_ECHO} "Running autoconf..." autoconf cd $ORIGDIR -$srcdir/configure --enable-maintainer-mode "$@" + +conf_flags="--enable-maintainer-mode" + +if test x$NOCONFIGURE = x; then + ${MY_ECHO} Running $srcdir/configure $conf_flags "$@" ... + $srcdir/configure $conf_flags "$@" || exit 1 +else + ${MY_ECHO} Skipping configure process. +fi diff -Nru chafa-1.2.1/chafa/chafa-canvas.c chafa-1.12.4/chafa/chafa-canvas.c --- chafa-1.2.1/chafa/chafa-canvas.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-canvas.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -22,9 +22,14 @@ #include #include #include -#include "smolscale/smolscale.h" -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" +#include "chafa.h" +#include "internal/chafa-batch.h" +#include "internal/chafa-canvas-internal.h" +#include "internal/chafa-canvas-printer.h" +#include "internal/chafa-private.h" +#include "internal/chafa-pixops.h" +#include "internal/chafa-work-cell.h" +#include "internal/smolscale/smolscale.h" /** * SECTION:chafa-canvas @@ -37,583 +42,596 @@ * specify any parameters, like the geometry, color space and so on, you * must create a #ChafaCanvasConfig first. * - * You can draw an image to the canvas using chafa_canvas_set_contents () - * and create an ANSI text representation of the canvas' current contents - * using chafa_canvas_build_ansi (). + * You can draw an image to the canvas using chafa_canvas_draw_all_pixels () + * and create an ANSI text (or sixel) representation of the canvas' current + * contents using chafa_canvas_build_ansi (). **/ -/** - * ChafaPixelType: - * @CHAFA_PIXEL_RGBA8_PREMULTIPLIED: Premultiplied RGBA, 8 bits per channel. - * @CHAFA_PIXEL_BGRA8_PREMULTIPLIED: Premultiplied BGRA, 8 bits per channel. - * @CHAFA_PIXEL_ARGB8_PREMULTIPLIED: Premultiplied ARGB, 8 bits per channel. - * @CHAFA_PIXEL_ABGR8_PREMULTIPLIED: Premultiplied ABGR, 8 bits per channel. - * @CHAFA_PIXEL_RGBA8_UNASSOCIATED: Unassociated RGBA, 8 bits per channel. - * @CHAFA_PIXEL_BGRA8_UNASSOCIATED: Unassociated BGRA, 8 bits per channel. - * @CHAFA_PIXEL_ARGB8_UNASSOCIATED: Unassociated ARGB, 8 bits per channel. - * @CHAFA_PIXEL_ABGR8_UNASSOCIATED: Unassociated ABGR, 8 bits per channel. - * @CHAFA_PIXEL_RGB8: Packed RGB (no alpha), 8 bits per channel. - * @CHAFA_PIXEL_BGR8: Packed BGR (no alpha), 8 bits per channel. - * @CHAFA_PIXEL_MAX: Last supported pixel type, plus one. - * - * Pixel formats supported by #ChafaCanvas. - **/ - -/* Fixed point multiplier */ -#define FIXED_MULT 16384 +/* Used for cell initialization. May be added up over multiple cells, so a + * low multiple needs to fit in an integer. */ +#define SYMBOL_ERROR_MAX (G_MAXINT / 8) /* Max candidates to consider in pick_symbol_and_colors_fast(). This is also * limited by a similar constant in chafa-symbol-map.c */ #define N_CANDIDATES_MAX 8 -/* See rgb_to_intensity_fast () */ -#define INTENSITY_MAX (256 * 8) - -/* Normalization: Percentage of pixels to discard at extremes of histogram */ -#define INDEXED_16_CROP_PCT 5 -#define INDEXED_2_CROP_PCT 20 - /* Dithering */ -#define DITHER_GRAIN_WIDTH 4 -#define DITHER_GRAIN_HEIGHT 4 #define DITHER_BASE_INTENSITY_FGBG 1.0 +#define DITHER_BASE_INTENSITY_8C 0.5 #define DITHER_BASE_INTENSITY_16C 0.25 #define DITHER_BASE_INTENSITY_256C 0.1 -#define BAYER_MATRIX_DIM_SHIFT 4 -#define BAYER_MATRIX_DIM (1 << (BAYER_MATRIX_DIM_SHIFT)) -#define BAYER_MATRIX_SIZE ((BAYER_MATRIX_DIM) * (BAYER_MATRIX_DIM)) typedef struct { - gint32 c [INTENSITY_MAX]; - - /* Lower and upper bounds */ - gint32 min, max; + ChafaColorPair colors; + gint error; } -Histogram; - -struct ChafaCanvasCell -{ - gunichar c; - - /* Colors can be either packed RGBA or index */ - guint32 fg_color; - guint32 bg_color; -}; - -struct ChafaCanvas -{ - gint refs; - - gint width_pixels, height_pixels; - ChafaPixel *pixels; - ChafaCanvasCell *cells; - guint have_alpha : 1; - guint needs_clear : 1; - ChafaColor fg_color; - ChafaColor bg_color; - guint work_factor_int; - - ChafaCanvasConfig config; - - /* Used when setting pixel data */ - const guint8 *src_pixels; - Histogram *hist; - ChafaPixelType src_pixel_type; - gint src_width, src_height, src_rowstride; - gint have_alpha_int; - gint dither_grain_width_shift, dither_grain_height_shift; - - /* Set if we're doing bayer dithering */ - gint *bayer_matrix; - gint bayer_size_shift; -}; +SymbolEval; typedef struct { - ChafaPixel fg; - ChafaPixel bg; - gint error; + ChafaColorPair colors; + gint error [2]; } -SymbolEval; +SymbolEval2; -/* block_out must point to CHAFA_SYMBOL_N_PIXELS-element array */ -static void -fetch_canvas_pixel_block (ChafaCanvas *canvas, gint cx, gint cy, ChafaPixel *block_out) +static ChafaColor +threshold_alpha (ChafaColor col, gint alpha_threshold) { - ChafaPixel *row_p; - ChafaPixel *end_p; - gint i = 0; - - row_p = &canvas->pixels [cy * CHAFA_SYMBOL_HEIGHT_PIXELS * canvas->width_pixels + cx * CHAFA_SYMBOL_WIDTH_PIXELS]; - end_p = row_p + (canvas->width_pixels * CHAFA_SYMBOL_HEIGHT_PIXELS); + col.ch [3] = col.ch [3] < alpha_threshold ? 0 : 255; + return col; +} - for ( ; row_p < end_p; row_p += canvas->width_pixels) - { - ChafaPixel *p0 = row_p; - ChafaPixel *p1 = p0 + CHAFA_SYMBOL_WIDTH_PIXELS; +static gint +color_to_rgb (const ChafaCanvas *canvas, ChafaColor col) +{ + col = threshold_alpha (col, canvas->config.alpha_threshold); + if (col.ch [3] == 0) + return -1; - for ( ; p0 < p1; p0++) - block_out [i++] = *p0; - } + return ((gint) col.ch [0] << 16) | ((gint) col.ch [1] << 8) | (gint) col.ch [2]; } -static void -threshold_alpha (ChafaCanvas *canvas, ChafaColor *color) +static gint +packed_rgba_to_rgb (const ChafaCanvas *canvas, guint32 rgba) { - if (color->ch [3] < canvas->config.alpha_threshold) - color->ch [3] = 0x00; - else - color->ch [3] = 0xff; + ChafaColor col; + + chafa_unpack_color (rgba, &col); + return color_to_rgb (canvas, col); } -static void -calc_mean_color (const ChafaPixel *block, ChafaColor *color_out) +static ChafaColor +packed_rgb_to_color (gint rgb) { - ChafaColor accum = { 0 }; - gint i; + ChafaColor col; - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + if (rgb < 0) + { + col.ch [0] = 0x80; + col.ch [1] = 0x80; + col.ch [2] = 0x80; + col.ch [3] = 0x00; + } + else { - chafa_color_add (&accum, &block->col); - block++; + col.ch [0] = (rgb >> 16) & 0xff; + col.ch [1] = (rgb >> 8) & 0xff; + col.ch [2] = rgb & 0xff; + col.ch [3] = 0xff; } - chafa_color_div_scalar (&accum, CHAFA_SYMBOL_N_PIXELS); - *color_out = accum; + return col; } -static gint -find_dominant_channel (const ChafaPixel *block) +static guint32 +packed_rgb_to_rgba (gint rgb) { - gint16 min [4] = { G_MAXINT16, G_MAXINT16, G_MAXINT16, G_MAXINT16 }; - gint16 max [4] = { G_MININT16, G_MININT16, G_MININT16, G_MININT16 }; - gint16 range [4]; - gint ch, best_ch; - gint i; + ChafaColor col; - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - for (ch = 0; ch < 4; ch++) - { - if (block [i].col.ch [ch] < min [ch]) - min [ch] = block [i].col.ch [ch]; - if (block [i].col.ch [ch] > max [ch]) - max [ch] = block [i].col.ch [ch]; - } - } + col = packed_rgb_to_color (rgb); + return chafa_pack_color (&col); +} - for (ch = 0; ch < 4; ch++) - range [ch] = max [ch] - min [ch]; +static gint16 +packed_rgb_to_index (const ChafaPalette *palette, ChafaColorSpace cs, gint rgb) +{ + ChafaColorCandidates ccand; + ChafaColor col; - best_ch = 0; - for (ch = 1; ch < 4; ch++) - if (range [ch] > range [best_ch]) - best_ch = ch; + if (rgb < 0) + return CHAFA_PALETTE_INDEX_TRANSPARENT; - return best_ch; + col = packed_rgb_to_color (rgb); + chafa_palette_lookup_nearest (palette, cs, &col, &ccand); + return ccand.index [0]; } -static void -sort_block_by_channel (ChafaPixel *block, gint ch) +static guint32 +transparent_cell_color (ChafaCanvasMode canvas_mode) { - const gint gaps [] = { 57, 23, 10, 4, 1 }; - gint g, i, j; - - /* Since we don't care about stability and the number of elements - * is small and known in advance, use a simple in-place shellsort. - * - * Due to locality and callback overhead this is probably faster - * than qsort(), although admittedly I haven't benchmarked it. - * - * Another option is to use radix, but since we support multiple - * color spaces with fixed-point reals, we could get more buckets - * than is practical. */ - - for (g = 0; ; g++) + if (canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) { - gint gap = gaps [g]; - - for (i = gap; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - ChafaPixel ptemp = block [i]; - - for (j = i; j >= gap && block [j - gap].col.ch [ch] > ptemp.col.ch [ch]; j -= gap) - { - block [j] = block [j - gap]; - } - - block [j] = ptemp; - } - - /* After gap == 1 the array is always left sorted */ - if (gap == 1) - break; + const ChafaColor col = { { 0x80, 0x80, 0x80, 0x00 } }; + return chafa_pack_color (&col); + } + else + { + return CHAFA_PALETTE_INDEX_TRANSPARENT; } } -/* colors_out must point to an array of two elements */ static void -pick_two_colors (const ChafaPixel *block, ChafaColor *colors_out) +eval_symbol_colors (ChafaCanvas *canvas, ChafaWorkCell *wcell, const ChafaSymbol *sym, SymbolEval *eval) { - ChafaPixel sorted_colors [CHAFA_SYMBOL_N_PIXELS]; - gint best_ch; - - best_ch = find_dominant_channel (block); - - /* FIXME: An array of index bytes might be faster due to smaller - * elements, but the indirection adds overhead so it'd have to be - * benchmarked. */ + if (canvas->config.color_extractor == CHAFA_COLOR_EXTRACTOR_AVERAGE) + chafa_work_cell_get_mean_colors_for_symbol (wcell, sym, &eval->colors); + else + chafa_work_cell_get_median_colors_for_symbol (wcell, sym, &eval->colors); +} - memcpy (sorted_colors, block, sizeof (sorted_colors)); - sort_block_by_channel (sorted_colors, best_ch); +static void +eval_symbol_colors_wide (ChafaCanvas *canvas, ChafaWorkCell *wcell_a, ChafaWorkCell *wcell_b, + const ChafaSymbol *sym_a, const ChafaSymbol *sym_b, + SymbolEval2 *eval) +{ + SymbolEval part_eval [2]; - /* Choose two colors by median cut */ + eval_symbol_colors (canvas, wcell_a, sym_a, &part_eval [0]); + eval_symbol_colors (canvas, wcell_b, sym_b, &part_eval [1]); - colors_out [0] = sorted_colors [CHAFA_SYMBOL_N_PIXELS / 4].col; - colors_out [1] = sorted_colors [(CHAFA_SYMBOL_N_PIXELS * 3) / 4].col; + eval->colors.colors [CHAFA_COLOR_PAIR_FG] + = chafa_color_average_2 (part_eval [0].colors.colors [CHAFA_COLOR_PAIR_FG], + part_eval [1].colors.colors [CHAFA_COLOR_PAIR_FG]); + eval->colors.colors [CHAFA_COLOR_PAIR_BG] + = chafa_color_average_2 (part_eval [0].colors.colors [CHAFA_COLOR_PAIR_BG], + part_eval [1].colors.colors [CHAFA_COLOR_PAIR_BG]); } -/* colors must point to an array of two elements */ -static guint64 -block_to_bitmap (const ChafaPixel *block, ChafaColor *colors) +static gint +calc_error_plain (const ChafaPixel *block, const ChafaColorPair *color_pair, const guint8 *cov) { - guint64 bitmap = 0; + gint error = 0; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { - gint error [2]; - - bitmap <<= 1; - - /* FIXME: What to do about alpha? */ - error [0] = chafa_color_diff_fast (&block [i].col, &colors [0]); - error [1] = chafa_color_diff_fast (&block [i].col, &colors [1]); + guint8 p = *cov++; + const ChafaPixel *p0 = block++; - if (error [0] < error [1]) - bitmap |= 1; + error += chafa_color_diff_fast (&color_pair->colors [p], &p0->col); } - return bitmap; + return error; } static void -calc_colors_plain (const ChafaPixel *block, ChafaColor *cols, const guint8 *cov) +eval_symbol_error (const ChafaWorkCell *wcell, + const ChafaSymbol *sym, SymbolEval *eval, + const ChafaPalette *fg_palette, + const ChafaPalette *bg_palette, + ChafaColorSpace color_space) { - const gint16 *in_s16 = (const gint16 *) block; - gint i; + const guint8 *covp = (guint8 *) &sym->coverage [0]; + ChafaColorPair pair; + gint error; - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + if (!bg_palette) + bg_palette = fg_palette; + if (!fg_palette) + fg_palette = bg_palette; + + if (fg_palette) + { + pair.colors [CHAFA_COLOR_PAIR_FG] = + *chafa_palette_get_color ( + fg_palette, + color_space, + chafa_palette_lookup_nearest (fg_palette, color_space, + &eval->colors.colors [CHAFA_COLOR_PAIR_FG], NULL)); + pair.colors [CHAFA_COLOR_PAIR_BG] = + *chafa_palette_get_color ( + bg_palette, + color_space, + chafa_palette_lookup_nearest (bg_palette, color_space, + &eval->colors.colors [CHAFA_COLOR_PAIR_BG], NULL)); + } + else { - gint16 *out_s16 = (gint16 *) (cols + *cov++); - - *out_s16++ += *in_s16++; - *out_s16++ += *in_s16++; - *out_s16++ += *in_s16++; - *out_s16++ += *in_s16++; + pair = eval->colors; } -} - -static void -eval_symbol_colors (const ChafaPixel *block, const ChafaSymbol *sym, SymbolEval *eval) -{ - const guint8 *covp = (guint8 *) &sym->coverage [0]; - ChafaColor cols [2] = { 0 }; -#ifdef HAVE_MMX_INTRINSICS - if (chafa_have_mmx ()) - calc_colors_mmx (block, cols, covp); +#ifdef HAVE_SSE41_INTRINSICS + if (chafa_have_sse41 ()) + error = calc_error_sse41 (wcell->pixels, &pair, covp); else #endif - calc_colors_plain (block, cols, covp); - - eval->fg.col = cols [1]; - eval->bg.col = cols [0]; - - if (sym->fg_weight > 1) - chafa_color_div_scalar (&eval->fg.col, sym->fg_weight); + error = calc_error_plain (wcell->pixels, &pair, covp); - if (sym->bg_weight > 1) - chafa_color_div_scalar (&eval->bg.col, sym->bg_weight); + eval->error = error; } -static gint -calc_error_plain (const ChafaPixel *block, const ChafaColor *cols, const guint8 *cov) +static void +eval_symbol_error_wide (const ChafaWorkCell *wcell_a, const ChafaWorkCell *wcell_b, + const ChafaSymbol2 *sym, SymbolEval2 *wide_eval, + const ChafaPalette *fg_palette, + const ChafaPalette *bg_palette, + ChafaColorSpace color_space) { - gint error = 0; - gint i; + SymbolEval eval [2]; - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - guint8 p = *cov++; - const ChafaPixel *p0 = block++; + eval [0].colors = wide_eval->colors; + eval [1].colors = wide_eval->colors; - error += chafa_color_diff_fast (&cols [p], &p0->col); - } + eval_symbol_error (wcell_a, &sym->sym [0], &eval [0], + fg_palette, bg_palette, color_space); + eval_symbol_error (wcell_b, &sym->sym [1], &eval [1], + fg_palette, bg_palette, color_space); - return error; + wide_eval->error [0] = eval [0].error; + wide_eval->error [1] = eval [1].error; } -static gint -calc_error_with_alpha (const ChafaPixel *block, const ChafaColor *cols, const guint8 *cov, ChafaColorSpace cs) +static void +eval_symbol (ChafaCanvas *canvas, ChafaWorkCell *wcell, gint sym_index, + gint *best_sym_index_out, SymbolEval *best_eval_inout) { - gint error = 0; - gint i; + const ChafaSymbol *sym; + SymbolEval eval; - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + sym = &canvas->config.symbol_map.symbols [sym_index]; + + if (canvas->config.fg_only_enabled) { - guint8 p = *cov++; - const ChafaPixel *p0 = block++; + eval.colors = canvas->default_colors; + } + else + { + eval_symbol_colors (canvas, wcell, sym, &eval); + } - error += chafa_color_diff_slow (&cols [p], &p0->col, cs); + if (canvas->use_quantized_error) + { + eval_symbol_error (wcell, sym, &eval, &canvas->fg_palette, + &canvas->bg_palette, canvas->config.color_space); + } + else + { + eval_symbol_error (wcell, sym, &eval, NULL, NULL, + canvas->config.color_space); } - return error; + if (eval.error < best_eval_inout->error) + { + *best_sym_index_out = sym_index; + *best_eval_inout = eval; + } } static void -eval_symbol_error (ChafaCanvas *canvas, const ChafaPixel *block, - const ChafaSymbol *sym, SymbolEval *eval) +eval_symbol_wide (ChafaCanvas *canvas, ChafaWorkCell *wcell_a, ChafaWorkCell *wcell_b, + gint sym_index, gint *best_sym_index_out, SymbolEval2 *best_eval_inout) { - const guint8 *covp = (guint8 *) &sym->coverage [0]; - ChafaColor cols [2] = { 0 }; - gint error; + const ChafaSymbol2 *sym2; + SymbolEval2 eval; - cols [0] = eval->bg.col; - cols [1] = eval->fg.col; + sym2 = &canvas->config.symbol_map.symbols2 [sym_index]; - if (canvas->have_alpha) + if (canvas->config.fg_only_enabled) { - error = calc_error_with_alpha (block, cols, covp, canvas->config.color_space); + eval.colors = canvas->default_colors; } else { -#ifdef HAVE_SSE41_INTRINSICS - if (chafa_have_sse41 ()) - error = calc_error_sse41 (block, cols, covp); - else -#endif - error = calc_error_plain (block, cols, covp); + eval_symbol_colors_wide (canvas, wcell_a, wcell_b, + &sym2->sym [0], + &sym2->sym [1], + &eval); } - eval->error = error; + if (canvas->use_quantized_error) + { + eval_symbol_error_wide (wcell_a, wcell_b, + sym2, + &eval, + &canvas->fg_palette, + &canvas->bg_palette, + canvas->config.color_space); + } + else + { + eval_symbol_error_wide (wcell_a, wcell_b, + sym2, + &eval, + NULL, + NULL, + canvas->config.color_space); + } + + if (eval.error [0] + eval.error [1] < best_eval_inout->error [0] + best_eval_inout->error [1]) + { + *best_sym_index_out = sym_index; + *best_eval_inout = eval; + } } static void pick_symbol_and_colors_slow (ChafaCanvas *canvas, - const ChafaPixel *block, + ChafaWorkCell *wcell, gunichar *sym_out, - ChafaColor *fg_col_out, - ChafaColor *bg_col_out, + ChafaColorPair *color_pair_out, gint *error_out) { - SymbolEval eval [CHAFA_N_SYMBOLS_MAX] = { 0 }; - gint n; + SymbolEval best_eval; + gint best_symbol = -1; gint i; + /* Find best symbol. All symbols are candidates. */ + + best_eval.error = SYMBOL_ERROR_MAX; + for (i = 0; canvas->config.symbol_map.symbols [i].c != 0; i++) - { - eval [i].error = G_MAXINT; + eval_symbol (canvas, wcell, i, &best_symbol, &best_eval); - /* FIXME: Always evaluate space so we get fallback colors */ + chafa_leave_mmx (); /* Make FPU happy again */ - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) - { - eval [i].fg.col = canvas->fg_color; - eval [i].bg.col = canvas->bg_color; - } - else - { - ChafaColorCandidates ccand; - ChafaColor fg_col, bg_col; + /* Output */ - eval_symbol_colors (block, &canvas->config.symbol_map.symbols [i], &eval [i]); + g_assert (best_symbol >= 0); - /* Threshold alpha */ + if (canvas->extract_colors && canvas->config.fg_only_enabled) + eval_symbol_colors (canvas, wcell, &canvas->config.symbol_map.symbols [best_symbol], &best_eval); - threshold_alpha (canvas, &eval [i].fg.col); - threshold_alpha (canvas, &eval [i].bg.col); + *sym_out = canvas->config.symbol_map.symbols [best_symbol].c; + *color_pair_out = best_eval.colors; - fg_col = eval [i].fg.col; - bg_col = eval [i].bg.col; + if (error_out) + *error_out = best_eval.error; +} - /* Pick palette colors before error evaluation; this improves - * fine detail fidelity slightly. */ +static void +pick_symbol_and_colors_wide_slow (ChafaCanvas *canvas, + ChafaWorkCell *wcell_a, + ChafaWorkCell *wcell_b, + gunichar *sym_out, + ChafaColorPair *color_pair_out, + gint *error_a_out, + gint *error_b_out) +{ + SymbolEval2 best_eval; + gint best_symbol = -1; + gint i; - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16) - { - chafa_pick_color_16 (&eval [i].fg.col, canvas->config.color_space, &ccand); - fg_col = *chafa_get_palette_color_256 (ccand.index [0], canvas->config.color_space); - chafa_pick_color_16 (&eval [i].bg.col, canvas->config.color_space, &ccand); - bg_col = *chafa_get_palette_color_256 (ccand.index [0], canvas->config.color_space); - } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240) - { - chafa_pick_color_240 (&eval [i].fg.col, canvas->config.color_space, &ccand); - fg_col = *chafa_get_palette_color_256 (ccand.index [0], canvas->config.color_space); - chafa_pick_color_240 (&eval [i].bg.col, canvas->config.color_space, &ccand); - bg_col = *chafa_get_palette_color_256 (ccand.index [0], canvas->config.color_space); - } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256) - { - chafa_pick_color_256 (&eval [i].fg.col, canvas->config.color_space, &ccand); - fg_col = *chafa_get_palette_color_256 (ccand.index [0], canvas->config.color_space); - chafa_pick_color_256 (&eval [i].bg.col, canvas->config.color_space, &ccand); - bg_col = *chafa_get_palette_color_256 (ccand.index [0], canvas->config.color_space); - } + /* Find best symbol. All symbols are candidates. */ - /* FIXME: The logic here seems overly complicated */ - if (canvas->config.canvas_mode != CHAFA_CANVAS_MODE_TRUECOLOR) - { - /* Transfer mean alpha over so we can use it later */ + best_eval.error [0] = best_eval.error [1] = SYMBOL_ERROR_MAX; - fg_col.ch [3] = eval [i].fg.col.ch [3]; - bg_col.ch [3] = eval [i].bg.col.ch [3]; + for (i = 0; canvas->config.symbol_map.symbols2 [i].sym [0].c != 0; i++) + eval_symbol_wide (canvas, wcell_a, wcell_b, i, &best_symbol, &best_eval); - eval [i].fg.col = fg_col; - eval [i].bg.col = bg_col; - } - } + chafa_leave_mmx (); /* Make FPU happy again */ - eval_symbol_error (canvas, block, &canvas->config.symbol_map.symbols [i], &eval [i]); - } + /* Output */ -#ifdef HAVE_MMX_INTRINSICS - /* Make FPU happy again */ - if (chafa_have_mmx ()) - leave_mmx (); -#endif + g_assert (best_symbol >= 0); - if (error_out) - *error_out = eval [0].error; + if (canvas->extract_colors && canvas->config.fg_only_enabled) + eval_symbol_colors_wide (canvas, wcell_a, wcell_b, + &canvas->config.symbol_map.symbols2 [best_symbol].sym [0], + &canvas->config.symbol_map.symbols2 [best_symbol].sym [1], + &best_eval); - for (i = 0, n = 0; canvas->config.symbol_map.symbols [i].c != 0; i++) - { - if ((eval [i].fg.col.ch [0] != eval [i].bg.col.ch [0] - || eval [i].fg.col.ch [1] != eval [i].bg.col.ch [1] - || eval [i].fg.col.ch [2] != eval [i].bg.col.ch [2]) - && eval [i].error < eval [n].error) - { - n = i; - if (error_out) - *error_out = eval [i].error; - } - } + *sym_out = canvas->config.symbol_map.symbols2 [best_symbol].sym [0].c; + *color_pair_out = best_eval.colors; - *sym_out = canvas->config.symbol_map.symbols [n].c; - *fg_col_out = eval [n].fg.col; - *bg_col_out = eval [n].bg.col; + if (error_a_out) + *error_a_out = best_eval.error [0]; + if (error_b_out) + *error_b_out = best_eval.error [1]; } static void pick_symbol_and_colors_fast (ChafaCanvas *canvas, - const ChafaPixel *block, + ChafaWorkCell *wcell, gunichar *sym_out, - ChafaColor *fg_col_out, - ChafaColor *bg_col_out, + ChafaColorPair *color_pair_out, gint *error_out) { - ChafaColor color_pair [2]; + ChafaColorPair color_pair; guint64 bitmap; ChafaCandidate candidates [N_CANDIDATES_MAX]; - SymbolEval eval [N_CANDIDATES_MAX]; gint n_candidates = 0; - gint best_candidate = 0; - gint best_error = G_MAXINT; + SymbolEval best_eval; + gint best_symbol; gint i; - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG - || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) + /* Generate short list of candidates */ + + if (canvas->extract_colors && !canvas->config.fg_only_enabled) { - color_pair [0] = canvas->fg_color; - color_pair [1] = canvas->bg_color; + chafa_work_cell_get_contrasting_color_pair (wcell, &color_pair); } else { - pick_two_colors (block, color_pair); + color_pair = canvas->default_colors; } - bitmap = block_to_bitmap (block, color_pair); + bitmap = chafa_work_cell_to_bitmap (wcell, &color_pair); n_candidates = CLAMP (canvas->work_factor_int, 1, N_CANDIDATES_MAX); chafa_symbol_map_find_candidates (&canvas->config.symbol_map, bitmap, - canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG - ? FALSE : TRUE, /* Consider inverted? */ + canvas->consider_inverted, candidates, &n_candidates); g_assert (n_candidates > 0); - if (n_candidates == 1) + /* Find best candidate */ + + best_symbol = -1; + best_eval.error = SYMBOL_ERROR_MAX; + + for (i = 0; i < n_candidates; i++) + eval_symbol (canvas, wcell, candidates [i].symbol_index, &best_symbol, &best_eval); + + chafa_leave_mmx (); /* Make FPU happy again */ + + /* Output */ + + g_assert (best_symbol >= 0); + + if (canvas->extract_colors && canvas->config.fg_only_enabled) + eval_symbol_colors (canvas, wcell, &canvas->config.symbol_map.symbols [best_symbol], &best_eval); + + *sym_out = canvas->config.symbol_map.symbols [best_symbol].c; + *color_pair_out = best_eval.colors; + + if (error_out) + *error_out = best_eval.error; +} + +static void +pick_symbol_and_colors_wide_fast (ChafaCanvas *canvas, + ChafaWorkCell *wcell_a, + ChafaWorkCell *wcell_b, + gunichar *sym_out, + ChafaColorPair *color_pair_out, + gint *error_a_out, + gint *error_b_out) +{ + ChafaColorPair color_pair; + guint64 bitmaps [2]; + ChafaCandidate candidates [N_CANDIDATES_MAX]; + gint n_candidates = 0; + SymbolEval2 best_eval; + gint best_symbol; + gint i; + + /* Generate short list of candidates */ + + if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) { - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) - { - eval [0].fg.col = canvas->fg_color; - eval [0].bg.col = canvas->bg_color; - } - else - { - eval_symbol_colors (block, - &canvas->config.symbol_map.symbols [candidates [0].symbol_index], - &eval [0]); - } + color_pair = canvas->default_colors; } else { - for (i = 0; i < n_candidates; i++) - { - const ChafaSymbol *sym = &canvas->config.symbol_map.symbols [candidates [i].symbol_index]; + ChafaColorPair color_pair_part [2]; - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) - { - eval [i].fg.col = canvas->fg_color; - eval [i].bg.col = canvas->bg_color; - } - else - { - eval_symbol_colors (block, sym, &eval [i]); - } - - eval_symbol_error (canvas, block, sym, &eval [i]); + chafa_work_cell_get_contrasting_color_pair (wcell_a, &color_pair_part [0]); + chafa_work_cell_get_contrasting_color_pair (wcell_b, &color_pair_part [1]); - if (eval [i].error < best_error) - { - best_candidate = i; - best_error = eval [i].error; - } - } + color_pair.colors [0] = chafa_color_average_2 (color_pair_part [0].colors [0], color_pair_part [1].colors [0]); + color_pair.colors [1] = chafa_color_average_2 (color_pair_part [0].colors [1], color_pair_part [1].colors [1]); } - *sym_out = canvas->config.symbol_map.symbols [candidates [best_candidate].symbol_index].c; - *fg_col_out = eval [best_candidate].fg.col; - *bg_col_out = eval [best_candidate].bg.col; - -#ifdef HAVE_MMX_INTRINSICS - /* Make FPU happy again */ - if (chafa_have_mmx ()) - leave_mmx (); -#endif + bitmaps [0] = chafa_work_cell_to_bitmap (wcell_a, &color_pair); + bitmaps [1] = chafa_work_cell_to_bitmap (wcell_b, &color_pair); + n_candidates = CLAMP (canvas->work_factor_int, 1, N_CANDIDATES_MAX); - if (error_out) - *error_out = best_error; + chafa_symbol_map_find_wide_candidates (&canvas->config.symbol_map, + bitmaps, + canvas->consider_inverted, + candidates, &n_candidates); + + g_assert (n_candidates > 0); + + /* Find best candidate */ + + best_symbol = -1; + best_eval.error [0] = best_eval.error [1] = SYMBOL_ERROR_MAX; + + for (i = 0; i < n_candidates; i++) + eval_symbol_wide (canvas, wcell_a, wcell_b, candidates [i].symbol_index, + &best_symbol, &best_eval); + + chafa_leave_mmx (); /* Make FPU happy again */ + + /* Output */ + + g_assert (best_symbol >= 0); + + if (canvas->extract_colors && canvas->config.fg_only_enabled) + eval_symbol_colors_wide (canvas, wcell_a, wcell_b, + &canvas->config.symbol_map.symbols2 [best_symbol].sym [0], + &canvas->config.symbol_map.symbols2 [best_symbol].sym [1], + &best_eval); + + *sym_out = canvas->config.symbol_map.symbols2 [best_symbol].sym [0].c; + *color_pair_out = best_eval.colors; + + if (error_a_out) + *error_a_out = best_eval.error [0]; + if (error_b_out) + *error_b_out = best_eval.error [1]; +} + +static const ChafaColor * +get_palette_color_with_color_space (ChafaPalette *palette, gint index, ChafaColorSpace cs) +{ + return chafa_palette_get_color (palette, cs, index); } static const ChafaColor * -get_palette_color (ChafaCanvas *canvas, gint index) +get_palette_color (ChafaCanvas *canvas, ChafaPalette *palette, gint index) +{ + return get_palette_color_with_color_space (palette, index, canvas->config.color_space); +} + +static void +apply_fill_fg_only (ChafaCanvas *canvas, const ChafaWorkCell *wcell, ChafaCanvasCell *cell) { - if (index == CHAFA_PALETTE_INDEX_FG) - return &canvas->fg_color; - if (index == CHAFA_PALETTE_INDEX_BG) - return &canvas->bg_color; - if (index == CHAFA_PALETTE_INDEX_TRANSPARENT) - return &canvas->bg_color; + ChafaColor mean; + ChafaColorCandidates ccand; + gint fg_value, bg_value, mean_value; + gint n_bits; + ChafaCandidate sym_cand; + gint n_sym_cands = 1; + + if (canvas->config.fill_symbol_map.n_symbols == 0) + return; + + chafa_work_cell_calc_mean_color (wcell, &mean); + + if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) + { + cell->fg_color = chafa_pack_color (&mean); + } + else + { + chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, &mean, &ccand); + cell->fg_color = ccand.index [0]; + } + + cell->bg_color = transparent_cell_color (canvas->config.canvas_mode); - return chafa_get_palette_color_256 (index, canvas->config.color_space); + /* FIXME: Do we care enough to weight channels properly here, or convert from DIN99d? + * Output looks acceptable without. Would have to check if it makes a noticeable + * difference. */ + fg_value = (canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [0] + + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [1] + + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [2]) + / 3; + bg_value = (canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [0] + + canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [1] + + canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [2]) + / 3; + mean_value = (mean.ch [0] + mean.ch [1] + mean.ch [2]) / 3; + + n_bits = ((mean_value * 64) + 128) / 255; + if (fg_value < bg_value) + n_bits = 64 - n_bits; + + chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, + n_bits, + FALSE, + &sym_cand, &n_sym_cands); + + cell->c = canvas->config.fill_symbol_map.symbols [sym_cand.symbol_index].c; } static void -apply_fill (ChafaCanvas *canvas, const ChafaPixel *block, ChafaCanvasCell *cell) +apply_fill (ChafaCanvas *canvas, const ChafaWorkCell *wcell, ChafaCanvasCell *cell) { ChafaColor mean; ChafaColor col [3]; @@ -626,7 +644,7 @@ if (canvas->config.fill_symbol_map.n_symbols == 0) return; - calc_mean_color (block, &mean); + chafa_work_cell_calc_mean_color (wcell, &mean); if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) { @@ -636,17 +654,29 @@ &sym_cand, &n_sym_cands); goto done; } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256) - { - chafa_pick_color_256 (&mean, canvas->config.color_space, &ccand); - } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240) + else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_8) { - chafa_pick_color_240 (&mean, canvas->config.color_space, &ccand); + chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, + &mean, &ccand); } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16) + else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16_8) { - chafa_pick_color_16 (&mean, canvas->config.color_space, &ccand); + ChafaColorCandidates ccand_bg; + + chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, + &mean, &ccand); + chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, + &mean, &ccand_bg); + + if (ccand.index [0] != ccand_bg.index [0]) + { + if (ccand.index [1] == ccand_bg.index [0]) + ccand.index [1] = ccand_bg.index [1]; + ccand.index [0] = ccand_bg.index [0]; + } } else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) @@ -659,8 +689,8 @@ g_assert_not_reached (); } - col [0] = *get_palette_color (canvas, ccand.index [0]); - col [1] = *get_palette_color (canvas, ccand.index [1]); + col [0] = *get_palette_color (canvas, &canvas->fg_palette, ccand.index [0]); + col [1] = *get_palette_color (canvas, &canvas->fg_palette, ccand.index [1]); /* In FGBG modes, background and transparency is the same thing. Make * sure we have two opaque colors for correct interpolation. */ @@ -678,7 +708,7 @@ col [2].ch [2] = (col [0].ch [2] * (64 - i) + col [1].ch [2] * i) / 64; col [2].ch [3] = (col [0].ch [3] * (64 - i) + col [1].ch [3] * i) / 64; - error = chafa_color_diff_slow (&mean, &col [2], canvas->config.color_space); + error = chafa_color_diff_fast (&mean, &col [2]); if (error < best_error) { /* In FGBG mode there's no way to invert or set the BG color, so @@ -689,16 +719,20 @@ } chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, best_i, - canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG - ? FALSE : TRUE, /* Consider inverted? */ + canvas->consider_inverted && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_INDEXED_16_8, &sym_cand, &n_sym_cands); /* If we end up with a featureless symbol (space or fill), make - * FG color equal to BG. */ - if (best_i == 0) - ccand.index [1] = ccand.index [0]; - else if (best_i == 64) - ccand.index [0] = ccand.index [1]; + * FG color equal to BG. Don't do this in FGBG mode, as it does not permit + * color manipulation. */ + if (canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG + && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_INDEXED_16_8) + { + if (best_i == 0) + ccand.index [1] = ccand.index [0]; + else if (best_i == 64) + ccand.index [0] = ccand.index [1]; + } if (sym_cand.is_inverted) { @@ -716,1108 +750,499 @@ } static void -update_cells_row (ChafaCanvas *canvas, gint row) +quantize_colors_for_cell_16_8 (ChafaCanvas *canvas, ChafaCanvasCell *cell, + const ChafaColorPair *color_pair) { - gint cx, cy; - gint i = 0; + /* First pick both colors from FG palette to see if we should eliminate the FG/BG + * distinction. This is necessary to prevent artifacts in solid color (fg-bg-fg-bg etc). */ - cy = row; - i = row * canvas->config.width; + /* TODO: Investigate if we could just force evaluation of the solid symbol instead. */ + + cell->fg_color = + chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); + cell->bg_color = + chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_BG], NULL); - for (cx = 0; cx < canvas->config.width; cx++, i++) + if (cell->fg_color == cell->bg_color && cell->fg_color >= 8 && cell->fg_color <= 15) { - ChafaPixel block [CHAFA_SYMBOL_N_PIXELS]; - ChafaCanvasCell *cell = &canvas->cells [i]; - gunichar sym = 0; - ChafaColorCandidates ccand; - ChafaColor fg_col, bg_col; + /* Chosen FG and BG colors should ideally be the same, but BG palette does not allow it. + * Use the solid char with FG color if we have one, else fall back to using the closest + * match from the BG palette for both FG and BG. */ - memset (cell, 0, sizeof (*cell)); - cell->c = ' '; - - fetch_canvas_pixel_block (canvas, cx, cy, block); - - if (canvas->config.symbol_map.n_symbols > 0) + if (canvas->solid_char) { - if (canvas->work_factor_int >= 8) - pick_symbol_and_colors_slow (canvas, block, &sym, &fg_col, &bg_col, NULL); - else - pick_symbol_and_colors_fast (canvas, block, &sym, &fg_col, &bg_col, NULL); - - cell->c = sym; - - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256) - { - chafa_pick_color_256 (&fg_col, canvas->config.color_space, &ccand); - cell->fg_color = ccand.index [0]; - chafa_pick_color_256 (&bg_col, canvas->config.color_space, &ccand); - cell->bg_color = ccand.index [0]; - } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240) - { - chafa_pick_color_240 (&fg_col, canvas->config.color_space, &ccand); - cell->fg_color = ccand.index [0]; - chafa_pick_color_240 (&bg_col, canvas->config.color_space, &ccand); - cell->bg_color = ccand.index [0]; - } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16) - { - chafa_pick_color_16 (&fg_col, canvas->config.color_space, &ccand); - cell->fg_color = ccand.index [0]; - chafa_pick_color_16 (&bg_col, canvas->config.color_space, &ccand); - cell->bg_color = ccand.index [0]; - } - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) - { - chafa_pick_color_fgbg (&fg_col, canvas->config.color_space, - &canvas->fg_color, &canvas->bg_color, &ccand); - cell->fg_color = ccand.index [0]; - chafa_pick_color_fgbg (&bg_col, canvas->config.color_space, - &canvas->fg_color, &canvas->bg_color, &ccand); - cell->bg_color = ccand.index [0]; - } - else - { - cell->fg_color = chafa_pack_color (&fg_col); - cell->bg_color = chafa_pack_color (&bg_col); - } + cell->c = canvas->solid_char; + cell->bg_color = + chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); } -#if 1 - /* If we produced a featureless cell, try fill */ - - /* FIXME: Check popcount == 0 or == 64 instead of symbol char */ - if (sym == 0 || sym == ' ' || sym == 0x2588 || cell->fg_color == cell->bg_color) + else { - apply_fill (canvas, block, cell); + cell->fg_color = cell->bg_color = + chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); } -#endif } -} - -typedef struct -{ - gint row; -} -CellBuildWork; - -static void -cell_build_worker (CellBuildWork *work, ChafaCanvas *canvas) -{ - update_cells_row (canvas, work->row); - g_slice_free (CellBuildWork, work); -} - -static void -update_cells (ChafaCanvas *canvas) -{ - GThreadPool *thread_pool = g_thread_pool_new ((GFunc) cell_build_worker, - canvas, - g_get_num_processors (), - FALSE, - NULL); - gint cy; - - for (cy = 0; cy < canvas->config.height; cy++) - { - CellBuildWork *work = g_slice_new (CellBuildWork); - work->row = cy; - g_thread_pool_push (thread_pool, work, NULL); - } - - g_thread_pool_free (thread_pool, FALSE, TRUE); -} - -static gint -rgb_to_intensity_fast (const ChafaColor *color) -{ - /* Sum to 8x so we can divide by shifting later */ - return color->ch [0] * 3 + color->ch [1] * 4 + color->ch [2]; -} - -static void -sum_histograms (const Histogram *hist_in, Histogram *hist_accum) -{ - gint i; - - for (i = 0; i < INTENSITY_MAX; i++) + else { - hist_accum->c [i] += hist_in->c [i]; + cell->bg_color = chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_BG], NULL); } } static void -histogram_calc_bounds (ChafaCanvas *canvas, Histogram *hist, gint crop_pct) +update_cell_colors (ChafaCanvas *canvas, ChafaCanvasCell *cell_out, + const ChafaColorPair *color_pair) { - gint64 pixels_crop; - gint i; - gint t; - - pixels_crop = (canvas->width_pixels * canvas->height_pixels * (((gint64) crop_pct * 1024) / 100)) / 1024; - - /* Find lower bound */ - - for (i = 0, t = pixels_crop; i < INTENSITY_MAX; i++) + if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_8 + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) { - t -= hist->c [i]; - if (t <= 0) - break; + cell_out->fg_color = + chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); + cell_out->bg_color = + chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, + &color_pair->colors [CHAFA_COLOR_PAIR_BG], NULL); } - - hist->min = i; - - /* Find upper bound */ - - for (i = INTENSITY_MAX - 1, t = pixels_crop; i >= 0; i--) + else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16_8) { - t -= hist->c [i]; - if (t <= 0) - break; + quantize_colors_for_cell_16_8 (canvas, cell_out, color_pair); } - - hist->max = i; -} - -static gint16 -normalize_ch (gint16 v, gint min, gint factor) -{ - gint vt = v; - - vt -= min; - if (vt < 0) - vt = 0; - vt *= factor; - vt /= FIXED_MULT; - if (vt > 255) - vt = 255; - return vt; -} - -static void -normalize_rgb (ChafaCanvas *canvas, const Histogram *hist, gint dest_y, gint n_rows) -{ - ChafaPixel *p0, *p1; - gint factor; - - /* Make sure range is more or less sane */ - - if (hist->min == hist->max) - return; - -#if 0 - if (min > 512) - min = 512; - if (max < 1536) - max = 1536; -#endif - - /* Adjust intensities */ - - factor = ((INTENSITY_MAX - 1) * FIXED_MULT) / (hist->max - hist->min); - -#if 0 - g_printerr ("[%d-%d] * %d, crop=%d \n", min, max, factor, pixels_crop); -#endif - - p0 = canvas->pixels + dest_y * canvas->width_pixels; - p1 = p0 + n_rows * canvas->width_pixels; - - for ( ; p0 < p1; p0++) + else { - p0->col.ch [0] = normalize_ch (p0->col.ch [0], hist->min / 8, factor); - p0->col.ch [1] = normalize_ch (p0->col.ch [1], hist->min / 8, factor); - p0->col.ch [2] = normalize_ch (p0->col.ch [2], hist->min / 8, factor); + cell_out->fg_color = chafa_pack_color (&color_pair->colors [CHAFA_COLOR_PAIR_FG]); + cell_out->bg_color = chafa_pack_color (&color_pair->colors [CHAFA_COLOR_PAIR_BG]); } -} - -static void -boost_saturation_rgb (ChafaColor *col) -{ -#define Pr .299 -#define Pg .587 -#define Pb .144 - gdouble P = sqrt ((col->ch [0]) * (gdouble) (col->ch [0]) * Pr - + (col->ch [1]) * (gdouble) (col->ch [1]) * Pg - + (col->ch [2]) * (gdouble) (col->ch [2]) * Pb); - - col->ch [0] = (P + ((gdouble) col->ch [0] - P) * 2); - col->ch [1] = (P + ((gdouble) col->ch [1] - P) * 2); - col->ch [2] = (P + ((gdouble) col->ch [2] - P) * 2); -} -static void -clamp_color_rgb (ChafaColor *col) -{ - col->ch [0] = CLAMP (col->ch [0], 0, 255); - col->ch [1] = CLAMP (col->ch [1], 0, 255); - col->ch [2] = CLAMP (col->ch [2], 0, 255); + if (canvas->config.fg_only_enabled) + cell_out->bg_color = transparent_cell_color (canvas->config.canvas_mode); } -static void -update_display_colors (ChafaCanvas *canvas) +static gint +update_cell (ChafaCanvas *canvas, ChafaWorkCell *work_cell, ChafaCanvasCell *cell_out) { - ChafaColor fg_col; - ChafaColor bg_col; + gunichar sym = 0; + ChafaColorPair color_pair; + gint sym_error; - chafa_unpack_color (canvas->config.fg_color_packed_rgb, &fg_col); - chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_col); + if (canvas->config.symbol_map.n_symbols == 0) + return SYMBOL_ERROR_MAX; - if (canvas->config.color_space == CHAFA_COLOR_SPACE_DIN99D) - { - chafa_color_rgb_to_din99d (&fg_col, &canvas->fg_color); - chafa_color_rgb_to_din99d (&bg_col, &canvas->bg_color); - } + if (canvas->work_factor_int >= 8) + pick_symbol_and_colors_slow (canvas, work_cell, &sym, &color_pair, &sym_error); else - { - canvas->fg_color = fg_col; - canvas->bg_color = bg_col; - } + pick_symbol_and_colors_fast (canvas, work_cell, &sym, &color_pair, &sym_error); - canvas->fg_color.ch [3] = 0xff; - canvas->bg_color.ch [3] = 0x00; -} + cell_out->c = sym; + update_cell_colors (canvas, cell_out, &color_pair); -static void -bayer_dither_pixel (ChafaCanvas *canvas, ChafaPixel *pixel, gint *matrix, gint x, gint y, guint size_shift, guint size_mask) -{ - gint bayer_index = (((y >> canvas->dither_grain_height_shift) & size_mask) << size_shift) - + ((x >> canvas->dither_grain_width_shift) & size_mask); - gint bayer_mod = matrix [bayer_index]; - gint i; + /* FIXME: It would probably be better to do the fgbg/bgfg blank symbol check + * from emit_ansi_fgbg_bgfg() here. */ - for (i = 0; i < 4; i++) - { - pixel->col.ch [i] += bayer_mod; - if (pixel->col.ch [i] < 0) - pixel->col.ch [i] = 0; - if (pixel->col.ch [i] > 255) - pixel->col.ch [i] = 255; - } + return sym_error; } -/* pixel must point to top-left pixel of the grain to be dithered */ static void -fs_dither_grain (ChafaCanvas *canvas, ChafaPixel *pixel, const ChafaPixel *error_in, - ChafaPixel *error_out_0, ChafaPixel *error_out_1, - ChafaPixel *error_out_2, ChafaPixel *error_out_3) +update_cells_wide (ChafaCanvas *canvas, ChafaWorkCell *work_cell_a, ChafaWorkCell *work_cell_b, + ChafaCanvasCell *cell_a_out, ChafaCanvasCell *cell_b_out, + gint *error_a_out, gint *error_b_out) { - gint grain_shift = canvas->dither_grain_width_shift + canvas->dither_grain_height_shift; - ChafaPixel next_error = { 0 }; - ChafaPixel accum = { 0 }; - ChafaColorCandidates cand = { 0 }; - ChafaPixel *p; - const ChafaColor *c; - gint x, y, i; - - p = pixel; - - for (y = 0; y < canvas->config.dither_grain_height; y++) - { - for (x = 0; x < canvas->config.dither_grain_width; x++, p++) - { - for (i = 0; i < 3; i++) - { - p->col.ch [i] += error_in->col.ch [i]; + gunichar sym = 0; + ChafaColorPair color_pair; - if (canvas->config.color_space == CHAFA_COLOR_SPACE_RGB) - { - if (p->col.ch [i] < 0) - { - next_error.col.ch [i] += p->col.ch [i]; - p->col.ch [i] = 0; - } - else if (p->col.ch [i] > 255) - { - next_error.col.ch [i] += p->col.ch [i] - 255; - p->col.ch [i] = 255; - } - } - - accum.col.ch [i] += p->col.ch [i]; - } - } + *error_a_out = *error_b_out = SYMBOL_ERROR_MAX; - p += canvas->width_pixels - canvas->config.dither_grain_width; - } - - for (i = 0; i < 3; i++) - accum.col.ch [i] >>= grain_shift; - - /* Don't try to dither alpha */ - accum.col.ch [3] = 0xff; + if (canvas->config.symbol_map.n_symbols2 == 0) + return; - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256) - chafa_pick_color_256 (&accum.col, canvas->config.color_space, &cand); - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240) - chafa_pick_color_240 (&accum.col, canvas->config.color_space, &cand); - else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16) - chafa_pick_color_16 (&accum.col, canvas->config.color_space, &cand); + if (canvas->work_factor_int >= 8) + pick_symbol_and_colors_wide_slow (canvas, work_cell_a, work_cell_b, + &sym, &color_pair, + error_a_out, error_b_out); else - chafa_pick_color_fgbg (&accum.col, canvas->config.color_space, - &canvas->fg_color, &canvas->bg_color, &cand); - - c = chafa_get_palette_color_256 (cand.index [0], canvas->config.color_space); + pick_symbol_and_colors_wide_fast (canvas, work_cell_a, work_cell_b, + &sym, &color_pair, + error_a_out, error_b_out); + + cell_a_out->c = sym; + cell_b_out->c = 0; + update_cell_colors (canvas, cell_a_out, &color_pair); + cell_b_out->fg_color = cell_a_out->fg_color; + cell_b_out->bg_color = cell_a_out->bg_color; + + /* quantize_colors_for_cell_16_8() can revert the char to solid, and + * the solid char is always narrow. Extend it to both cells. */ + if (cell_a_out->c == canvas->solid_char) + cell_b_out->c = cell_a_out->c; +} + +/* Number of entries in our cell ring buffer. This allows us to do lookback + * and replace single-cell symbols with double-cell ones if it improves + * the error value. */ +#define N_BUF_CELLS 4 - for (i = 0; i < 3; i++) - { - /* FIXME: Floating point op is slow. Factor this out and make - * dither_intensity == 1.0 the fast path. */ - next_error.col.ch [i] = ((next_error.col.ch [i] >> grain_shift) + (accum.col.ch [i] - c->ch [i]) * canvas->config.dither_intensity); - - error_out_0->col.ch [i] += next_error.col.ch [i] * 7 / 16; - error_out_1->col.ch [i] += next_error.col.ch [i] * 1 / 16; - error_out_2->col.ch [i] += next_error.col.ch [i] * 5 / 16; - error_out_3->col.ch [i] += next_error.col.ch [i] * 3 / 16; - } -} +/* Calculate index after positive or negative wraparound(s) */ +#define buf_cell_index(i) (((i) + N_BUF_CELLS * 64) % N_BUF_CELLS) static void -convert_rgb_to_din99d (ChafaCanvas *canvas, gint dest_y, gint n_rows) +update_cells_row (ChafaCanvas *canvas, gint row) { - ChafaPixel *pixel = canvas->pixels + dest_y * canvas->width_pixels; - ChafaPixel *pixel_max = pixel + n_rows * canvas->width_pixels; - - /* RGB -> DIN99d */ - - for ( ; pixel < pixel_max; pixel++) - { - chafa_color_rgb_to_din99d (&pixel->col, &pixel->col); - } -} + ChafaCanvasCell *cells; + ChafaWorkCell work_cells [N_BUF_CELLS]; + gint cell_errors [N_BUF_CELLS]; + gint cx, cy; -static void -bayer_dither (ChafaCanvas *canvas, gint dest_y, gint n_rows) -{ - ChafaPixel *pixel = canvas->pixels + dest_y * canvas->width_pixels; - ChafaPixel *pixel_max = pixel + n_rows * canvas->width_pixels; - guint bayer_size_mask = (1 << canvas->bayer_size_shift) - 1; - gint x, y; + cells = &canvas->cells [row * canvas->config.width]; + cy = row; - for (y = dest_y; pixel < pixel_max; y++) + for (cx = 0; cx < canvas->config.width; cx++) { - for (x = 0; x < canvas->width_pixels; x++) - { - bayer_dither_pixel (canvas, pixel, canvas->bayer_matrix, x, y, - canvas->bayer_size_shift, bayer_size_mask); - pixel++; - } - } -} - -static void -fs_dither (ChafaCanvas *canvas, gint dest_y, gint n_rows) -{ - ChafaPixel *pixel; - ChafaPixel *error_rows; - ChafaPixel *error_row [2]; - ChafaPixel *pp; - gint width_grains = canvas->width_pixels >> canvas->dither_grain_width_shift; - gint x, y; + gint buf_index = cx % N_BUF_CELLS; + ChafaWorkCell *wcell = &work_cells [buf_index]; + ChafaCanvasCell wide_cells [2]; + gint wide_cell_errors [2]; - g_assert (canvas->width_pixels % canvas->config.dither_grain_width == 0); - g_assert (dest_y % canvas->config.dither_grain_height == 0); - g_assert (n_rows % canvas->config.dither_grain_height == 0); + memset (&cells [cx], 0, sizeof (cells [cx])); + cells [cx].c = ' '; - dest_y >>= canvas->dither_grain_height_shift; - n_rows >>= canvas->dither_grain_height_shift; + chafa_work_cell_init (wcell, canvas->pixels, canvas->width_pixels, cx, cy); + cell_errors [buf_index] = update_cell (canvas, wcell, &cells [cx]); - error_rows = alloca (width_grains * 2 * sizeof (ChafaPixel)); - error_row [0] = error_rows; - error_row [1] = error_rows + width_grains; + /* Try wide symbol */ - memset (error_row [0], 0, width_grains * sizeof (ChafaPixel)); + /* FIXME: If we're overlapping the rightmost half of a wide symbol, + * try to revert it to two regular symbols and overwrite the rightmost + * one. */ - for (y = dest_y; y < dest_y + n_rows; y++) - { - memset (error_row [1], 0, width_grains * sizeof (ChafaPixel)); - - if (!(y & 1)) + if (cx >= 1 && cells [cx - 1].c != 0) { - /* Forwards pass */ - pixel = canvas->pixels + (y << canvas->dither_grain_height_shift) * canvas->width_pixels; + gint wide_buf_index [2]; + + wide_buf_index [0] = buf_cell_index (cx - 1); + wide_buf_index [1] = buf_index; - fs_dither_grain (canvas, pixel, error_row [0], - error_row [0] + 1, - error_row [1] + 1, - error_row [1], - error_row [1] + 1); - pixel += canvas->config.dither_grain_width; + update_cells_wide (canvas, + &work_cells [wide_buf_index [0]], + &work_cells [wide_buf_index [1]], + &wide_cells [0], + &wide_cells [1], + &wide_cell_errors [0], + &wide_cell_errors [1]); - for (x = 1; ((x + 1) << canvas->dither_grain_width_shift) < canvas->width_pixels; x++) + if (wide_cell_errors [0] + wide_cell_errors [1] < + cell_errors [wide_buf_index [0]] + cell_errors [wide_buf_index [1]]) { - fs_dither_grain (canvas, pixel, error_row [0] + x, - error_row [0] + x + 1, - error_row [1] + x + 1, - error_row [1] + x, - error_row [1] + x - 1); - pixel += canvas->config.dither_grain_width; + cells [cx - 1] = wide_cells [0]; + cells [cx] = wide_cells [1]; + cell_errors [wide_buf_index [0]] = wide_cell_errors [0]; + cell_errors [wide_buf_index [1]] = wide_cell_errors [1]; } - - fs_dither_grain (canvas, pixel, error_row [0] + x, - error_row [1] + x, - error_row [1] + x, - error_row [1] + x - 1, - error_row [1] + x - 1); } - else - { - /* Backwards pass */ - pixel = canvas->pixels + (y << canvas->dither_grain_height_shift) * canvas->width_pixels + canvas->width_pixels - canvas->config.dither_grain_width; - fs_dither_grain (canvas, pixel, error_row [0] + width_grains - 1, - error_row [0] + width_grains - 2, - error_row [1] + width_grains - 2, - error_row [1] + width_grains - 1, - error_row [1] + width_grains - 2); - - pixel -= canvas->config.dither_grain_width; + /* If we produced a featureless cell, try fill */ - for (x = width_grains - 2; x > 0; x--) + /* FIXME: Check popcount == 0 or == 64 instead of symbol char */ + if (cells [cx].c != 0 && (cells [cx].c == ' ' || cells [cx].c == 0x2588 + || cells [cx].fg_color == cells [cx].bg_color)) + { + if (canvas->config.fg_only_enabled) { - fs_dither_grain (canvas, pixel, error_row [0] + x, - error_row [0] + x - 1, - error_row [1] + x - 1, - error_row [1] + x, - error_row [1] + x + 1); - pixel -= canvas->config.dither_grain_width; + apply_fill_fg_only (canvas, wcell, &cells [cx]); + cells [cx].bg_color = transparent_cell_color (canvas->config.canvas_mode); + } + else + { + apply_fill (canvas, wcell, &cells [cx]); } - - fs_dither_grain (canvas, pixel, error_row [0], - error_row [1], - error_row [1], - error_row [1] + 1, - error_row [1] + 1); } - pp = error_row [0]; - error_row [0] = error_row [1]; - error_row [1] = pp; - } -} + /* If cell is still featureless after fill, use blank_char consistently */ -static void -bayer_and_convert_rgb_to_din99d (ChafaCanvas *canvas, gint dest_y, gint n_rows) -{ - ChafaPixel *pixel = canvas->pixels + dest_y * canvas->width_pixels; - ChafaPixel *pixel_max = pixel + n_rows * canvas->width_pixels; - guint bayer_size_mask = (1 << canvas->bayer_size_shift) - 1; - gint x, y; - - for (y = dest_y; pixel < pixel_max; y++) - { - for (x = 0; x < canvas->width_pixels; x++) + if (cells [cx].c != 0 && (cells [cx].c == ' ' + || cells [cx].fg_color == cells [cx].bg_color)) { - bayer_dither_pixel (canvas, pixel, canvas->bayer_matrix, x, y, - canvas->bayer_size_shift, bayer_size_mask); - chafa_color_rgb_to_din99d (&pixel->col, &pixel->col); - pixel++; - } - } -} - -static void -fs_and_convert_rgb_to_din99d (ChafaCanvas *canvas, gint dest_y, gint n_rows) -{ - convert_rgb_to_din99d (canvas, dest_y, n_rows); - fs_dither (canvas, dest_y, n_rows); -} + cells [cx].c = canvas->blank_char; -static void -maybe_clear (ChafaCanvas *canvas) -{ - gint i; - - if (!canvas->needs_clear) - return; - - for (i = 0; i < canvas->config.width * canvas->config.height; i++) - { - ChafaCanvasCell *cell = &canvas->cells [i]; + /* Copy FG color from previous cell in order to avoid emitting + * unnecessary control sequences changing it, but only if we're 100% + * sure the "blank" char has no foreground features. It's safest to + * permit this optimization only with ASCII space. */ + if (canvas->blank_char == ' ' && cx > 0) + { + cells [cx].fg_color = cells [cx - 1].fg_color; - memset (cell, 0, sizeof (*cell)); - cell->c = ' '; + /* We may use inverted colors when the foreground is transparent. + * Some downstream tools don't handle this and will keep + * modulating the wrong pen. In order to suppress long runs of + * artifacts, make the (unused) foreground pen opaque (gh#108). */ + if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) + cells [cx].fg_color |= 0xff000000; + else if (cells [cx].fg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) + cells [cx].fg_color = CHAFA_PALETTE_INDEX_FG; + } + } } } -typedef struct -{ - ChafaCanvas *canvas; - Histogram hist; - gint n_batches_pixels; - gint n_rows_per_batch_pixels; - gint n_batches_cells; - gint n_rows_per_batch_cells; - SmolScaleCtx *scale_ctx; -} -PrepareContext; - -typedef struct -{ - gint first_row; - gint n_rows; - Histogram hist; -} -PreparePixelsBatch1; - static void -prepare_pixels_1_inner (PreparePixelsBatch1 *work, - PrepareContext *prep_ctx, - const guint8 *data_p, - ChafaPixel *pixel_out, - gint *alpha_sum) +cell_build_worker (ChafaBatchInfo *batch, ChafaCanvas *canvas) { - ChafaColor *col = &pixel_out->col; - gint v; - - col->ch [0] = data_p [0]; - col->ch [1] = data_p [1]; - col->ch [2] = data_p [2]; - col->ch [3] = data_p [3]; - - *alpha_sum += (0xff - col->ch [3]); + gint i; - if (prep_ctx->canvas->config.preprocessing_enabled - && prep_ctx->canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16) + for (i = 0; i < batch->n_rows; i++) { - boost_saturation_rgb (col); - clamp_color_rgb (col); + update_cells_row (canvas, batch->first_row + i); } - - /* Build histogram */ - v = rgb_to_intensity_fast (col); - work->hist.c [v]++; } static void -prepare_pixels_1_worker_nearest (PreparePixelsBatch1 *work, PrepareContext *prep_ctx) +update_cells (ChafaCanvas *canvas) { - ChafaPixel *pixel; - ChafaCanvas *canvas; - gint dest_y; - gint px, py; - gint x_inc, y_inc; - gint alpha_sum = 0; - const guint8 *data; - gint n_rows; - gint rowstride; - - canvas = prep_ctx->canvas; - dest_y = work->first_row; - data = canvas->src_pixels; - n_rows = work->n_rows; - rowstride = canvas->src_rowstride; - - x_inc = (canvas->src_width * FIXED_MULT) / (canvas->width_pixels); - y_inc = (canvas->src_height * FIXED_MULT) / (canvas->height_pixels); - - pixel = canvas->pixels + dest_y * canvas->width_pixels; - - for (py = dest_y; py < dest_y + n_rows; py++) - { - const guint8 *data_row_p; - - data_row_p = data + ((py * y_inc) / FIXED_MULT) * rowstride; - - for (px = 0; px < canvas->width_pixels; px++) - { - const guint8 *data_p = data_row_p + ((px * x_inc) / FIXED_MULT) * 4; - prepare_pixels_1_inner (work, prep_ctx, data_p, pixel++, &alpha_sum); - } - } - - if (alpha_sum > 0) - g_atomic_int_set (&canvas->have_alpha_int, 1); + chafa_process_batches (canvas, + (GFunc) cell_build_worker, + NULL, /* _post */ + canvas->config.height, + chafa_get_n_actual_threads (), + 1); } static void -prepare_pixels_1_worker_smooth (PreparePixelsBatch1 *work, PrepareContext *prep_ctx) +differentiate_channel (guint8 *dest_channel, guint8 reference_channel, gint min_diff) { - ChafaCanvas *canvas = prep_ctx->canvas; - ChafaPixel *pixel, *pixel_max; - gint alpha_sum = 0; - guint8 *scaled_data; - const guint8 *data_p; + gint diff; - canvas = prep_ctx->canvas; + diff = (gint) *dest_channel - (gint) reference_channel; - scaled_data = g_malloc (canvas->width_pixels * work->n_rows * sizeof (guint32)); - smol_scale_batch_full (prep_ctx->scale_ctx, scaled_data, work->first_row, work->n_rows); - - data_p = scaled_data; - pixel = canvas->pixels + work->first_row * canvas->width_pixels; - pixel_max = pixel + work->n_rows * canvas->width_pixels; - - while (pixel < pixel_max) - { - prepare_pixels_1_inner (work, prep_ctx, data_p, pixel++, &alpha_sum); - data_p += 4; - } - - g_free (scaled_data); - - if (alpha_sum > 0) - g_atomic_int_set (&canvas->have_alpha_int, 1); + if (diff >= -min_diff && diff <= 0) + *dest_channel = MAX ((gint) reference_channel - min_diff, 0); + else if (diff <= min_diff && diff >= 0) + *dest_channel = MIN ((gint) reference_channel + min_diff, 255); } static void -prepare_pixels_pass_1 (PrepareContext *prep_ctx) +update_display_colors (ChafaCanvas *canvas) { - GThreadPool *thread_pool; - PreparePixelsBatch1 *batches; - gint cy; - gint i; - - /* First pass - * ---------- - * - * - Scale and convert pixel format - * - Apply local preprocessing like saturation boost (optional) - * - Generate histogram for later passes (e.g. for normalization) - * - Figure out if we have alpha transparency - */ - - batches = g_new0 (PreparePixelsBatch1, prep_ctx->n_batches_pixels); + ChafaColor fg_col; + ChafaColor bg_col; - thread_pool = g_thread_pool_new ((GFunc) ((prep_ctx->canvas->work_factor_int < 3 - && prep_ctx->canvas->src_pixel_type == CHAFA_PIXEL_RGBA8_UNASSOCIATED) - ? prepare_pixels_1_worker_nearest - : prepare_pixels_1_worker_smooth), - prep_ctx, - g_get_num_processors (), - FALSE, - NULL); + chafa_unpack_color (canvas->config.fg_color_packed_rgb, &fg_col); + chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_col); - for (cy = 0, i = 0; - cy < prep_ctx->canvas->height_pixels; - cy += prep_ctx->n_rows_per_batch_pixels, i++) + if (canvas->config.color_space == CHAFA_COLOR_SPACE_DIN99D) { - PreparePixelsBatch1 *batch = &batches [i]; - - batch->first_row = cy; - batch->n_rows = MIN (prep_ctx->canvas->height_pixels - cy, prep_ctx->n_rows_per_batch_pixels); - - g_thread_pool_push (thread_pool, batch, NULL); + chafa_color_rgb_to_din99d (&fg_col, &canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG]); + chafa_color_rgb_to_din99d (&bg_col, &canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG]); } - - /* Wait for threads to finish */ - g_thread_pool_free (thread_pool, FALSE, TRUE); - - /* Generate final histogram */ - if (prep_ctx->canvas->config.preprocessing_enabled) + else { - for (i = 0; i < prep_ctx->n_batches_pixels; i++) - sum_histograms (&batches [i].hist, &prep_ctx->hist); - - histogram_calc_bounds (prep_ctx->canvas, &prep_ctx->hist, - prep_ctx->canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 ? INDEXED_16_CROP_PCT : INDEXED_2_CROP_PCT); + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG] = fg_col; + canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG] = bg_col; } - /* Report alpha situation */ - if (prep_ctx->canvas->have_alpha_int) - prep_ctx->canvas->have_alpha = TRUE; - - g_free (batches); -} - -typedef struct -{ - gint first_row; - gint n_rows; -} -PreparePixelsBatch2; - -static void -composite_alpha_on_bg (ChafaCanvas *canvas, gint first_row, gint n_rows) -{ - ChafaPixel *p0, *p1; + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [3] = 0xff; + canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [3] = 0x00; - p0 = canvas->pixels + first_row * canvas->width_pixels; - p1 = p0 + n_rows * canvas->width_pixels; + /* When holding the BG, we need to compare against a consistent + * foreground color for symbol selection by outline. 50% gray + * yields acceptable results as a stand-in average of all possible + * colors. The BG color can't be too similar, so push it away a + * little if needed. This should work with both bright and dark + * background colors, and the background color doesn't have to + * be precise. + * + * We don't need to do this for monochrome modes, as they use the + * FG/BG colors directly. */ - for ( ; p0 < p1; p0++) + if (canvas->extract_colors && canvas->config.fg_only_enabled) { - p0->col.ch [0] += (canvas->bg_color.ch [0] * (255 - (guint32) p0->col.ch [3])) / 255; - p0->col.ch [1] += (canvas->bg_color.ch [1] * (255 - (guint32) p0->col.ch [3])) / 255; - p0->col.ch [2] += (canvas->bg_color.ch [2] * (255 - (guint32) p0->col.ch [3])) / 255; - } -} + gint i; -static void -prepare_pixels_2_worker (PreparePixelsBatch2 *work, PrepareContext *prep_ctx) -{ - ChafaCanvas *canvas = prep_ctx->canvas; + chafa_unpack_color (0xff7f7f7f, + &canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG]); - if (canvas->config.preprocessing_enabled - && (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 - || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG - || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG)) - normalize_rgb (canvas, &prep_ctx->hist, work->first_row, work->n_rows); - - if (canvas->config.color_space == CHAFA_COLOR_SPACE_DIN99D) - { - if (canvas->config.dither_mode == CHAFA_DITHER_MODE_ORDERED) - { - bayer_and_convert_rgb_to_din99d (canvas, - work->first_row, - work->n_rows); - } - else if (canvas->config.dither_mode == CHAFA_DITHER_MODE_DIFFUSION) + for (i = 0; i < 3; i++) { - fs_and_convert_rgb_to_din99d (canvas, - work->first_row, - work->n_rows); - } - else - { - convert_rgb_to_din99d (canvas, - work->first_row, - work->n_rows); + differentiate_channel (&canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [i], + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [i], + 5); } } - else if (canvas->config.dither_mode == CHAFA_DITHER_MODE_ORDERED) - { - bayer_dither (canvas, - work->first_row, - work->n_rows); - } - else if (canvas->config.dither_mode == CHAFA_DITHER_MODE_DIFFUSION) - { - fs_dither (canvas, - work->first_row, - work->n_rows); - } - - /* Must do this after DIN99d conversion, since bg_color will be DIN99d too */ - if (canvas->have_alpha) - composite_alpha_on_bg (canvas, work->first_row, work->n_rows); -} - -static gboolean -need_pass_2 (ChafaCanvas *canvas) -{ - if ((canvas->config.preprocessing_enabled - && (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 - || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG - || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG)) - || canvas->have_alpha - || canvas->config.color_space == CHAFA_COLOR_SPACE_DIN99D - || canvas->config.dither_mode != CHAFA_DITHER_MODE_NONE) - return TRUE; - - return FALSE; } static void -prepare_pixels_pass_2 (PrepareContext *prep_ctx) +maybe_clear (ChafaCanvas *canvas) { - GThreadPool *thread_pool; - PreparePixelsBatch1 *batches; - gint cy; gint i; - /* Second pass - * ----------- - * - * - Normalization (optional) - * - Dithering (optional) - * - Color space conversion; DIN99d (optional) - */ - - if (!need_pass_2 (prep_ctx->canvas)) + if (!canvas->needs_clear) return; - batches = g_new0 (PreparePixelsBatch1, prep_ctx->n_batches_pixels); - - thread_pool = g_thread_pool_new ((GFunc) prepare_pixels_2_worker, - prep_ctx, - g_get_num_processors (), - FALSE, - NULL); - - for (cy = 0, i = 0; - cy < prep_ctx->canvas->height_pixels; - cy += prep_ctx->n_rows_per_batch_pixels, i++) + for (i = 0; i < canvas->config.width * canvas->config.height; i++) { - PreparePixelsBatch1 *batch = &batches [i]; - - batch->first_row = cy; - batch->n_rows = MIN (prep_ctx->canvas->height_pixels - cy, prep_ctx->n_rows_per_batch_pixels); + ChafaCanvasCell *cell = &canvas->cells [i]; - g_thread_pool_push (thread_pool, batch, NULL); + memset (cell, 0, sizeof (*cell)); + cell->c = ' '; } - - /* Wait for threads to finish */ - g_thread_pool_free (thread_pool, FALSE, TRUE); - - g_free (batches); } static void -prepare_pixel_data (ChafaCanvas *canvas) +setup_palette (ChafaCanvas *canvas) { - PrepareContext prep_ctx = { 0 }; - guint n_cpus; - - n_cpus = g_get_num_processors (); - - prep_ctx.canvas = canvas; - prep_ctx.n_batches_pixels = (canvas->height_pixels + n_cpus - 1) / n_cpus; - prep_ctx.n_rows_per_batch_pixels = (canvas->height_pixels + prep_ctx.n_batches_pixels - 1) / prep_ctx.n_batches_pixels; - prep_ctx.n_batches_cells = (canvas->config.height + n_cpus - 1) / n_cpus; - prep_ctx.n_rows_per_batch_cells = (canvas->config.height + prep_ctx.n_batches_cells - 1) / prep_ctx.n_batches_cells; + ChafaPaletteType fg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; + ChafaPaletteType bg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; + ChafaColor fg_col; + ChafaColor bg_col; - prep_ctx.scale_ctx = smol_scale_new ((SmolPixelType) canvas->src_pixel_type, - (const guint32 *) canvas->src_pixels, - canvas->src_width, - canvas->src_height, - canvas->src_rowstride, - SMOL_PIXEL_RGBA8_PREMULTIPLIED, - NULL, - canvas->width_pixels, - canvas->height_pixels, - canvas->width_pixels * sizeof (guint32)); + chafa_unpack_color (canvas->config.fg_color_packed_rgb, &fg_col); + chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_col); - prepare_pixels_pass_1 (&prep_ctx); - prepare_pixels_pass_2 (&prep_ctx); + fg_col.ch [3] = 0xff; + bg_col.ch [3] = 0x00; - smol_scale_destroy (prep_ctx.scale_ctx); -} + /* The repetition here kind of sucks, but it'll get better once the + * palette refactoring is done and subtypes go away. */ -static void -emit_ansi_truecolor (ChafaCanvas *canvas, GString *gs, gint i, gint i_max) -{ - for ( ; i < i_max; i++) + switch (chafa_canvas_config_get_canvas_mode (&canvas->config)) { - ChafaCanvasCell *cell = &canvas->cells [i]; - ChafaColor fg, bg; - - chafa_unpack_color (cell->fg_color, &fg); - chafa_unpack_color (cell->bg_color, &bg); - - if (fg.ch [3] < canvas->config.alpha_threshold) - { - if (bg.ch [3] < canvas->config.alpha_threshold) - { - /* FIXME: Respect include/exclude for space */ - g_string_append (gs, "\x1b[0m "); - } - else - { - g_string_append_printf (gs, "\x1b[0m\x1b[7m\x1b[38;2;%d;%d;%dm", - bg.ch [0], bg.ch [1], bg.ch [2]); - g_string_append_unichar (gs, cell->c); - } - } - else if (bg.ch [3] < canvas->config.alpha_threshold) - { - g_string_append_printf (gs, "\x1b[0m\x1b[38;2;%d;%d;%dm", - fg.ch [0], fg.ch [1], fg.ch [2]); - g_string_append_unichar (gs, cell->c); - } - else - { - g_string_append_printf (gs, "\x1b[0m\x1b[38;2;%d;%d;%dm\x1b[48;2;%d;%d;%dm", - fg.ch [0], fg.ch [1], fg.ch [2], - bg.ch [0], bg.ch [1], bg.ch [2]); - g_string_append_unichar (gs, cell->c); - } - } -} + case CHAFA_CANVAS_MODE_TRUECOLOR: + fg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; + bg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; + break; -static void -emit_ansi_256 (ChafaCanvas *canvas, GString *gs, gint i, gint i_max) -{ - for ( ; i < i_max; i++) - { - ChafaCanvasCell *cell = &canvas->cells [i]; + case CHAFA_CANVAS_MODE_INDEXED_256: + fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_256; + bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_256; + break; - if (cell->fg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) - { - if (cell->bg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) - { - g_string_append (gs, "\x1b[0m "); - } - else - { - g_string_append_printf (gs, "\x1b[0m\x1b[7m\x1b[38;5;%dm", - cell->bg_color); - g_string_append_unichar (gs, cell->c); - } - } - else if (cell->bg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) - { - g_string_append_printf (gs, "\x1b[0m\x1b[38;5;%dm", - cell->fg_color); - g_string_append_unichar (gs, cell->c); - } - else - { - g_string_append_printf (gs, "\x1b[0m\x1b[38;5;%dm\x1b[48;5;%dm", - cell->fg_color, - cell->bg_color); - g_string_append_unichar (gs, cell->c); - } - } -} + case CHAFA_CANVAS_MODE_INDEXED_240: + fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_240; + bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_240; + break; -/* Uses aixterm control codes for bright colors */ -static void -emit_ansi_16 (ChafaCanvas *canvas, GString *gs, gint i, gint i_max) -{ - for ( ; i < i_max; i++) - { - ChafaCanvasCell *cell = &canvas->cells [i]; + case CHAFA_CANVAS_MODE_INDEXED_16: + fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_16; + bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_16; + break; - if (cell->fg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) - { - if (cell->bg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) - { - g_string_append (gs, "\x1b[0m "); - } - else - { - g_string_append_printf (gs, "\x1b[0m\x1b[7m\x1b[%dm", - cell->bg_color < 8 ? cell->bg_color + 30 - : cell->bg_color + 90 - 8); - g_string_append_unichar (gs, cell->c); - } - } - else if (cell->bg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) - { - g_string_append_printf (gs, "\x1b[0m\x1b[%dm", - cell->fg_color < 8 ? cell->fg_color + 30 - : cell->fg_color + 90 - 8); - g_string_append_unichar (gs, cell->c); - } - else - { - g_string_append_printf (gs, "\x1b[0m\x1b[%dm\x1b[%dm", - cell->fg_color < 8 ? cell->fg_color + 30 - : cell->fg_color + 90 - 8, - cell->bg_color < 8 ? cell->bg_color + 40 - : cell->bg_color + 100 - 8); - g_string_append_unichar (gs, cell->c); - } - } -} + case CHAFA_CANVAS_MODE_INDEXED_16_8: + fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_16; + bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_8; + break; -static void -emit_ansi_fgbg_bgfg (ChafaCanvas *canvas, GString *gs, gint i, gint i_max) -{ - gunichar blank_symbol = 0; + case CHAFA_CANVAS_MODE_INDEXED_8: + fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_8; + bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_8; + break; - if (chafa_symbol_map_has_symbol (&canvas->config.symbol_map, ' ')) - { - blank_symbol = ' '; - } - else if (chafa_symbol_map_has_symbol (&canvas->config.symbol_map, 0x2588 /* Solid block */ )) - { - blank_symbol = 0x2588; + case CHAFA_CANVAS_MODE_FGBG_BGFG: + case CHAFA_CANVAS_MODE_FGBG: + fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_FGBG; + bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_FGBG; + break; + + case CHAFA_CANVAS_MODE_MAX: + g_assert_not_reached (); } - for ( ; i < i_max; i++) - { - ChafaCanvasCell *cell = &canvas->cells [i]; - gboolean invert = FALSE; - gunichar c = cell->c; + chafa_palette_init (&canvas->fg_palette, fg_pal_type); - if (cell->fg_color == cell->bg_color && blank_symbol != 0) - { - c = blank_symbol; - if (blank_symbol == 0x2588) - invert = TRUE; - } + chafa_palette_set_color (&canvas->fg_palette, CHAFA_PALETTE_INDEX_FG, &fg_col); + chafa_palette_set_color (&canvas->fg_palette, CHAFA_PALETTE_INDEX_BG, &bg_col); - if (cell->bg_color == CHAFA_PALETTE_INDEX_FG) - { - invert ^= TRUE; - } + chafa_palette_set_alpha_threshold (&canvas->fg_palette, canvas->config.alpha_threshold); + chafa_palette_set_transparent_index (&canvas->fg_palette, CHAFA_PALETTE_INDEX_TRANSPARENT); - g_string_append_printf (gs, "\x1b[%dm", - invert ? 7 : 0); - g_string_append_unichar (gs, c); - } -} + chafa_palette_init (&canvas->bg_palette, bg_pal_type); -static void -emit_ansi_fgbg (ChafaCanvas *canvas, GString *gs, gint i, gint i_max) -{ - for ( ; i < i_max; i++) - { - ChafaCanvasCell *cell = &canvas->cells [i]; + chafa_palette_set_color (&canvas->bg_palette, CHAFA_PALETTE_INDEX_FG, &fg_col); + chafa_palette_set_color (&canvas->bg_palette, CHAFA_PALETTE_INDEX_BG, &bg_col); - g_string_append_unichar (gs, cell->c); - } + chafa_palette_set_alpha_threshold (&canvas->bg_palette, canvas->config.alpha_threshold); + chafa_palette_set_transparent_index (&canvas->bg_palette, CHAFA_PALETTE_INDEX_TRANSPARENT); } -static GString * -build_ansi_gstring (ChafaCanvas *canvas) +static gunichar +find_best_blank_char (ChafaCanvas *canvas) { - GString *gs = g_string_new (""); - gint i, i_max, i_step, i_next; - - maybe_clear (canvas); - - i = 0; - i_max = canvas->config.width * canvas->config.height; - i_step = canvas->config.width; + ChafaCandidate candidates [N_CANDIDATES_MAX]; + gint n_candidates; + gunichar best_char = 0x20; - for ( ; i < i_max; i = i_next) + /* Try space (0x20) first */ + if (chafa_symbol_map_has_symbol (&canvas->config.symbol_map, 0x20) + || chafa_symbol_map_has_symbol (&canvas->config.fill_symbol_map, 0x20)) + return 0x20; + + n_candidates = N_CANDIDATES_MAX; + chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, + 0, + FALSE, + candidates, + &n_candidates); + if (n_candidates > 0) { - i_next = i + i_step; - - switch (canvas->config.canvas_mode) + best_char = canvas->config.fill_symbol_map.symbols [candidates [0].symbol_index].c; + } + else + { + n_candidates = N_CANDIDATES_MAX; + chafa_symbol_map_find_candidates (&canvas->config.symbol_map, + 0, + FALSE, + candidates, + &n_candidates); + if (n_candidates > 0) { - case CHAFA_CANVAS_MODE_TRUECOLOR: - emit_ansi_truecolor (canvas, gs, i, i_next); - break; - case CHAFA_CANVAS_MODE_INDEXED_256: - case CHAFA_CANVAS_MODE_INDEXED_240: - emit_ansi_256 (canvas, gs, i, i_next); - break; - case CHAFA_CANVAS_MODE_INDEXED_16: - emit_ansi_16 (canvas, gs, i, i_next); - break; - case CHAFA_CANVAS_MODE_FGBG_BGFG: - emit_ansi_fgbg_bgfg (canvas, gs, i, i_next); - break; - case CHAFA_CANVAS_MODE_FGBG: - emit_ansi_fgbg (canvas, gs, i, i_next); - break; - case CHAFA_CANVAS_MODE_MAX: - g_assert_not_reached (); - break; + best_char = canvas->config.symbol_map.symbols [candidates [0].symbol_index].c; } - - /* No control codes in FGBG mode */ - if (canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG) - g_string_append (gs, "\x1b[0m"); - - /* Last line should not end in newline */ - if (i_next < i_max) - g_string_append_c (gs, '\n'); } - return gs; + return best_char; } -static gint -calc_dither_grain_shift (gint size) +static gunichar +find_best_solid_char (ChafaCanvas *canvas) { - switch (size) + ChafaCandidate candidates [N_CANDIDATES_MAX]; + gint n_candidates; + gunichar best_char = 0; + + /* Try solid block (0x2588) first */ + if (chafa_symbol_map_has_symbol (&canvas->config.symbol_map, 0x2588) + || chafa_symbol_map_has_symbol (&canvas->config.fill_symbol_map, 0x2588)) + return 0x2588; + + n_candidates = N_CANDIDATES_MAX; + chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, + 64, + FALSE, + candidates, + &n_candidates); + if (n_candidates > 0 && candidates [0].hamming_distance <= 32) { - case 1: - return 0; - case 2: - return 1; - case 4: - return 2; - case 8: - return 3; - default: - g_assert_not_reached (); + best_char = canvas->config.fill_symbol_map.symbols [candidates [0].symbol_index].c; + } + else + { + n_candidates = N_CANDIDATES_MAX; + chafa_symbol_map_find_candidates (&canvas->config.symbol_map, + 0xffffffffffffffff, + FALSE, + candidates, + &n_candidates); + if (n_candidates > 0 && candidates [0].hamming_distance <= 32) + { + best_char = canvas->config.symbol_map.symbols [candidates [0].symbol_index].c; + } } - return 0; + return best_char; } /** @@ -1834,6 +1259,7 @@ chafa_canvas_new (const ChafaCanvasConfig *config) { ChafaCanvas *canvas; + gdouble dither_intensity = 1.0; if (config) { @@ -1851,45 +1277,87 @@ chafa_canvas_config_init (&canvas->config); canvas->refs = 1; - canvas->width_pixels = canvas->config.width * CHAFA_SYMBOL_WIDTH_PIXELS; - canvas->height_pixels = canvas->config.height * CHAFA_SYMBOL_HEIGHT_PIXELS; + + if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) + { + /* ANSI art */ + canvas->width_pixels = canvas->config.width * CHAFA_SYMBOL_WIDTH_PIXELS; + canvas->height_pixels = canvas->config.height * CHAFA_SYMBOL_HEIGHT_PIXELS; + } + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) + { + /* Sixels */ + canvas->width_pixels = canvas->config.width * canvas->config.cell_width; + canvas->height_pixels = canvas->config.height * canvas->config.cell_height; + + /* Ensure height is the biggest multiple of 6 that will fit + * in our cells. We don't want a fringe going outside our + * bottom cell. */ + canvas->height_pixels -= canvas->height_pixels % 6; + } + else /* CHAFA_PIXEL_MODE_KITTY or CHAFA_PIXEL_MODE_ITERM2 */ + { + canvas->width_pixels = canvas->config.width * canvas->config.cell_width; + canvas->height_pixels = canvas->config.height * canvas->config.cell_height; + } + canvas->pixels = NULL; canvas->cells = g_new (ChafaCanvasCell, canvas->config.width * canvas->config.height); - canvas->work_factor_int = canvas->config.work_factor * 10 + 0.5; + canvas->work_factor_int = canvas->config.work_factor * 10 + 0.5f; canvas->needs_clear = TRUE; canvas->have_alpha = FALSE; + canvas->consider_inverted = !(canvas->config.fg_only_enabled + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG); + + canvas->extract_colors = !(canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG + || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG); + + if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) + canvas->config.fg_only_enabled = TRUE; + + canvas->use_quantized_error = + (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16_8 + && !canvas->config.fg_only_enabled); + chafa_symbol_map_prepare (&canvas->config.symbol_map); chafa_symbol_map_prepare (&canvas->config.fill_symbol_map); + canvas->blank_char = find_best_blank_char (canvas); + canvas->solid_char = find_best_solid_char (canvas); + /* In truecolor mode we don't support any fancy color spaces for now, since * we'd have to convert back to RGB space when emitting control codes, and * the code for that has yet to be written. In palette modes we just use * the palette mappings. * - * There is also no reason to dither in truecolor mode. */ - if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) + * There is also no reason to dither in truecolor mode, _unless_ we're + * producing sixels, which quantize to a dynamic palette. */ + if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY + || canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2 + || (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR + && canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS)) { canvas->config.color_space = CHAFA_COLOR_SPACE_RGB; canvas->config.dither_mode = CHAFA_DITHER_MODE_NONE; } - canvas->dither_grain_width_shift = calc_dither_grain_shift (canvas->config.dither_grain_width); - canvas->dither_grain_height_shift = calc_dither_grain_shift (canvas->config.dither_grain_height); - if (canvas->config.dither_mode == CHAFA_DITHER_MODE_ORDERED) { - gdouble dither_intensity; - switch (canvas->config.canvas_mode) { + case CHAFA_CANVAS_MODE_TRUECOLOR: case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: dither_intensity = DITHER_BASE_INTENSITY_256C; break; case CHAFA_CANVAS_MODE_INDEXED_16: + case CHAFA_CANVAS_MODE_INDEXED_16_8: dither_intensity = DITHER_BASE_INTENSITY_16C; break; + case CHAFA_CANVAS_MODE_INDEXED_8: + dither_intensity = DITHER_BASE_INTENSITY_8C; + break; case CHAFA_CANVAS_MODE_FGBG: case CHAFA_CANVAS_MODE_FGBG_BGFG: dither_intensity = DITHER_BASE_INTENSITY_FGBG; @@ -1898,12 +1366,15 @@ g_assert_not_reached (); break; } - - canvas->bayer_size_shift = BAYER_MATRIX_DIM_SHIFT; - canvas->bayer_matrix = chafa_gen_bayer_matrix (BAYER_MATRIX_DIM, dither_intensity * canvas->config.dither_intensity); } + chafa_dither_init (&canvas->dither, canvas->config.dither_mode, + dither_intensity * canvas->config.dither_intensity, + canvas->config.dither_grain_width, + canvas->config.dither_grain_height); + update_display_colors (canvas); + setup_palette (canvas); return canvas; } @@ -1932,7 +1403,8 @@ canvas->pixels = NULL; canvas->cells = g_new (ChafaCanvasCell, canvas->config.width * canvas->config.height); canvas->needs_clear = TRUE; - canvas->bayer_matrix = g_memdup (orig->bayer_matrix, BAYER_MATRIX_SIZE * sizeof (gint)); + + chafa_dither_copy (&orig->dither, &canvas->dither); return canvas; } @@ -1955,6 +1427,22 @@ g_atomic_int_inc (&canvas->refs); } +static void +destroy_pixel_canvas (ChafaCanvas *canvas) +{ + if (canvas->pixel_canvas) + { + if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) + chafa_sixel_canvas_destroy (canvas->pixel_canvas); + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY) + chafa_kitty_canvas_destroy (canvas->pixel_canvas); + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) + chafa_iterm2_canvas_destroy (canvas->pixel_canvas); + + canvas->pixel_canvas = NULL; + } +} + /** * chafa_canvas_unref: * @canvas: Canvas to remove a reference from @@ -1974,9 +1462,12 @@ if (g_atomic_int_dec_and_test (&canvas->refs)) { chafa_canvas_config_deinit (&canvas->config); + destroy_pixel_canvas (canvas); + chafa_dither_deinit (&canvas->dither); + chafa_palette_deinit (&canvas->fg_palette); + chafa_palette_deinit (&canvas->bg_palette); g_free (canvas->pixels); g_free (canvas->cells); - g_free (canvas->bayer_matrix); g_free (canvas); } } @@ -2029,27 +1520,88 @@ if (src_width == 0 || src_height == 0) return; - canvas->pixels = g_new (ChafaPixel, canvas->width_pixels * canvas->height_pixels); - canvas->hist = g_new (Histogram, 1); + if (canvas->pixels) + { + g_free (canvas->pixels); + canvas->pixels = NULL; + } + + destroy_pixel_canvas (canvas); + + if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) + { + /* Symbol mode */ - canvas->src_pixel_type = src_pixel_type; - canvas->src_pixels = src_pixels; - canvas->src_width = src_width; - canvas->src_height = src_height; - canvas->src_rowstride = src_rowstride; - canvas->have_alpha_int = 0; - - prepare_pixel_data (canvas); + canvas->pixels = g_new (ChafaPixel, canvas->width_pixels * canvas->height_pixels); - if (canvas->config.alpha_threshold == 0) - canvas->have_alpha = FALSE; + chafa_prepare_pixel_data_for_symbols (&canvas->fg_palette, &canvas->dither, + canvas->config.color_space, + canvas->config.preprocessing_enabled, + canvas->work_factor_int, + src_pixel_type, + src_pixels, + src_width, src_height, + src_rowstride, + canvas->pixels, + canvas->width_pixels, canvas->height_pixels); - update_cells (canvas); - canvas->needs_clear = FALSE; + if (canvas->config.alpha_threshold == 0) + canvas->have_alpha = FALSE; - g_free (canvas->hist); - g_free (canvas->pixels); - canvas->pixels = NULL; + update_cells (canvas); + canvas->needs_clear = FALSE; + + g_free (canvas->pixels); + canvas->pixels = NULL; + } + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) + { + /* Sixel mode */ + + canvas->fg_palette.alpha_threshold = canvas->config.alpha_threshold; + canvas->pixel_canvas = chafa_sixel_canvas_new (canvas->width_pixels, + canvas->height_pixels, + canvas->config.color_space, + &canvas->fg_palette, + &canvas->dither); + chafa_sixel_canvas_draw_all_pixels (canvas->pixel_canvas, + src_pixel_type, + src_pixels, + src_width, src_height, + src_rowstride); + } + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY) + { + ChafaColor bg_color; + + chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_color); + bg_color.ch [3] = canvas->config.alpha_threshold < 1 ? 0x00 : 0xff; + + /* Kitty mode */ + + canvas->fg_palette.alpha_threshold = canvas->config.alpha_threshold; + canvas->pixel_canvas = chafa_kitty_canvas_new (canvas->width_pixels, + canvas->height_pixels); + chafa_kitty_canvas_draw_all_pixels (canvas->pixel_canvas, + src_pixel_type, + src_pixels, + src_width, src_height, + src_rowstride, + bg_color); + } + else /* if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) */ + { + /* iTerm2 mode */ + + canvas->fg_palette.alpha_threshold = canvas->config.alpha_threshold; + canvas->pixel_canvas = chafa_iterm2_canvas_new (canvas->width_pixels, + canvas->height_pixels); + chafa_iterm2_canvas_draw_all_pixels (canvas->pixel_canvas, + src_pixel_type, + src_pixels, + src_width, src_height, + src_rowstride); + } } /** @@ -2087,6 +1639,8 @@ * All output lines except for the last one will end in a newline. * * Returns: A UTF-8 string of ANSI sequences and symbols + * + * Deprecated: 1.6: Use chafa_canvas_print() instead. **/ GString * chafa_canvas_build_ansi (ChafaCanvas *canvas) @@ -2094,5 +1648,455 @@ g_return_val_if_fail (canvas != NULL, NULL); g_return_val_if_fail (canvas->refs > 0, NULL); - return build_ansi_gstring (canvas); + return chafa_canvas_print (canvas, NULL); +} + +/** + * chafa_canvas_print: + * @canvas: The canvas to generate a printable representation of + * @term_info: Terminal to format for, or %NULL for fallback + * + * Builds a UTF-8 string of terminal control sequences and symbols + * representing the canvas' current contents. This can e.g. be printed + * to a terminal. The exact choice of escape sequences and symbols, + * dimensions, etc. is determined by the configuration assigned to + * @canvas on its creation. + * + * All output lines except for the last one will end in a newline. + * + * Returns: A UTF-8 string of terminal control sequences and symbols + * + * Since: 1.6 + **/ +GString * +chafa_canvas_print (ChafaCanvas *canvas, ChafaTermInfo *term_info) +{ + GString *str; + + g_return_val_if_fail (canvas != NULL, NULL); + g_return_val_if_fail (canvas->refs > 0, NULL); + + if (term_info) + chafa_term_info_ref (term_info); + else + term_info = chafa_term_db_get_fallback_info (chafa_term_db_get_default ()); + + if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) + { + maybe_clear (canvas); + str = chafa_canvas_print_symbols (canvas, term_info); + } + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS + && chafa_term_info_get_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS)) + { + gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; + gchar *out; + + /* Sixel mode */ + + out = chafa_term_info_emit_begin_sixels (term_info, buf, 0, 1, 0); + *out = '\0'; + str = g_string_new (buf); + + g_string_append_printf (str, "\"1;1;%d;%d", canvas->width_pixels, canvas->height_pixels); + chafa_sixel_canvas_build_ansi (canvas->pixel_canvas, str); + + out = chafa_term_info_emit_end_sixels (term_info, buf); + *out = '\0'; + g_string_append (str, buf); + } + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY + && chafa_term_info_get_seq (term_info, CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1)) + { + /* Kitty mode */ + + str = g_string_new (""); + chafa_kitty_canvas_build_ansi (canvas->pixel_canvas, term_info, str, + canvas->config.width, canvas->config.height); + } + else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) + { + /* iTerm2 mode */ + + str = g_string_new (""); + chafa_iterm2_canvas_build_ansi (canvas->pixel_canvas, term_info, str, + canvas->config.width, canvas->config.height); + } + else + { + str = g_string_new (""); + } + + chafa_term_info_unref (term_info); + return str; +} + +/** + * chafa_canvas_get_char_at: + * @canvas: The canvas to inspect + * @x: Column of character cell to inspect + * @y: Row of character cell to inspect + * + * Returns the character at cell (x, y). The coordinates are zero-indexed. For + * double-width characters, the leftmost cell will contain the character + * and the rightmost cell will contain 0. + * + * Returns: The character at (x, y) + * + * Since: 1.8 + **/ +gunichar +chafa_canvas_get_char_at (ChafaCanvas *canvas, gint x, gint y) +{ + g_return_val_if_fail (canvas != NULL, 0); + g_return_val_if_fail (canvas->refs > 0, 0); + g_return_val_if_fail (x >= 0 && x < canvas->config.width, 0); + g_return_val_if_fail (y >= 0 && y < canvas->config.height, 0); + + return canvas->cells [y * canvas->config.width + x].c; +} + +/** + * chafa_canvas_set_char_at: + * @canvas: The canvas to manipulate + * @x: Column of character cell to manipulate + * @y: Row of character cell to manipulate + * @c: The character value to store + * + * Sets the character at cell (x, y). The coordinates are zero-indexed. For + * double-width characters, the leftmost cell must contain the character + * and the cell to the right of it will automatically be set to 0. + * + * If the character is a nonprintable or zero-width, no change will be + * made. + * + * Returns: The number of cells output (0, 1 or 2) + * + * Since: 1.8 + **/ +gint +chafa_canvas_set_char_at (ChafaCanvas *canvas, gint x, gint y, gunichar c) +{ + ChafaCanvasCell *cell; + gint cwidth = 1; + + g_return_val_if_fail (canvas != NULL, 0); + g_return_val_if_fail (canvas->refs > 0, 0); + g_return_val_if_fail (x >= 0 && x < canvas->config.width, 0); + g_return_val_if_fail (y >= 0 && y < canvas->config.height, 0); + + if (!g_unichar_isprint (c) || g_unichar_iszerowidth (c)) + return 0; + + if (g_unichar_iswide (c)) + cwidth = 2; + + if (x + cwidth > canvas->config.width) + return 0; + + cell = &canvas->cells [y * canvas->config.width + x]; + cell [0].c = c; + + if (cwidth == 2) + { + cell [1].c = 0; + cell [1].fg_color = cell [0].fg_color; + cell [1].bg_color = cell [0].bg_color; + } + + /* If we're overwriting the rightmost half of a wide character, + * clear its leftmost half */ + + if (x > 0) + { + if (cell [-1].c != 0 + && g_unichar_iswide (cell [-1].c)) + cell [-1].c = canvas->blank_char; + } + + return cwidth; +} + +/** + * chafa_canvas_get_colors_at: + * @canvas: The canvas to inspect + * @x: Column of character cell to inspect + * @y: Row of character cell to inspect + * @fg_out: Storage for foreground color + * @bg_out: Storage for background color + * + * Gets the colors at cell (x, y). The coordinates are zero-indexed. For + * double-width characters, both cells will contain the same colors. + * + * The colors will be -1 for transparency, packed 8bpc RGB otherwise, + * i.e. 0x00RRGGBB hex. + * + * If the canvas is in an indexed mode, palette lookups will be made + * for you. + * + * Since: 1.8 + **/ +void +chafa_canvas_get_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint *fg_out, gint *bg_out) +{ + const ChafaCanvasCell *cell; + gint fg = -1, bg = -1; + + g_return_if_fail (canvas != NULL); + g_return_if_fail (canvas->refs > 0); + g_return_if_fail (x >= 0 && x < canvas->config.width); + g_return_if_fail (y >= 0 && y < canvas->config.height); + + cell = &canvas->cells [y * canvas->config.width + x]; + + switch (canvas->config.canvas_mode) + { + case CHAFA_CANVAS_MODE_TRUECOLOR: + fg = packed_rgba_to_rgb (canvas, cell->fg_color); + bg = packed_rgba_to_rgb (canvas, cell->bg_color); + break; + case CHAFA_CANVAS_MODE_INDEXED_256: + case CHAFA_CANVAS_MODE_INDEXED_240: + case CHAFA_CANVAS_MODE_INDEXED_16: + case CHAFA_CANVAS_MODE_INDEXED_16_8: + case CHAFA_CANVAS_MODE_INDEXED_8: + case CHAFA_CANVAS_MODE_FGBG_BGFG: + case CHAFA_CANVAS_MODE_FGBG: + if (cell->fg_color == CHAFA_PALETTE_INDEX_BG + || cell->fg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) + fg = -1; + else + fg = color_to_rgb (canvas, + *get_palette_color_with_color_space (&canvas->fg_palette, cell->fg_color, + CHAFA_COLOR_SPACE_RGB)); + if (cell->bg_color == CHAFA_PALETTE_INDEX_BG + || cell->bg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) + bg = -1; + else + bg = color_to_rgb (canvas, + *get_palette_color_with_color_space (&canvas->bg_palette, cell->bg_color, + CHAFA_COLOR_SPACE_RGB)); + break; + case CHAFA_CANVAS_MODE_MAX: + g_assert_not_reached (); + break; + } + + *fg_out = fg; + *bg_out = bg; +} + +/** + * chafa_canvas_set_colors_at: + * @canvas: The canvas to manipulate + * @x: Column of character cell to manipulate + * @y: Row of character cell to manipulate + * @fg: Foreground color + * @bg: Background color + * + * Sets the colors at cell (x, y). The coordinates are zero-indexed. For + * double-width characters, both cells will be set to the same color. + * + * The colors must be -1 for transparency, packed 8bpc RGB otherwise, + * i.e. 0x00RRGGBB hex. + * + * If the canvas is in an indexed mode, palette lookups will be made + * for you. + * + * Since: 1.8 + **/ +void +chafa_canvas_set_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint fg, gint bg) +{ + ChafaCanvasCell *cell; + + g_return_if_fail (canvas != NULL); + g_return_if_fail (canvas->refs > 0); + g_return_if_fail (x >= 0 && x < canvas->config.width); + g_return_if_fail (y >= 0 && y < canvas->config.height); + + cell = &canvas->cells [y * canvas->config.width + x]; + + switch (canvas->config.canvas_mode) + { + case CHAFA_CANVAS_MODE_TRUECOLOR: + cell->fg_color = packed_rgb_to_rgba (fg); + cell->bg_color = packed_rgb_to_rgba (bg); + break; + case CHAFA_CANVAS_MODE_INDEXED_256: + case CHAFA_CANVAS_MODE_INDEXED_240: + case CHAFA_CANVAS_MODE_INDEXED_16: + case CHAFA_CANVAS_MODE_INDEXED_16_8: + case CHAFA_CANVAS_MODE_INDEXED_8: + cell->fg_color = packed_rgb_to_index (&canvas->fg_palette, canvas->config.color_space, fg); + cell->bg_color = packed_rgb_to_index (&canvas->bg_palette, canvas->config.color_space, bg); + break; + case CHAFA_CANVAS_MODE_FGBG_BGFG: + cell->fg_color = fg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; + cell->bg_color = bg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; + break; + case CHAFA_CANVAS_MODE_FGBG: + cell->fg_color = fg >= 0 ? fg : CHAFA_PALETTE_INDEX_TRANSPARENT; + break; + case CHAFA_CANVAS_MODE_MAX: + g_assert_not_reached (); + break; + } + + /* If setting the color of half a wide char, set it for the other half too */ + + if (x > 0 && cell->c == 0) + { + cell [-1].fg_color = cell->fg_color; + cell [-1].bg_color = cell->bg_color; + } + + if (x < canvas->config.width - 1 && cell [1].c == 0) + { + cell [1].fg_color = cell->fg_color; + cell [1].bg_color = cell->bg_color; + } +} + +/** + * chafa_canvas_get_raw_colors_at: + * @canvas: The canvas to inspect + * @x: Column of character cell to inspect + * @y: Row of character cell to inspect + * @fg_out: Storage for foreground color + * @bg_out: Storage for background color + * + * Gets the colors at cell (x, y). The coordinates are zero-indexed. For + * double-width characters, both cells will contain the same colors. + * + * The colors will be -1 for transparency, packed 8bpc RGB, i.e. + * 0x00RRGGBB hex in truecolor mode, or the raw pen value (0-255) in + * indexed modes. + * + * It's the caller's responsibility to handle the color values correctly + * according to the canvas mode (truecolor or indexed). + * + * Since: 1.8 + **/ +void +chafa_canvas_get_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint *fg_out, gint *bg_out) +{ + const ChafaCanvasCell *cell; + gint fg = -1, bg = -1; + + g_return_if_fail (canvas != NULL); + g_return_if_fail (canvas->refs > 0); + g_return_if_fail (x >= 0 && x < canvas->config.width); + g_return_if_fail (y >= 0 && y < canvas->config.height); + + cell = &canvas->cells [y * canvas->config.width + x]; + + switch (canvas->config.canvas_mode) + { + case CHAFA_CANVAS_MODE_TRUECOLOR: + fg = packed_rgba_to_rgb (canvas, cell->fg_color); + bg = packed_rgba_to_rgb (canvas, cell->bg_color); + break; + case CHAFA_CANVAS_MODE_INDEXED_256: + case CHAFA_CANVAS_MODE_INDEXED_240: + case CHAFA_CANVAS_MODE_INDEXED_16: + case CHAFA_CANVAS_MODE_INDEXED_16_8: + case CHAFA_CANVAS_MODE_INDEXED_8: + fg = cell->fg_color < 256 ? (gint) cell->fg_color : -1; + bg = cell->bg_color < 256 ? (gint) cell->bg_color : -1; + break; + case CHAFA_CANVAS_MODE_FGBG_BGFG: + fg = cell->fg_color == CHAFA_PALETTE_INDEX_FG ? 0 : -1; + bg = cell->bg_color == CHAFA_PALETTE_INDEX_FG ? 0 : -1; + break; + case CHAFA_CANVAS_MODE_FGBG: + fg = 0; + bg = -1; + break; + case CHAFA_CANVAS_MODE_MAX: + g_assert_not_reached (); + break; + } + + if (fg_out) + *fg_out = fg; + if (bg_out) + *bg_out = bg; +} + +/** + * chafa_canvas_set_raw_colors_at: + * @canvas: The canvas to manipulate + * @x: Column of character cell to manipulate + * @y: Row of character cell to manipulate + * @fg: Foreground color + * @bg: Background color + * + * Sets the colors at cell (x, y). The coordinates are zero-indexed. For + * double-width characters, both cells will be set to the same color. + * + * The colors must be -1 for transparency, packed 8bpc RGB, i.e. + * 0x00RRGGBB hex in truecolor mode, or the raw pen value (0-255) in + * indexed modes. + * + * It's the caller's responsibility to handle the color values correctly + * according to the canvas mode (truecolor or indexed). + * + * Since: 1.8 + **/ +void +chafa_canvas_set_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint fg, gint bg) +{ + ChafaCanvasCell *cell; + + g_return_if_fail (canvas != NULL); + g_return_if_fail (canvas->refs > 0); + g_return_if_fail (x >= 0 && x < canvas->config.width); + g_return_if_fail (y >= 0 && y < canvas->config.height); + + cell = &canvas->cells [y * canvas->config.width + x]; + + switch (canvas->config.canvas_mode) + { + case CHAFA_CANVAS_MODE_TRUECOLOR: + cell->fg_color = packed_rgb_to_rgba (fg); + cell->bg_color = packed_rgb_to_rgba (bg); + break; + case CHAFA_CANVAS_MODE_INDEXED_256: + case CHAFA_CANVAS_MODE_INDEXED_240: + case CHAFA_CANVAS_MODE_INDEXED_16: + case CHAFA_CANVAS_MODE_INDEXED_16_8: + case CHAFA_CANVAS_MODE_INDEXED_8: + cell->fg_color = fg >= 0 ? fg : CHAFA_PALETTE_INDEX_TRANSPARENT; + cell->bg_color = bg >= 0 ? bg : CHAFA_PALETTE_INDEX_TRANSPARENT; + break; + case CHAFA_CANVAS_MODE_FGBG_BGFG: + cell->fg_color = fg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; + cell->bg_color = bg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; + break; + case CHAFA_CANVAS_MODE_FGBG: + cell->fg_color = fg >= 0 ? fg : CHAFA_PALETTE_INDEX_TRANSPARENT; + break; + case CHAFA_CANVAS_MODE_MAX: + g_assert_not_reached (); + break; + } + + /* If setting the color of half a wide char, set it for the other half too */ + + if (x > 0 && cell->c == 0) + { + cell [-1].fg_color = cell->fg_color; + cell [-1].bg_color = cell->bg_color; + } + + if (x < canvas->config.width - 1 && cell [1].c == 0) + { + cell [1].fg_color = cell->fg_color; + cell [1].bg_color = cell->bg_color; + } } diff -Nru chafa-1.2.1/chafa/chafa-canvas-config.c chafa-1.12.4/chafa/chafa-canvas-config.c --- chafa-1.2.1/chafa/chafa-canvas-config.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-canvas-config.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -20,8 +20,8 @@ #include "config.h" #include /* memset, memcpy */ -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" +#include "chafa.h" +#include "internal/chafa-private.h" /** * SECTION:chafa-canvas-config @@ -45,12 +45,21 @@ * @CHAFA_CANVAS_MODE_INDEXED_256: 256 colors. * @CHAFA_CANVAS_MODE_INDEXED_240: 256 colors, but avoid using the lower 16 whose values vary between terminal environments. * @CHAFA_CANVAS_MODE_INDEXED_16: 16 colors using the aixterm ANSI extension. + * @CHAFA_CANVAS_MODE_INDEXED_16_8: 16 FG colors (8 of which enabled with bold/bright) and 8 BG colors. + * @CHAFA_CANVAS_MODE_INDEXED_8: 8 colors, compatible with original ANSI X3.64. * @CHAFA_CANVAS_MODE_FGBG_BGFG: Default foreground and background colors, plus inversion. * @CHAFA_CANVAS_MODE_FGBG: Default foreground and background colors. No ANSI codes will be used. * @CHAFA_CANVAS_MODE_MAX: Last supported canvas mode plus one. **/ /** + * ChafaColorExtractor: + * @CHAFA_COLOR_EXTRACTOR_AVERAGE: Use the average colors of each symbol's coverage area. + * @CHAFA_COLOR_EXTRACTOR_MEDIAN: Use the median colors of each symbol's coverage area. + * @CHAFA_COLOR_EXTRACTOR_MAX: Last supported color extractor plus one. + **/ + +/** * ChafaColorSpace: * @CHAFA_COLOR_SPACE_RGB: RGB color space. Fast but imprecise. * @CHAFA_COLOR_SPACE_DIN99D: DIN99d color space. Slower, but good perceptual color precision. @@ -65,6 +74,24 @@ * @CHAFA_DITHER_MODE_MAX: Last supported dither mode plus one. **/ +/** + * ChafaPixelMode: + * @CHAFA_PIXEL_MODE_SYMBOLS: Pixel data is approximated using character symbols ("ANSI art"). + * @CHAFA_PIXEL_MODE_SIXELS: Pixel data is encoded as sixels. + * @CHAFA_PIXEL_MODE_KITTY: Pixel data is encoded using the Kitty terminal protocol. + * @CHAFA_PIXEL_MODE_ITERM2: Pixel data is encoded using the iTerm2 terminal protocol. + * @CHAFA_PIXEL_MODE_MAX: Last supported pixel mode plus one. + **/ + +/** + * ChafaOptimizations: + * @CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES: Suppress redundant SGR control sequences. + * @CHAFA_OPTIMIZATION_SKIP_CELLS: Reserved for future use. + * @CHAFA_OPTIMIZATION_REPEAT_CELLS: Use REP sequence to compress repeated runs of similar cells. + * @CHAFA_OPTIMIZATION_NONE: All optimizations disabled. + * @CHAFA_OPTIMIZATION_ALL: All optimizations enabled. + **/ + /* Private */ void @@ -76,9 +103,13 @@ canvas_config->refs = 1; canvas_config->canvas_mode = CHAFA_CANVAS_MODE_TRUECOLOR; + canvas_config->color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; canvas_config->color_space = CHAFA_COLOR_SPACE_RGB; + canvas_config->pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; canvas_config->width = 80; canvas_config->height = 24; + canvas_config->cell_width = 8; + canvas_config->cell_height = 8; canvas_config->dither_mode = CHAFA_DITHER_MODE_NONE; canvas_config->dither_grain_width = 4; canvas_config->dither_grain_height = 4; @@ -88,9 +119,14 @@ canvas_config->alpha_threshold = 127; canvas_config->work_factor = 0.5; canvas_config->preprocessing_enabled = TRUE; + canvas_config->optimizations = CHAFA_OPTIMIZATION_ALL; + canvas_config->fg_only_enabled = FALSE; chafa_symbol_map_init (&canvas_config->symbol_map); - chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_ALL); + chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_BLOCK); + chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_BORDER); + chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_SPACE); + chafa_symbol_map_remove_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_WIDE); chafa_symbol_map_init (&canvas_config->fill_symbol_map); } @@ -236,10 +272,56 @@ } /** + * chafa_canvas_config_get_cell_geometry: + * @config: A #ChafaCanvasConfig + * @cell_width_out: Location to store cell width in, or %NULL + * @cell_height_out: Location to store cell height in, or %NULL + * + * Returns @config's cell width and height in pixels in the + * provided output locations. + * + * Since: 1.4 + **/ +void +chafa_canvas_config_get_cell_geometry (const ChafaCanvasConfig *config, gint *cell_width_out, gint *cell_height_out) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (config->refs > 0); + + if (cell_width_out) + *cell_width_out = config->cell_width; + if (cell_height_out) + *cell_height_out = config->cell_height; +} + +/** + * chafa_canvas_config_set_cell_geometry: + * @config: A #ChafaCanvasConfig + * @cell_width: Cell width in pixels + * @cell_height: Cell height in pixels + * + * Sets @config's cell width and height in pixels to @cell_width x @cell_height. + * + * Since: 1.4 + **/ +void +chafa_canvas_config_set_cell_geometry (ChafaCanvasConfig *config, gint cell_width, gint cell_height) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (config->refs > 0); + g_return_if_fail (cell_width > 0); + g_return_if_fail (cell_height > 0); + + config->cell_width = cell_width; + config->cell_height = cell_height; +} + +/** * chafa_canvas_config_get_canvas_mode: * @config: A #ChafaCanvasConfig * - * Returns @config's #ChafaCanvasMode. + * Returns @config's #ChafaCanvasMode. This determines how colors (and + * color control codes) are used in the output. * * Returns: The #ChafaCanvasMode. **/ @@ -257,7 +339,8 @@ * @config: A #ChafaCanvasConfig * @mode: A #ChafaCanvasMode * - * Sets @config's stored #ChafaCanvasMode to @mode. + * Sets @config's stored #ChafaCanvasMode to @mode. This determines how + * colors (and color control codes) are used in the output. **/ void chafa_canvas_config_set_canvas_mode (ChafaCanvasConfig *config, ChafaCanvasMode mode) @@ -270,6 +353,46 @@ } /** + * chafa_canvas_config_get_color_extractor: + * @config: A #ChafaCanvasConfig + * + * Returns @config's #ChafaColorExtractor. This determines how colors are + * approximated in character symbol output. + * + * Returns: The #ChafaColorExtractor. + * + * Since: 1.4 + **/ +ChafaColorExtractor +chafa_canvas_config_get_color_extractor (const ChafaCanvasConfig *config) +{ + g_return_val_if_fail (config != NULL, CHAFA_COLOR_EXTRACTOR_AVERAGE); + g_return_val_if_fail (config->refs > 0, CHAFA_COLOR_EXTRACTOR_AVERAGE); + + return config->color_extractor; +} + +/** + * chafa_canvas_config_set_color_extractor: + * @config: A #ChafaCanvasConfig + * @color_extractor: A #ChafaColorExtractor + * + * Sets @config's stored #ChafaColorExtractor to @color_extractor. This + * determines how colors are approximated in character symbol output. + * + * Since: 1.4 + **/ +void +chafa_canvas_config_set_color_extractor (ChafaCanvasConfig *config, ChafaColorExtractor color_extractor) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (config->refs > 0); + g_return_if_fail (color_extractor < CHAFA_COLOR_EXTRACTOR_MAX); + + config->color_extractor = color_extractor; +} + +/** * chafa_canvas_config_get_color_space: * @config: A #ChafaCanvasConfig * @@ -510,7 +633,6 @@ * * Sets the work/quality tradeoff factor. A higher value means more time * and memory will be spent towards a higher quality output. - * **/ void chafa_canvas_config_set_work_factor (ChafaCanvasConfig *config, gfloat work_factor) @@ -687,3 +809,131 @@ config->dither_intensity = intensity; } + +/** + * chafa_canvas_config_get_pixel_mode: + * @config: A #ChafaCanvasConfig + * + * Returns @config's #ChafaPixelMode. + * + * Returns: The #ChafaPixelMode. This determines how pixel graphics are + * rendered in the output. + * + * Since: 1.4 + **/ +ChafaPixelMode +chafa_canvas_config_get_pixel_mode (const ChafaCanvasConfig *config) +{ + g_return_val_if_fail (config != NULL, CHAFA_PIXEL_MODE_SYMBOLS); + g_return_val_if_fail (config->refs > 0, CHAFA_PIXEL_MODE_SYMBOLS); + + return config->pixel_mode; +} + +/** + * chafa_canvas_config_set_pixel_mode: + * @config: A #ChafaCanvasConfig + * @pixel_mode: A #ChafaPixelMode + * + * Sets @config's stored #ChafaPixelMode to @pixel_mode. This determines + * how pixel graphics are rendered in the output. + * + * Since: 1.4 + **/ +void +chafa_canvas_config_set_pixel_mode (ChafaCanvasConfig *config, ChafaPixelMode pixel_mode) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (config->refs > 0); + g_return_if_fail (pixel_mode < CHAFA_PIXEL_MODE_MAX); + + config->pixel_mode = pixel_mode; +} + +/** + * chafa_canvas_config_get_optimizations: + * @config: A #ChafaCanvasConfig + * + * Returns @config's optimization flags. When enabled, these may produce + * more compact output at the cost of reduced compatibility and increased CPU + * use. Output quality is unaffected. + * + * Returns: The #ChafaOptimizations flags. + * + * Since: 1.6 + **/ +ChafaOptimizations +chafa_canvas_config_get_optimizations (const ChafaCanvasConfig *config) +{ + g_return_val_if_fail (config != NULL, CHAFA_OPTIMIZATION_NONE); + g_return_val_if_fail (config->refs > 0, CHAFA_OPTIMIZATION_NONE); + + return config->optimizations; +} + +/** + * chafa_canvas_config_set_optimizations: + * @config: A #ChafaCanvasConfig + * @optimizations: A combination of #ChafaOptimizations flags + * + * Sets @config's stored optimization flags. When enabled, these may produce + * more compact output at the cost of reduced compatibility and increased CPU + * use. Output quality is unaffected. + * + * Since: 1.6 + **/ +void +chafa_canvas_config_set_optimizations (ChafaCanvasConfig *config, ChafaOptimizations optimizations) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (config->refs > 0); + + config->optimizations = optimizations; +} + +/** + * chafa_canvas_config_get_fg_only_enabled: + * @config: A #ChafaCanvasConfig + * + * Queries whether to use foreground colors only, leaving the background + * unmodified in the canvas output. This is relevant only when the + * #ChafaPixelMode is set to #CHAFA_PIXEL_MODE_SYMBOLS. + * + * When this is set, the canvas will emit escape codes to set the foreground + * color only. + * + * Returns: %TRUE if using foreground colors only, %FALSE otherwise. + * + * Since: 1.8 + **/ +gboolean +chafa_canvas_config_get_fg_only_enabled (const ChafaCanvasConfig *config) +{ + g_return_val_if_fail (config != NULL, CHAFA_OPTIMIZATION_NONE); + g_return_val_if_fail (config->refs > 0, CHAFA_OPTIMIZATION_NONE); + + return config->fg_only_enabled; +} + +/** + * chafa_canvas_config_set_fg_only_enabled: + * @config: A #ChafaCanvasConfig + * @fg_only_enabled: Whether to use foreground colors only + * + * Indicates whether to use foreground colors only, leaving the background + * unmodified in the canvas output. This is relevant only when the + * #ChafaPixelMode is set to #CHAFA_PIXEL_MODE_SYMBOLS. + * + * When this is set, the canvas will emit escape codes to set the foreground + * color only. + * + * Since: 1.8 + **/ +void +chafa_canvas_config_set_fg_only_enabled (ChafaCanvasConfig *config, gboolean fg_only_enabled) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (config->refs > 0); + + config->fg_only_enabled = fg_only_enabled; +} diff -Nru chafa-1.2.1/chafa/chafa-canvas-config.h chafa-1.12.4/chafa/chafa-canvas-config.h --- chafa-1.2.1/chafa/chafa-canvas-config.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-canvas-config.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -28,6 +28,17 @@ G_BEGIN_DECLS +/* Color extractor */ + +typedef enum +{ + CHAFA_COLOR_EXTRACTOR_AVERAGE, + CHAFA_COLOR_EXTRACTOR_MEDIAN, + + CHAFA_COLOR_EXTRACTOR_MAX +} +ChafaColorExtractor; + /* Color spaces */ typedef enum @@ -49,6 +60,8 @@ CHAFA_CANVAS_MODE_INDEXED_16, CHAFA_CANVAS_MODE_FGBG_BGFG, CHAFA_CANVAS_MODE_FGBG, + CHAFA_CANVAS_MODE_INDEXED_8, + CHAFA_CANVAS_MODE_INDEXED_16_8, CHAFA_CANVAS_MODE_MAX } @@ -66,6 +79,34 @@ } ChafaDitherMode; +/* Pixel modes */ + +typedef enum +{ + CHAFA_PIXEL_MODE_SYMBOLS, + CHAFA_PIXEL_MODE_SIXELS, + CHAFA_PIXEL_MODE_KITTY, + CHAFA_PIXEL_MODE_ITERM2, + + CHAFA_PIXEL_MODE_MAX +} +ChafaPixelMode; + +/* Sequence optimization flags. When enabled, these may produce more compact + * output at the cost of reduced compatibility and increased CPU use. Output + * quality is unaffected. */ + +typedef enum +{ + CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES = (1 << 0), + CHAFA_OPTIMIZATION_SKIP_CELLS = (1 << 1), + CHAFA_OPTIMIZATION_REPEAT_CELLS = (1 << 2), + + CHAFA_OPTIMIZATION_NONE = 0, + CHAFA_OPTIMIZATION_ALL = 0x7fffffff +} +ChafaOptimizations; + /* Canvas config */ typedef struct ChafaCanvasConfig ChafaCanvasConfig; @@ -84,11 +125,21 @@ CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_geometry (ChafaCanvasConfig *config, gint width, gint height); +CHAFA_AVAILABLE_IN_1_4 +void chafa_canvas_config_get_cell_geometry (const ChafaCanvasConfig *config, gint *cell_width_out, gint *cell_height_out); +CHAFA_AVAILABLE_IN_1_4 +void chafa_canvas_config_set_cell_geometry (ChafaCanvasConfig *config, gint cell_width, gint cell_height); + CHAFA_AVAILABLE_IN_ALL ChafaCanvasMode chafa_canvas_config_get_canvas_mode (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_canvas_mode (ChafaCanvasConfig *config, ChafaCanvasMode mode); +CHAFA_AVAILABLE_IN_1_4 +ChafaColorExtractor chafa_canvas_config_get_color_extractor (const ChafaCanvasConfig *config); +CHAFA_AVAILABLE_IN_1_4 +void chafa_canvas_config_set_color_extractor (ChafaCanvasConfig *config, ChafaColorExtractor color_extractor); + CHAFA_AVAILABLE_IN_ALL ChafaColorSpace chafa_canvas_config_get_color_space (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL @@ -144,6 +195,21 @@ CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_config_set_dither_intensity (ChafaCanvasConfig *config, gfloat intensity); +CHAFA_AVAILABLE_IN_1_4 +ChafaPixelMode chafa_canvas_config_get_pixel_mode (const ChafaCanvasConfig *config); +CHAFA_AVAILABLE_IN_1_4 +void chafa_canvas_config_set_pixel_mode (ChafaCanvasConfig *config, ChafaPixelMode pixel_mode); + +CHAFA_AVAILABLE_IN_1_6 +ChafaOptimizations chafa_canvas_config_get_optimizations (const ChafaCanvasConfig *config); +CHAFA_AVAILABLE_IN_1_6 +void chafa_canvas_config_set_optimizations (ChafaCanvasConfig *config, ChafaOptimizations optimizations); + +CHAFA_AVAILABLE_IN_1_8 +gboolean chafa_canvas_config_get_fg_only_enabled (const ChafaCanvasConfig *config); +CHAFA_AVAILABLE_IN_1_8 +void chafa_canvas_config_set_fg_only_enabled (ChafaCanvasConfig *config, gboolean fg_only_enabled); + G_END_DECLS #endif /* __CHAFA_CANVAS_CONFIG_H__ */ diff -Nru chafa-1.2.1/chafa/chafa-canvas.h chafa-1.12.4/chafa/chafa-canvas.h --- chafa-1.2.1/chafa/chafa-canvas.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-canvas.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -24,32 +24,9 @@ # error "Only can be included directly." #endif -G_BEGIN_DECLS - -/* Canvas */ +#include -typedef enum -{ - /* 32 bits per pixel */ - - CHAFA_PIXEL_RGBA8_PREMULTIPLIED, - CHAFA_PIXEL_BGRA8_PREMULTIPLIED, - CHAFA_PIXEL_ARGB8_PREMULTIPLIED, - CHAFA_PIXEL_ABGR8_PREMULTIPLIED, - - CHAFA_PIXEL_RGBA8_UNASSOCIATED, - CHAFA_PIXEL_BGRA8_UNASSOCIATED, - CHAFA_PIXEL_ARGB8_UNASSOCIATED, - CHAFA_PIXEL_ABGR8_UNASSOCIATED, - - /* 24 bits per pixel */ - - CHAFA_PIXEL_RGB8, - CHAFA_PIXEL_BGR8, - - CHAFA_PIXEL_MAX -} -ChafaPixelType; +G_BEGIN_DECLS typedef struct ChafaCanvas ChafaCanvas; @@ -65,17 +42,36 @@ CHAFA_AVAILABLE_IN_ALL const ChafaCanvasConfig *chafa_canvas_peek_config (ChafaCanvas *canvas); -CHAFA_DEPRECATED_IN_1_2 -void chafa_canvas_set_contents_rgba8 (ChafaCanvas *canvas, const guint8 *src_pixels, - gint src_width, gint src_height, gint src_rowstride); - - CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_draw_all_pixels (ChafaCanvas *canvas, ChafaPixelType src_pixel_type, const guint8 *src_pixels, gint src_width, gint src_height, gint src_rowstride); +CHAFA_AVAILABLE_IN_1_6 +GString *chafa_canvas_print (ChafaCanvas *canvas, ChafaTermInfo *term_info); -CHAFA_AVAILABLE_IN_ALL +CHAFA_AVAILABLE_IN_1_8 +gunichar chafa_canvas_get_char_at (ChafaCanvas *canvas, gint x, gint y); +CHAFA_AVAILABLE_IN_1_8 +gint chafa_canvas_set_char_at (ChafaCanvas *canvas, gint x, gint y, gunichar c); + +CHAFA_AVAILABLE_IN_1_8 +void chafa_canvas_get_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint *fg_out, gint *bg_out); +CHAFA_AVAILABLE_IN_1_8 +void chafa_canvas_set_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint fg, gint bg); + +CHAFA_AVAILABLE_IN_1_8 +void chafa_canvas_get_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint *fg_out, gint *bg_out); +CHAFA_AVAILABLE_IN_1_8 +void chafa_canvas_set_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, + gint fg, gint bg); + +CHAFA_DEPRECATED_IN_1_2 +void chafa_canvas_set_contents_rgba8 (ChafaCanvas *canvas, const guint8 *src_pixels, + gint src_width, gint src_height, gint src_rowstride); +CHAFA_DEPRECATED_IN_1_6 GString *chafa_canvas_build_ansi (ChafaCanvas *canvas); G_END_DECLS diff -Nru chafa-1.2.1/chafa/chafa-colors.c chafa-1.12.4/chafa/chafa-colors.c --- chafa-1.2.1/chafa/chafa-colors.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-colors.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,547 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -#include "config.h" - -#include /* abs */ -#include /* pow, cbrt, log, sqrt, atan2, cos, sin */ -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" - -#define N_TERM_COLORS 259 - -/* 256-color values */ -static guint32 term_colors_256 [N_TERM_COLORS] = -{ - 0x000000, 0x800000, 0x007000, 0x707000, 0x000070, 0x700070, 0x007070, 0xc0c0c0, - /* 0x808080 -> */ 0x404040, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, - - 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, - 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, - - 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, - 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, - - 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, - 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, - - 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, - 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, - - 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, - 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, - - 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, - 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, - - 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, - 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, - - 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, - 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, - - 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, - 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, - - 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, - 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, - - 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, - 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, - - 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, - 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, - - 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, - 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, - - 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, - 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, - - 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, - 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, - - 0x808080, /* Transparent */ - 0xffffff, /* Foreground */ - 0x000000, /* Background */ -}; - -static ChafaPaletteColor palette_256 [N_TERM_COLORS]; -static guchar color_cube_216_channel_index [256]; -static gboolean palette_initialized; - -void -chafa_init_palette (void) -{ - gint i; - - if (palette_initialized) - return; - - for (i = 0; i < N_TERM_COLORS; i++) - { - chafa_unpack_color (term_colors_256 [i], &palette_256 [i].col [0]); - chafa_color_rgb_to_din99d (&palette_256 [i].col [0], &palette_256 [i].col [1]); - - palette_256 [i].col [0].ch [3] = 0xff; /* Fully opaque */ - palette_256 [i].col [1].ch [3] = 0xff; /* Fully opaque */ - } - - /* Transparent color */ - palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [0].ch [3] = 0x00; - palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [1].ch [3] = 0x00; - - for (i = 0; i < 0x5f / 2; i++) - color_cube_216_channel_index [i] = 0; - for ( ; i < (0x5f + 0x87) / 2; i++) - color_cube_216_channel_index [i] = 1; - for ( ; i < (0x87 + 0xaf) / 2; i++) - color_cube_216_channel_index [i] = 2; - for ( ; i < (0xaf + 0xd7) / 2; i++) - color_cube_216_channel_index [i] = 3; - for ( ; i < (0xd7 + 0xff) / 2; i++) - color_cube_216_channel_index [i] = 4; - for ( ; i <= 0xff; i++) - color_cube_216_channel_index [i] = 5; - - palette_initialized = TRUE; -} - -const ChafaColor * -chafa_get_palette_color_256 (guint index, ChafaColorSpace color_space) -{ - return &palette_256 [index].col [color_space]; -} - -guint32 -chafa_pack_color (const ChafaColor *color) -{ - /* Assumes each channel 0 <= value <= 255 */ - return (color->ch [0] << 16) - | (color->ch [1] << 8) - | (color->ch [2]) - | (color->ch [3] << 24); /* Alpha */ -} - -void -chafa_unpack_color (guint32 packed, ChafaColor *color_out) -{ - color_out->ch [0] = (packed >> 16) & 0xff; - color_out->ch [1] = (packed >> 8) & 0xff; - color_out->ch [2] = packed & 0xff; - color_out->ch [3] = (packed >> 24) & 0xff; /* Alpha */ -} - -void -chafa_color_div_scalar (ChafaColor *color, gint scalar) -{ - color->ch [0] /= scalar; - color->ch [1] /= scalar; - color->ch [2] /= scalar; - color->ch [3] /= scalar; -} - -typedef struct -{ - gdouble c [3]; -} -ChafaColorRGBf; - -typedef struct -{ - gdouble c [3]; -} -ChafaColorXYZ; - -typedef struct -{ - gdouble c [3]; -} -ChafaColorLab; - -static gdouble -invert_rgb_channel_compand (gdouble v) -{ - return v <= 0.04045 ? (v / 12.92) : pow ((v + 0.055) / 1.044, 2.4); -} - -static void -convert_rgb_to_xyz (const ChafaColor *rgbi, ChafaColorXYZ *xyz) -{ - ChafaColorRGBf rgbf; - gint i; - - for (i = 0; i < 3; i++) - { - rgbf.c [i] = (gdouble) rgbi->ch [i] / 255.0; - rgbf.c [i] = invert_rgb_channel_compand (rgbf.c [i]); - } - - xyz->c [0] = 0.4124564 * rgbf.c [0] + 0.3575761 * rgbf.c [1] + 0.1804375 * rgbf.c [2]; - xyz->c [1] = 0.2126729 * rgbf.c [0] + 0.7151522 * rgbf.c [1] + 0.0721750 * rgbf.c [2]; - xyz->c [2] = 0.0193339 * rgbf.c [0] + 0.1191920 * rgbf.c [1] + 0.9503041 * rgbf.c [2]; -} - -#define XYZ_EPSILON (216.0 / 24389.0) -#define XYZ_KAPPA (24389.0 / 27.0) - -static gdouble -lab_f (gdouble v) -{ - return v > XYZ_EPSILON ? cbrt (v) : (XYZ_KAPPA * v + 16.0) / 116.0; -} - -static void -convert_xyz_to_lab (const ChafaColorXYZ *xyz, ChafaColorLab *lab) -{ - ChafaColorXYZ wp = { { 0.95047, 1.0, 1.08883 } }; /* D65 white point */ - ChafaColorXYZ xyz2; - gint i; - - for (i = 0; i < 3; i++) - xyz2.c [i] = lab_f (xyz->c [i] / wp.c [i]); - - lab->c [0] = 116.0 * xyz2.c [1] - 16.0; - lab->c [1] = 500.0 * (xyz2.c [0] - xyz2.c [1]); - lab->c [2] = 200.0 * (xyz2.c [1] - xyz2.c [2]); -} - -void -chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99) -{ - ChafaColorXYZ xyz; - ChafaColorLab lab; - gdouble adj_L, ee, f, G, C, h; - - convert_rgb_to_xyz (rgb, &xyz); - - /* Apply tristimulus-space correction term */ - - xyz.c [0] = 1.12 * xyz.c [0] - 0.12 * xyz.c [2]; - - /* Convert to L*a*b* */ - - convert_xyz_to_lab (&xyz, &lab); - adj_L = 325.22 * log (1.0 + 0.0036 * lab.c [0]); - - /* Intermediate parameters */ - - ee = 0.6427876096865393 * lab.c [1] + 0.766044443118978 * lab.c [2]; - f = 1.14 * (0.6427876096865393 * lab.c [2] - 0.766044443118978 * lab.c [1]); - G = sqrt (ee * ee + f * f); - - /* Hue/chroma */ - - C = 22.5 * log (1.0 + 0.06 * G); - - h = atan2 (f, ee) + 0.8726646 /* 50 degrees */; - while (h < 0.0) h += 6.283185; /* 360 degrees */ - while (h > 6.283185) h -= 6.283185; /* 360 degrees */ - - din99->ch [0] = adj_L * 4.0; - din99->ch [1] = C * cos (h) * 4.0; - din99->ch [2] = C * sin (h) * 4.0; - din99->ch [3] = rgb->ch [3]; -} - -static gint -color_diff_rgb (const ChafaColor *col_a, const ChafaColor *col_b) -{ - gint error = 0; - gint d [3]; - - d [0] = col_b->ch [0] - col_a->ch [0]; - d [0] = d [0] * d [0]; - d [1] = col_b->ch [1] - col_a->ch [1]; - d [1] = d [1] * d [1]; - d [2] = col_b->ch [2] - col_a->ch [2]; - d [2] = d [2] * d [2]; - - error = 2 * d [0] + 4 * d [1] + 3 * d [2] - + (((col_a->ch [0] + col_b->ch [0]) / 2) - * abs (d [0] - d [2])) / 256; - - return error; -} - -static gint -color_diff_euclidean (const ChafaColor *col_a, const ChafaColor *col_b) -{ - gint error = 0; - gint d [3]; - - d [0] = col_b->ch [0] - col_a->ch [0]; - d [0] = d [0] * d [0]; - d [1] = col_b->ch [1] - col_a->ch [1]; - d [1] = d [1] * d [1]; - d [2] = col_b->ch [2] - col_a->ch [2]; - d [2] = d [2] * d [2]; - - error = d [0] + d [1] + d [2]; - return error; -} - -static gint -color_diff_alpha (const ChafaColor *col_a, const ChafaColor *col_b, gint error) -{ - gint max_opacity; - gint a; - - /* Alpha */ - a = col_b->ch [3] - col_a->ch [3]; - a = a * a; - max_opacity = MAX (col_a->ch [3], col_b->ch [3]); - error *= max_opacity; - error /= 256; - error += a * 8; - - return error; -} - -gint -chafa_color_diff_slow (const ChafaColor *col_a, const ChafaColor *col_b, ChafaColorSpace color_space) -{ - gint error; - - if (color_space == CHAFA_COLOR_SPACE_RGB) - error = color_diff_rgb (col_a, col_b); - else if (color_space == CHAFA_COLOR_SPACE_DIN99D) - error = color_diff_euclidean (col_a, col_b); - else - { - g_assert_not_reached (); - return -1; - } - - error = color_diff_alpha (col_a, col_b, error); - - return error; -} - -/* FIXME: We may be able to avoid mixing alpha in most cases, but 16-color fill relies - * on it at the moment. */ -void -chafa_color_mix (ChafaColor *out, const ChafaColor *a, const ChafaColor *b, gint ratio) -{ - gint i; - - for (i = 0; i < 4; i++) - out->ch [i] = (a->ch [i] * ratio + b->ch [i] * (1000 - ratio)) / 1000; -} - -static void -init_candidates (ChafaColorCandidates *candidates) -{ - candidates->index [0] = candidates->index [1] = -1; - candidates->error [0] = candidates->error [1] = G_MAXINT; -} - -static gboolean -update_candidates (ChafaColorCandidates *candidates, gint index, gint error) -{ - if (error < candidates->error [0]) - { - candidates->index [1] = candidates->index [0]; - candidates->index [0] = index; - candidates->error [1] = candidates->error [0]; - candidates->error [0] = error; - return TRUE; - } - else if (error < candidates->error [1]) - { - candidates->index [1] = index; - candidates->error [1] = error; - return TRUE; - } - - return FALSE; -} - -static gint -update_candidates_with_color_index_diff (ChafaColorCandidates *candidates, ChafaColorSpace color_space, const ChafaColor *color, gint index) -{ - const ChafaColor *palette_color; - gint error; - - palette_color = chafa_get_palette_color_256 (index, color_space); - error = chafa_color_diff_slow (color, palette_color, color_space); - update_candidates (candidates, index, error); - - return error; -} - -static void -pick_color_216_cube (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) -{ - gint i; - - g_assert (color_space == CHAFA_COLOR_SPACE_RGB); - - i = 16 + (color_cube_216_channel_index [color->ch [0]] * 6 * 6 - + color_cube_216_channel_index [color->ch [1]] * 6 - + color_cube_216_channel_index [color->ch [2]]); - - update_candidates_with_color_index_diff (candidates, color_space, color, i); -} - -static void -pick_color_24_grays (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) -{ - const ChafaColor *palette_color; - gint error, last_error = G_MAXINT; - gint step, i; - - g_assert (color_space == CHAFA_COLOR_SPACE_RGB); - - i = 232 + 12; - last_error = update_candidates_with_color_index_diff (candidates, color_space, color, i); - - palette_color = chafa_get_palette_color_256 (i + 1, color_space); - error = chafa_color_diff_slow (color, palette_color, color_space); - if (error < last_error) - { - update_candidates (candidates, i, error); - last_error = error; - step = 1; - i++; - } - else - { - step = -1; - } - - do - { - i += step; - palette_color = chafa_get_palette_color_256 (i, color_space); - - error = chafa_color_diff_slow (color, palette_color, color_space); - if (error > last_error) - break; - - update_candidates (candidates, i, error); - last_error = error; - } - while (i >= 232 && i <= 255); -} - -static void -pick_color_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) -{ - gint i; - - for (i = 0; i < 16; i++) - { - update_candidates_with_color_index_diff (candidates, color_space, color, i); - } - - /* Try transparency */ - - update_candidates_with_color_index_diff (candidates, color_space, color, - CHAFA_PALETTE_INDEX_TRANSPARENT); -} - -void -chafa_pick_color_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) -{ - init_candidates (candidates); - pick_color_16 (color, color_space, candidates); -} - -void -chafa_pick_color_256 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) -{ - gint i; - - init_candidates (candidates); - - if (color_space == CHAFA_COLOR_SPACE_RGB) - { - pick_color_216_cube (color, color_space, candidates); - pick_color_24_grays (color, color_space, candidates); - - /* This will try transparency too. Do this last so ties are broken in - * favor of high-index colors. */ - pick_color_16 (color, color_space, candidates); - } - else - { - /* All colors including transparent, but not bg or fg */ - for (i = 0; i < 257; i++) - { - update_candidates_with_color_index_diff (candidates, color_space, color, i); - } - } -} - -void -chafa_pick_color_240 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) -{ - gint i; - - init_candidates (candidates); - - if (color_space == CHAFA_COLOR_SPACE_RGB) - { - pick_color_216_cube (color, color_space, candidates); - pick_color_24_grays (color, color_space, candidates); - - /* Try transparency */ - - update_candidates_with_color_index_diff (candidates, color_space, color, - CHAFA_PALETTE_INDEX_TRANSPARENT); - } - else - { - /* Color cube and transparent, but not lower 16, bg or fg */ - for (i = 16; i < 257; i++) - { - update_candidates_with_color_index_diff (candidates, color_space, color, i); - } - } -} - -/* Pick the best approximation of color from a palette consisting of - * fg_color and bg_color */ -void -chafa_pick_color_fgbg (const ChafaColor *color, ChafaColorSpace color_space, - const ChafaColor *fg_color, const ChafaColor *bg_color, - ChafaColorCandidates *candidates) -{ - gint error; - - init_candidates (candidates); - - error = chafa_color_diff_slow (color, fg_color, color_space); - update_candidates (candidates, CHAFA_PALETTE_INDEX_FG, error); - - error = chafa_color_diff_slow (color, bg_color, color_space); - update_candidates (candidates, CHAFA_PALETTE_INDEX_BG, error); - - /* Consider opaque background too */ - - if (candidates->index [0] != CHAFA_PALETTE_INDEX_BG) - { - ChafaColor bg_color_opaque = *bg_color; - bg_color_opaque.ch [3] = 0xff; - - error = chafa_color_diff_slow (color, &bg_color_opaque, color_space); - update_candidates (candidates, CHAFA_PALETTE_INDEX_BG, error); - } -} diff -Nru chafa-1.2.1/chafa/chafa-common.h chafa-1.12.4/chafa/chafa-common.h --- chafa-1.2.1/chafa/chafa-common.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-common.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_COMMON_H__ +#define __CHAFA_COMMON_H__ + +#if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) +# error "Only can be included directly." +#endif + +G_BEGIN_DECLS + +/** + * ChafaPixelType: + * @CHAFA_PIXEL_RGBA8_PREMULTIPLIED: Premultiplied RGBA, 8 bits per channel. + * @CHAFA_PIXEL_BGRA8_PREMULTIPLIED: Premultiplied BGRA, 8 bits per channel. + * @CHAFA_PIXEL_ARGB8_PREMULTIPLIED: Premultiplied ARGB, 8 bits per channel. + * @CHAFA_PIXEL_ABGR8_PREMULTIPLIED: Premultiplied ABGR, 8 bits per channel. + * @CHAFA_PIXEL_RGBA8_UNASSOCIATED: Unassociated RGBA, 8 bits per channel. + * @CHAFA_PIXEL_BGRA8_UNASSOCIATED: Unassociated BGRA, 8 bits per channel. + * @CHAFA_PIXEL_ARGB8_UNASSOCIATED: Unassociated ARGB, 8 bits per channel. + * @CHAFA_PIXEL_ABGR8_UNASSOCIATED: Unassociated ABGR, 8 bits per channel. + * @CHAFA_PIXEL_RGB8: Packed RGB (no alpha), 8 bits per channel. + * @CHAFA_PIXEL_BGR8: Packed BGR (no alpha), 8 bits per channel. + * @CHAFA_PIXEL_MAX: Last supported pixel type, plus one. + * + * Pixel formats supported by #ChafaCanvas and #ChafaSymbolMap. + * + * Since: 1.4 + **/ + +typedef enum +{ + /* 32 bits per pixel */ + + CHAFA_PIXEL_RGBA8_PREMULTIPLIED, + CHAFA_PIXEL_BGRA8_PREMULTIPLIED, + CHAFA_PIXEL_ARGB8_PREMULTIPLIED, + CHAFA_PIXEL_ABGR8_PREMULTIPLIED, + + CHAFA_PIXEL_RGBA8_UNASSOCIATED, + CHAFA_PIXEL_BGRA8_UNASSOCIATED, + CHAFA_PIXEL_ARGB8_UNASSOCIATED, + CHAFA_PIXEL_ABGR8_UNASSOCIATED, + + /* 24 bits per pixel */ + + CHAFA_PIXEL_RGB8, + CHAFA_PIXEL_BGR8, + + CHAFA_PIXEL_MAX +} +ChafaPixelType; + +G_END_DECLS + +#endif /* __CHAFA_COMMON_H__ */ diff -Nru chafa-1.2.1/chafa/chafaconfig.h.in chafa-1.12.4/chafa/chafaconfig.h.in --- chafa-1.2.1/chafa/chafaconfig.h.in 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafaconfig.h.in 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,16 @@ +/* chafaconfig.h + * + * This is a generated file. Please modify 'chafaconfig.h.in'. */ + +#ifndef __CHAFACONFIG_H__ +#define __CHAFACONFIG_H__ + +G_BEGIN_DECLS + +#define CHAFA_MAJOR_VERSION @CHAFA_MAJOR_VERSION@ +#define CHAFA_MINOR_VERSION @CHAFA_MINOR_VERSION@ +#define CHAFA_MICRO_VERSION @CHAFA_MICRO_VERSION@ + +G_END_DECLS + +#endif /* __CHAFACONFIG_H__ */ diff -Nru chafa-1.2.1/chafa/chafa-features.c chafa-1.12.4/chafa/chafa-features.c --- chafa-1.2.1/chafa/chafa-features.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-features.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -19,8 +19,8 @@ #include "config.h" -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" +#include "chafa.h" +#include "internal/chafa-private.h" /** * SECTION:chafa-features @@ -46,6 +46,8 @@ static gboolean have_sse41; static gboolean have_popcnt; +static gint n_threads = -1; + static void init_features (void) { @@ -180,3 +182,56 @@ return g_string_free (features_gstr, FALSE); } + +/** + * chafa_get_n_threads: + * + * Queries the maximum number of worker threads to use for parallel processing. + * + * Returns: The number of threads, or -1 if determined automatically + **/ +gint +chafa_get_n_threads (void) +{ + return g_atomic_int_get (&n_threads); +} + +/** + * chafa_set_n_threads: + * @n: Number of threads + * + * Sets the maximum number of worker threads to use for parallel processing, + * or -1 to determine this automatically. The default is -1. + * + * Setting this to 0 or 1 will avoid using thread pools and instead perform + * all processing in the main thread. + **/ +void +chafa_set_n_threads (gint n) +{ + g_return_if_fail (n >= -1); + + return g_atomic_int_set (&n_threads, n); +} + +/** + * chafa_get_n_actual_threads: + * + * Queries the number of worker threads that will actually be used for + * parallel processing. + * + * Returns: Number of threads, always >= 1 + **/ +gint +chafa_get_n_actual_threads (void) +{ + gint n_threads; + + n_threads = chafa_get_n_threads (); + if (n_threads < 0) + n_threads = g_get_num_processors (); + if (n_threads <= 0) + n_threads = 1; + + return n_threads; +} diff -Nru chafa-1.2.1/chafa/chafa-features.h chafa-1.12.4/chafa/chafa-features.h --- chafa-1.2.1/chafa/chafa-features.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-features.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -43,6 +43,14 @@ CHAFA_AVAILABLE_IN_ALL gchar *chafa_describe_features (ChafaFeatures features); +CHAFA_AVAILABLE_IN_1_10 +gint chafa_get_n_threads (void); +CHAFA_AVAILABLE_IN_1_10 +void chafa_set_n_threads (gint n); + +CHAFA_AVAILABLE_IN_1_10 +gint chafa_get_n_actual_threads (void); + G_END_DECLS #endif /* __CHAFA_FEATURES_H__ */ diff -Nru chafa-1.2.1/chafa/chafa.h chafa-1.12.4/chafa/chafa.h --- chafa-1.2.1/chafa/chafa.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -28,10 +28,13 @@ /* Version macros go before everything else */ #include +#include #include #include #include #include +#include +#include #include G_END_DECLS diff -Nru chafa-1.2.1/chafa/chafa-mmx.c chafa-1.12.4/chafa/chafa-mmx.c --- chafa-1.2.1/chafa/chafa-mmx.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-mmx.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -#include "config.h" - -#include -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" - -void -calc_colors_mmx (const ChafaPixel *pixels, ChafaColor *cols, const guint8 *cov) -{ - __m64 *m64p0 = (__m64 *) pixels; - __m64 *cols_m64 = (__m64 *) cols; - gint i; - - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - __m64 *m64p1; - - m64p1 = cols_m64 + cov [i]; - *m64p1 = _mm_adds_pi16 (*m64p1, m64p0 [i]); - } - -#if 0 - /* Called after outer loop is done */ - _mm_empty (); -#endif -} - -void -leave_mmx (void) -{ - _mm_empty (); -} diff -Nru chafa-1.2.1/chafa/chafa-popcnt.c chafa-1.12.4/chafa/chafa-popcnt.c --- chafa-1.2.1/chafa/chafa-popcnt.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-popcnt.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -#include "config.h" - -#include -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" - -gint -chafa_pop_count_u64_builtin (guint64 v) -{ -#if defined(HAVE_POPCNT64_INTRINSICS) - return (gint) _mm_popcnt_u64 (v); -#else /* HAVE_POPCNT32_INTRINSICS */ - __int32_t* w = (__int32_t*)&v; - return (gint) _mm_popcnt_u32(w[0]) + _mm_popcnt_u32(w[1]); -#endif -} - -void -chafa_pop_count_vu64_builtin (const guint64 *vv, gint *vc, gint n) -{ - while (n--) - { -#if defined(HAVE_POPCNT64_INTRINSICS) - *(vc++) = _mm_popcnt_u64 (*(vv++)); -#else /* HAVE_POPCNT32_INTRINSICS */ - __int32_t* w = (__int32_t*)vv; - *(vc++) = _mm_popcnt_u32(w[0]) + _mm_popcnt_u32(w[1]); - vv++; -#endif - } -} - -void -chafa_hamming_distance_vu64_builtin (guint64 a, const guint64 *vb, gint *vc, gint n) -{ -#if defined(HAVE_POPCNT64_INTRINSICS) - while (n >= 4) - { - n -= 4; - *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); - *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); - *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); - *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); - } - - while (n--) { - *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); - } -#else /* HAVE_POPCNT32_INTRINSICS */ - __int32_t* aa = (__int32_t*)&a; - __int32_t* wb = (__int32_t*)vb; - while (n--) { - *(vc++) = _mm_popcnt_u32(aa[0]^wb[0]) + _mm_popcnt_u32(aa[1]^wb[1]); - wb += 2; - } -#endif -} diff -Nru chafa-1.2.1/chafa/chafa-private.h chafa-1.12.4/chafa/chafa-private.h --- chafa-1.2.1/chafa/chafa-private.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-private.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,283 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -#ifndef __CHAFA_PRIVATE_H__ -#define __CHAFA_PRIVATE_H__ - -#include - -G_BEGIN_DECLS - -/* Colors and color spaces */ - -#define CHAFA_PALETTE_INDEX_TRANSPARENT 256 -#define CHAFA_PALETTE_INDEX_FG 257 -#define CHAFA_PALETTE_INDEX_BG 258 - -/* Color space agnostic, using fixed point */ -typedef struct -{ - gint16 ch [4]; -} -ChafaColor; - -typedef struct -{ - ChafaColor col; -} -ChafaPixel; - -typedef struct -{ - ChafaColor col [CHAFA_COLOR_SPACE_MAX]; -} -ChafaPaletteColor; - -/* Character symbols and symbol classes */ - -#define CHAFA_N_SYMBOLS_MAX 1024 /* For static temp arrays */ -#define CHAFA_SYMBOL_N_PIXELS (CHAFA_SYMBOL_WIDTH_PIXELS * CHAFA_SYMBOL_HEIGHT_PIXELS) - -typedef struct -{ - ChafaSymbolTags sc; - gunichar c; - gchar *coverage; - gint fg_weight, bg_weight; - guint64 bitmap; - gint popcount; -} -ChafaSymbol; - -struct ChafaSymbolMap -{ - gint refs; - - guint need_rebuild : 1; - GHashTable *desired_symbols; - - /* Populated by chafa_symbol_map_prepare () */ - guint64 *packed_bitmaps; - ChafaSymbol *symbols; - gint n_symbols; -}; - -/* Symbol selection candidate */ - -typedef struct -{ - gint16 symbol_index; - guint8 hamming_distance; - guint8 is_inverted; -} -ChafaCandidate; - -/* Color selection candidate pair */ - -typedef struct -{ - gint16 index [2]; - gint error [2]; -} -ChafaColorCandidates; - -/* Canvas config */ - -struct ChafaCanvasConfig -{ - gint refs; - - gint width, height; - ChafaCanvasMode canvas_mode; - ChafaColorSpace color_space; - ChafaDitherMode dither_mode; - gint dither_grain_width, dither_grain_height; - gfloat dither_intensity; - guint32 fg_color_packed_rgb; - guint32 bg_color_packed_rgb; - gint alpha_threshold; /* 0-255. 255 = no alpha in output */ - gfloat work_factor; - ChafaSymbolMap symbol_map; - ChafaSymbolMap fill_symbol_map; - guint preprocessing_enabled : 1; -}; - -/* Canvas */ - -typedef struct ChafaCanvasCell ChafaCanvasCell; - -/* Library functions */ - -extern ChafaSymbol *chafa_symbols; - -void chafa_init_palette (void); -void chafa_init_symbols (void); - -void chafa_init (void); -gboolean chafa_have_mmx (void) G_GNUC_PURE; -gboolean chafa_have_sse41 (void) G_GNUC_PURE; -gboolean chafa_have_popcnt (void) G_GNUC_PURE; - -void chafa_symbol_map_init (ChafaSymbolMap *symbol_map); -void chafa_symbol_map_deinit (ChafaSymbolMap *symbol_map); -void chafa_symbol_map_copy_contents (ChafaSymbolMap *dest, const ChafaSymbolMap *src); -void chafa_symbol_map_prepare (ChafaSymbolMap *symbol_map); -gboolean chafa_symbol_map_has_symbol (const ChafaSymbolMap *symbol_map, gunichar symbol); -void chafa_symbol_map_find_candidates (const ChafaSymbolMap *symbol_map, - guint64 bitmap, - gboolean do_inverse, - ChafaCandidate *candidates_out, - gint *n_candidates_inout); -void chafa_symbol_map_find_fill_candidates (const ChafaSymbolMap *symbol_map, - gint popcount, - gboolean do_inverse, - ChafaCandidate *candidates_out, - gint *n_candidates_inout); - -void chafa_canvas_config_init (ChafaCanvasConfig *canvas_config); -void chafa_canvas_config_deinit (ChafaCanvasConfig *canvas_config); -void chafa_canvas_config_copy_contents (ChafaCanvasConfig *dest, const ChafaCanvasConfig *src); - -gint *chafa_gen_bayer_matrix (gint matrix_size, gdouble magnitude); - -/* Colors */ - -guint32 chafa_pack_color (const ChafaColor *color) G_GNUC_PURE; -void chafa_unpack_color (guint32 packed, ChafaColor *color_out); - -#define chafa_color_add(d, s) \ -G_STMT_START { \ - (d)->ch [0] += (s)->ch [0]; (d)->ch [1] += (s)->ch [1]; (d)->ch [2] += (s)->ch [2]; (d)->ch [3] += (s)->ch [3]; \ -} G_STMT_END - -#define chafa_color_diff_fast(col_a, col_b) \ -(((col_b)->ch [0] - (col_a)->ch [0]) * ((col_b)->ch [0] - (col_a)->ch [0]) \ - + ((col_b)->ch [1] - (col_a)->ch [1]) * ((col_b)->ch [1] - (col_a)->ch [1]) \ - + ((col_b)->ch [2] - (col_a)->ch [2]) * ((col_b)->ch [2] - (col_a)->ch [2])) - -/* Required to get alpha right */ -gint chafa_color_diff_slow (const ChafaColor *col_a, const ChafaColor *col_b, ChafaColorSpace color_space) G_GNUC_PURE; - -void chafa_color_div_scalar (ChafaColor *color, gint scalar); - -void chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99); - -/* Ratio is in the range 0-1000 */ -void chafa_color_mix (ChafaColor *out, const ChafaColor *a, const ChafaColor *b, gint ratio); - -/* Takes values 0-255 for r, g, b and returns a universal palette index 0-255 */ -void chafa_pick_color_256 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); - -/* Takes values 0-255 for r, g, b and returns a universal palette index 16-255 */ -void chafa_pick_color_240 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); - -/* Takes values 0-255 for r, g, b and returns a universal palette index 0-15 */ -void chafa_pick_color_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); - -/* Takes values 0-255 for r, g, b and returns CHAFA_PALETTE_INDEX_FG or CHAFA_PALETTE_INDEX_BG */ -void chafa_pick_color_fgbg (const ChafaColor *color, ChafaColorSpace color_space, - const ChafaColor *fg_color, const ChafaColor *bg_color, - ChafaColorCandidates *candidates); - -const ChafaColor *chafa_get_palette_color_256 (guint index, ChafaColorSpace color_space) G_GNUC_CONST; - -#ifdef HAVE_MMX_INTRINSICS -void calc_colors_mmx (const ChafaPixel *pixels, ChafaColor *cols, const guint8 *cov); -void leave_mmx (void); -#endif - -#ifdef HAVE_SSE41_INTRINSICS -gint calc_error_sse41 (const ChafaPixel *pixels, const ChafaColor *cols, const guint8 *cov) G_GNUC_PURE; -#endif - -#if defined(HAVE_POPCNT64_INTRINSICS) || defined(HAVE_POPCNT32_INTRINSICS) -#define HAVE_POPCNT_INTRINSICS -#endif - -#ifdef HAVE_POPCNT_INTRINSICS -gint chafa_pop_count_u64_builtin (guint64 v) G_GNUC_PURE; -void chafa_pop_count_vu64_builtin (const guint64 *vv, gint *vc, gint n); -void chafa_hamming_distance_vu64_builtin (guint64 a, const guint64 *vb, gint *vc, gint n); -#endif - -/* Inline functions */ - -static inline guint64 chafa_slow_pop_count (guint64 v) G_GNUC_UNUSED; -static inline gint chafa_population_count_u64 (guint64 v) G_GNUC_UNUSED; -static inline void chafa_population_count_vu64 (const guint64 *vv, gint *vc, gint n) G_GNUC_UNUSED; - -static inline guint64 -chafa_slow_pop_count (guint64 v) -{ - /* Generic population count from - * http://www.graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel - * - * Peter Kankowski has more hacks, including better SIMD versions, at - * https://www.strchr.com/crc32_popcnt */ - - v = v - ((v >> 1) & (guint64) ~(guint64) 0 / 3); - v = (v & (guint64) ~(guint64) 0 / 15 * 3) + ((v >> 2) & (guint64) ~(guint64) 0 / 15 * 3); - v = (v + (v >> 4)) & (guint64) ~(guint64) 0 / 255 * 15; - return (guint64) (v * ((guint64) ~(guint64) 0 / 255)) >> (sizeof (guint64) - 1) * 8; -} - -static inline gint -chafa_population_count_u64 (guint64 v) -{ -#ifdef HAVE_POPCNT_INTRINSICS - if (chafa_have_popcnt ()) - return chafa_pop_count_u64_builtin (v); -#endif - - return chafa_slow_pop_count (v); -} - -static inline void -chafa_population_count_vu64 (const guint64 *vv, gint *vc, gint n) -{ -#ifdef HAVE_POPCNT_INTRINSICS - if (chafa_have_popcnt ()) - { - chafa_pop_count_vu64_builtin (vv, vc, n); - return; - } -#endif - - while (n--) - *(vc++) = chafa_slow_pop_count (*(vv++)); -} - -static inline void -chafa_hamming_distance_vu64 (guint64 a, const guint64 *vb, gint *vc, gint n) -{ -#ifdef HAVE_POPCNT_INTRINSICS - if (chafa_have_popcnt ()) - { - chafa_hamming_distance_vu64_builtin (a, vb, vc, n); - return; - } -#endif - - while (n--) - *(vc++) = chafa_slow_pop_count (a ^ *(vb++)); -} - -G_END_DECLS - -#endif /* __CHAFA_PRIVATE_H__ */ diff -Nru chafa-1.2.1/chafa/chafa-sse41.c chafa-1.12.4/chafa/chafa-sse41.c --- chafa-1.2.1/chafa/chafa-sse41.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-sse41.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -#include "config.h" - -#include -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" - -gint -calc_error_sse41 (const ChafaPixel *pixels, const ChafaColor *cols, const guint8 *cov) -{ - __m64 *m64p0 = (__m64 *) pixels; - __m64 *m64p1 = (__m64 *) cols; - __m128i err4 = { 0 }; - const gint32 *e = (gint32 *) &err4; - gint i; - - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - __m128i t0, t1, t; - - t0 = _mm_cvtepi16_epi32 (_mm_loadl_epi64 ((__m128i *) &m64p0 [i])); - t1 = _mm_cvtepi16_epi32 (_mm_loadl_epi64 ((__m128i *) &m64p1 [cov [i]])); - - t = t0 - t1; - t = _mm_mullo_epi32 (t, t); - err4 += t; - } - - return e [0] + e [1] + e [2]; -} diff -Nru chafa-1.2.1/chafa/chafa-symbol-map.c chafa-1.12.4/chafa/chafa-symbol-map.c --- chafa-1.2.1/chafa/chafa-symbol-map.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-symbol-map.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -21,12 +21,48 @@ #include /* memset, memcpy */ #include /* qsort */ -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" +#include "chafa.h" +#include "internal/chafa-private.h" +#include "internal/smolscale/smolscale.h" /* Max number of candidates to return from chafa_symbol_map_find_candidates() */ #define N_CANDIDATES_MAX 8 +typedef enum +{ + SELECTOR_TAG, + SELECTOR_RANGE +} +SelectorType; + +typedef struct +{ + guint selector_type : 1; + guint additive : 1; + + ChafaSymbolTags tags; + + /* First and last code points are inclusive */ + gunichar first_code_point; + gunichar last_code_point; +} +Selector; + +typedef struct +{ + gunichar c; + guint64 bitmap; +} +Glyph; + +/* Double-width glyphs */ +typedef struct +{ + gunichar c; + guint64 bitmap [2]; +} +Glyph2; + /** * CHAFA_SYMBOL_WIDTH_PIXELS: * @@ -62,7 +98,19 @@ * @CHAFA_SYMBOL_TAG_TECHNICAL: Miscellaneous technical symbols. * @CHAFA_SYMBOL_TAG_GEOMETRIC: Geometric shapes. * @CHAFA_SYMBOL_TAG_ASCII: Printable ASCII characters. + * @CHAFA_SYMBOL_TAG_ALPHA: Letters. + * @CHAFA_SYMBOL_TAG_DIGIT: Digits. + * @CHAFA_SYMBOL_TAG_ALNUM: Joint set of letters and digits. + * @CHAFA_SYMBOL_TAG_NARROW: Characters that are one cell wide. + * @CHAFA_SYMBOL_TAG_WIDE: Characters that are two cells wide. + * @CHAFA_SYMBOL_TAG_AMBIGUOUS: Characters of uncertain width. Always excluded unless specifically asked for. + * @CHAFA_SYMBOL_TAG_UGLY: Characters that are generally undesired or unlikely to render well. Always excluded unless specifically asked for. + * @CHAFA_SYMBOL_TAG_LEGACY: Legacy computer symbols, including sextants, wedges and more. + * @CHAFA_SYMBOL_TAG_SEXTANT: Sextant 2x3 mosaics. + * @CHAFA_SYMBOL_TAG_WEDGE: Wedge shapes that align with sextants. + * @CHAFA_SYMBOL_TAG_LATIN: Latin and Latin-like symbols (superset of ASCII). * @CHAFA_SYMBOL_TAG_EXTRA: Symbols not in any other category. + * @CHAFA_SYMBOL_TAG_BAD: Joint set of ugly and ambiguous characters. Always excluded unless specifically asked for. * @CHAFA_SYMBOL_TAG_ALL: Special value meaning all supported symbols. **/ @@ -104,6 +152,178 @@ } #endif +static guint8 * +bitmap_to_bytes (guint64 bitmap) +{ + guint8 *cov = g_malloc0 (CHAFA_SYMBOL_N_PIXELS); + gint i; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + cov [i] = (bitmap >> (CHAFA_SYMBOL_N_PIXELS - 1 - i)) & 1; + } + + return cov; +} + +/* Input format must always be RGBA8. old_format is just an indicator of how + * the channel values are to be extracted. */ +static void +pixels_to_coverage (const guint8 *pixels_in, ChafaPixelType old_format, guint8 *pixels_out, + gint n_pixels) +{ + gint i; + + if (old_format == CHAFA_PIXEL_RGB8 || old_format == CHAFA_PIXEL_BGR8) + { + for (i = 0; i < n_pixels; i++) + pixels_out [i] = (pixels_in [i * 4] + pixels_in [i * 4 + 1] + pixels_in [i * 4 + 2]) / 3; + } + else + { + for (i = 0; i < n_pixels; i++) + pixels_out [i] = pixels_in [i * 4 + 3]; + } +} + +static void +sharpen_coverage (const guint8 *cov_in, guint8 *cov_out, gint width, gint height) +{ + gint k [3] [3] = + { + /* Sharpen + boost contrast */ + { 0, -1, 0 }, + { -1, 6, -1 }, + { 0, -1, 0 } + }; + gint x, y; + gint i, j; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + gint sum = 0; + + for (i = 0; i < 3; i++) + { + for (j = 0; j < 3; j++) + { + gint a = x + i - 1, b = y + j - 1; + + /* At edges, just clone the border pixels outwards */ + a = CLAMP (a, 0, width - 1); + b = CLAMP (b, 0, height - 1); + + sum += (gint) cov_in [a + b * width] * k [i] [j]; + } + } + + cov_out [x + y * width] = CLAMP (sum, 0, 255); + } + } +} + +static guint64 +coverage_to_bitmap (const guint8 *cov, gint rowstride) +{ + guint64 bitmap = 0; + gint x, y; + + for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) + { + for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) + { + bitmap <<= 1; + if (cov [y * rowstride + x] > 127) + bitmap |= 1; + } + } + + return bitmap; +} + +static void +bitmap_to_argb (guint64 bitmap, guint8 *argb, gint rowstride) +{ + guint8 *p; + gint x, y; + + for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) + { + p = argb + y * rowstride; + + for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) + { + guint32 *p32 = (guint32 *) p; + + /* Set = 0xffffffff, clear = 0x00000000 */ + *p32 = (-(guint32) ((bitmap >> 63) & 1)); + p += 4; + bitmap <<= 1; + } + } +} + +static guint64 +glyph_to_bitmap (gint width, gint height, + gint rowstride, + ChafaPixelType pixel_format, + gpointer pixels) +{ + guint8 scaled_pixels [CHAFA_SYMBOL_N_PIXELS * 4]; + guint8 cov [CHAFA_SYMBOL_N_PIXELS]; + guint8 sharpened_cov [CHAFA_SYMBOL_N_PIXELS]; + guint64 bitmap; + + /* Scale to cell dimensions */ + + smol_scale_simple ((SmolPixelType) pixel_format, pixels, width, height, rowstride, + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + (gpointer) scaled_pixels, + CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS, + CHAFA_SYMBOL_WIDTH_PIXELS * 4); + + /* Generate coverage map */ + + pixels_to_coverage (scaled_pixels, pixel_format, cov, CHAFA_SYMBOL_N_PIXELS); + sharpen_coverage (cov, sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS); + bitmap = coverage_to_bitmap (sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS); + + return bitmap; +} + +static void +glyph_to_bitmap_wide (gint width, gint height, + gint rowstride, + ChafaPixelType pixel_format, + gpointer pixels, + guint64 *left_bitmap_out, + guint64 *right_bitmap_out) +{ + guint8 scaled_pixels [CHAFA_SYMBOL_N_PIXELS * 2 * 4]; + guint8 cov [CHAFA_SYMBOL_N_PIXELS * 2]; + guint8 sharpened_cov [CHAFA_SYMBOL_N_PIXELS * 2]; + + /* Scale to cell dimensions */ + + smol_scale_simple ((SmolPixelType) pixel_format, pixels, width, height, rowstride, + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + (gpointer) scaled_pixels, + CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS, + CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2); + + /* Generate coverage map */ + + pixels_to_coverage (scaled_pixels, pixel_format, cov, CHAFA_SYMBOL_N_PIXELS * 2); + sharpen_coverage (cov, sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS); + + *left_bitmap_out = coverage_to_bitmap (sharpened_cov, + CHAFA_SYMBOL_WIDTH_PIXELS * 2); + *right_bitmap_out = coverage_to_bitmap (sharpened_cov + CHAFA_SYMBOL_WIDTH_PIXELS, + CHAFA_SYMBOL_WIDTH_PIXELS * 2); +} + static gint compare_symbols_popcount (const void *a, const void *b) { @@ -117,86 +337,450 @@ return 0; } +static gint +compare_symbols2_popcount (const void *a, const void *b) +{ + const ChafaSymbol2 *a_sym = a; + const ChafaSymbol2 *b_sym = b; + + if (a_sym->sym [0].popcount + a_sym->sym [1].popcount + < b_sym->sym [0].popcount + b_sym->sym [1].popcount) + return -1; + if (a_sym->sym [0].popcount + a_sym->sym [1].popcount + > b_sym->sym [0].popcount + b_sym->sym [1].popcount) + return 1; + return 0; +} + static void -rebuild_symbols (ChafaSymbolMap *symbol_map) +compile_symbols (ChafaSymbolMap *symbol_map, GHashTable *desired_symbols) { + GHashTableIter iter; + gpointer key, value; gint i; + for (i = 0; i < symbol_map->n_symbols; i++) + g_free (symbol_map->symbols [i].coverage); + g_free (symbol_map->symbols); g_free (symbol_map->packed_bitmaps); - symbol_map->n_symbols = symbol_map->desired_symbols ? g_hash_table_size (symbol_map->desired_symbols) : 0; + symbol_map->n_symbols = g_hash_table_size (desired_symbols); symbol_map->symbols = g_new (ChafaSymbol, symbol_map->n_symbols + 1); - if (symbol_map->desired_symbols) - { - GHashTableIter iter; - gpointer key, value; - gint i; - - g_hash_table_iter_init (&iter, symbol_map->desired_symbols); - i = 0; - - while (g_hash_table_iter_next (&iter, &key, &value)) - { - gint src_index = GPOINTER_TO_INT (key); - symbol_map->symbols [i++] = chafa_symbols [src_index]; - } + g_hash_table_iter_init (&iter, desired_symbols); + i = 0; - qsort (symbol_map->symbols, symbol_map->n_symbols, sizeof (ChafaSymbol), - compare_symbols_popcount); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + ChafaSymbol *sym = value; + symbol_map->symbols [i] = *sym; + symbol_map->symbols [i].coverage = g_memdup (symbol_map->symbols [i].coverage, + CHAFA_SYMBOL_N_PIXELS); + i++; } + qsort (symbol_map->symbols, symbol_map->n_symbols, sizeof (ChafaSymbol), + compare_symbols_popcount); + /* Clear sentinel */ memset (&symbol_map->symbols [symbol_map->n_symbols], 0, sizeof (ChafaSymbol)); symbol_map->packed_bitmaps = g_new (guint64, symbol_map->n_symbols); for (i = 0; i < symbol_map->n_symbols; i++) symbol_map->packed_bitmaps [i] = symbol_map->symbols [i].bitmap; +} + +static void +compile_symbols_wide (ChafaSymbolMap *symbol_map, GHashTable *desired_symbols) +{ + GHashTableIter iter; + gpointer key, value; + gint i; + + for (i = 0; i < symbol_map->n_symbols2; i++) + { + g_free (symbol_map->symbols2 [i].sym [0].coverage); + g_free (symbol_map->symbols2 [i].sym [1].coverage); + } + + g_free (symbol_map->symbols2); + + symbol_map->n_symbols2 = g_hash_table_size (desired_symbols); + symbol_map->symbols2 = g_new (ChafaSymbol2, symbol_map->n_symbols2 + 1); + + g_hash_table_iter_init (&iter, desired_symbols); + i = 0; + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + ChafaSymbol2 *sym = value; + symbol_map->symbols2 [i] = *sym; + symbol_map->symbols2 [i].sym [0].coverage = g_memdup (symbol_map->symbols2 [i].sym [0].coverage, + CHAFA_SYMBOL_N_PIXELS); + symbol_map->symbols2 [i].sym [1].coverage = g_memdup (symbol_map->symbols2 [i].sym [1].coverage, + CHAFA_SYMBOL_N_PIXELS); + i++; + } + + qsort (symbol_map->symbols2, symbol_map->n_symbols2, sizeof (ChafaSymbol2), + compare_symbols2_popcount); + + /* Clear sentinel */ + memset (&symbol_map->symbols2 [symbol_map->n_symbols2], 0, sizeof (ChafaSymbol2)); + + symbol_map->packed_bitmaps2 = g_new (guint64, symbol_map->n_symbols2 * 2); + for (i = 0; i < symbol_map->n_symbols2; i++) + { + symbol_map->packed_bitmaps2 [i * 2] = symbol_map->symbols2 [i].sym [0].bitmap; + symbol_map->packed_bitmaps2 [i * 2 + 1] = symbol_map->symbols2 [i].sym [1].bitmap; + } +} + +static gboolean +char_is_selected (GArray *selectors, ChafaSymbolTags tags, gunichar c) +{ + ChafaSymbolTags auto_exclude_tags = CHAFA_SYMBOL_TAG_BAD; + GUnicodeScript script; + gboolean is_selected = FALSE; + gint i; + + /* Always exclude characters that would mangle the output */ + if (!g_unichar_isprint (c) || g_unichar_iszerowidth (c) + || c == '\t') + return FALSE; + + /* We don't support RTL, so RTL characters will break the output. + * + * Ideally we'd exclude the R and AL bidi classes, but unfortunately we don't + * have a convenient API available to us to determine the bidi class of a + * character. So we just exclude a few scripts and hope for the best. + * + * A better implementation could extract directionality from the Unicode DB: + * + * https://www.unicode.org/reports/tr9/#Bidirectional_Character_Types + * https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt */ + script = g_unichar_get_script (c); + if (script == G_UNICODE_SCRIPT_ARABIC + || script == G_UNICODE_SCRIPT_HEBREW + || script == G_UNICODE_SCRIPT_THAANA + || script == G_UNICODE_SCRIPT_SYRIAC) + return FALSE; + + for (i = 0; i < (gint) selectors->len; i++) + { + const Selector *selector = &g_array_index (selectors, Selector, i); + + switch (selector->selector_type) + { + case SELECTOR_TAG: + if (tags & selector->tags) + { + is_selected = selector->additive ? TRUE : FALSE; + + /* We exclude "bad" symbols unless the user explicitly refers + * to them by tag. I.e. the selector string "0..fffff" will not + * include matches for "ugly", but "-ugly+0..fffff" will. */ + auto_exclude_tags &= ~((guint) selector->tags); + } + break; + + case SELECTOR_RANGE: + if (c >= selector->first_code_point && c <= selector->last_code_point) + is_selected = selector->additive ? TRUE : FALSE; + break; + } + } + + if (tags & auto_exclude_tags) + is_selected = FALSE; + + return is_selected; +} + +static void +free_symbol (gpointer sym_p) +{ + ChafaSymbol *sym = sym_p; + + g_free (sym->coverage); + g_free (sym); +} + +static void +free_symbol_wide (gpointer sym_p) +{ + ChafaSymbol2 *sym = sym_p; + + g_free (sym->sym [0].coverage); + g_free (sym->sym [1].coverage); + g_free (sym); +} + +static void +rebuild_symbols (ChafaSymbolMap *symbol_map) +{ + GHashTable *desired_syms; + GHashTable *desired_syms_wide; + GHashTableIter iter; + gpointer key, value; + gint i; + + desired_syms = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, free_symbol); + desired_syms_wide = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, free_symbol_wide); + + /* Pick built-in symbols */ + + if (symbol_map->use_builtin_glyphs) + { + for (i = 0; chafa_symbols [i].c != 0; i++) + { + if (char_is_selected (symbol_map->selectors, + chafa_symbols [i].sc, + chafa_symbols [i].c)) + { + ChafaSymbol *sym = g_new (ChafaSymbol, 1); + + *sym = chafa_symbols [i]; + sym->coverage = g_memdup (sym->coverage, CHAFA_SYMBOL_N_PIXELS); + g_hash_table_replace (desired_syms, + GUINT_TO_POINTER (chafa_symbols [i].c), + sym); + } + } + } + + /* Pick built-in symbols (wide) */ + + if (symbol_map->use_builtin_glyphs) + { + for (i = 0; chafa_symbols2 [i].sym [0].c != 0; i++) + { + if (char_is_selected (symbol_map->selectors, + chafa_symbols2 [i].sym [0].sc, + chafa_symbols2 [i].sym [0].c)) + { + ChafaSymbol2 *sym = g_new (ChafaSymbol2, 1); + + *sym = chafa_symbols2 [i]; + sym->sym [0].coverage = g_memdup (sym->sym [0].coverage, CHAFA_SYMBOL_N_PIXELS); + sym->sym [1].coverage = g_memdup (sym->sym [1].coverage, CHAFA_SYMBOL_N_PIXELS); + g_hash_table_replace (desired_syms_wide, + GUINT_TO_POINTER (chafa_symbols2 [i].sym [0].c), + sym); + } + } + } + + /* Pick user glyph symbols */ + + g_hash_table_iter_init (&iter, symbol_map->glyphs); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + Glyph *glyph = value; + ChafaSymbolTags tags = chafa_get_tags_for_char (glyph->c); + + if (char_is_selected (symbol_map->selectors, tags, glyph->c)) + { + ChafaSymbol *sym = g_new0 (ChafaSymbol, 1); + + sym->sc = tags; + sym->c = glyph->c; + sym->bitmap = glyph->bitmap; + sym->coverage = (gchar *) bitmap_to_bytes (glyph->bitmap); + sym->popcount = chafa_population_count_u64 (glyph->bitmap); + sym->fg_weight = sym->popcount; + sym->bg_weight = CHAFA_SYMBOL_N_PIXELS - sym->popcount; + + g_hash_table_replace (desired_syms, GUINT_TO_POINTER (glyph->c), sym); + } + } + + compile_symbols (symbol_map, desired_syms); + g_hash_table_destroy (desired_syms); + + /* Pick user glyph symbols (wide) */ + + g_hash_table_iter_init (&iter, symbol_map->glyphs2); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + Glyph2 *glyph = value; + ChafaSymbolTags tags = chafa_get_tags_for_char (glyph->c); + + if (char_is_selected (symbol_map->selectors, tags, glyph->c)) + { + ChafaSymbol2 *sym = g_new0 (ChafaSymbol2, 1); + + sym->sym [0].sc = tags; + sym->sym [0].c = glyph->c; + sym->sym [0].bitmap = glyph->bitmap [0]; + sym->sym [0].coverage = (gchar *) bitmap_to_bytes (glyph->bitmap [0]); + sym->sym [0].popcount = chafa_population_count_u64 (glyph->bitmap [0]); + sym->sym [0].fg_weight = sym->sym [0].popcount; + sym->sym [0].bg_weight = CHAFA_SYMBOL_N_PIXELS - sym->sym [0].popcount; + + sym->sym [1].sc = tags; + sym->sym [1].c = glyph->c; + sym->sym [1].bitmap = glyph->bitmap [1]; + sym->sym [1].coverage = (gchar *) bitmap_to_bytes (glyph->bitmap [1]); + sym->sym [1].popcount = chafa_population_count_u64 (glyph->bitmap [1]); + sym->sym [1].fg_weight = sym->sym [1].popcount; + sym->sym [1].bg_weight = CHAFA_SYMBOL_N_PIXELS - sym->sym [1].popcount; + + g_hash_table_replace (desired_syms_wide, GUINT_TO_POINTER (glyph->c), sym); + } + } + + compile_symbols_wide (symbol_map, desired_syms_wide); + g_hash_table_destroy (desired_syms_wide); symbol_map->need_rebuild = FALSE; } static GHashTable * -copy_hash_table (GHashTable *src) +copy_glyph_table (GHashTable *src) { GHashTable *dest; GHashTableIter iter; gpointer key, value; - dest = g_hash_table_new (g_direct_hash, g_direct_equal); + dest = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); g_hash_table_iter_init (&iter, src); while (g_hash_table_iter_next (&iter, &key, &value)) { - g_hash_table_insert (dest, key, value); + g_hash_table_insert (dest, key, g_memdup (value, sizeof (Glyph))); } return dest; } -static void -add_by_tags (GHashTable *sym_ht, ChafaSymbolTags tags) +static GHashTable * +copy_glyph2_table (GHashTable *src) +{ + GHashTable *dest; + GHashTableIter iter; + gpointer key, value; + + dest = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + + g_hash_table_iter_init (&iter, src); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + g_hash_table_insert (dest, key, g_memdup (value, sizeof (Glyph2))); + } + + return dest; +} + +static GArray * +copy_selector_array (GArray *src) { + GArray *dest; gint i; - for (i = 0; chafa_symbols [i].c != 0; i++) + dest = g_array_new (FALSE, FALSE, sizeof (Selector)); + + for (i = 0; i < (gint) src->len; i++) { - if (chafa_symbols [i].sc & tags) - g_hash_table_add (sym_ht, GINT_TO_POINTER (i)); + const Selector *s = &g_array_index (src, Selector, i); + g_array_append_val (dest, *s); } + + return dest; } static void -remove_by_tags (GHashTable *sym_ht, ChafaSymbolTags tags) +add_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags) { - gint i; + Selector s = { 0 }; + + s.selector_type = SELECTOR_TAG; + s.additive = TRUE; + s.tags = tags; + + g_array_append_val (symbol_map->selectors, s); +} + +static void +remove_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags) +{ + Selector s = { 0 }; + + s.selector_type = SELECTOR_TAG; + s.additive = FALSE; + s.tags = tags; + + g_array_append_val (symbol_map->selectors, s); +} + +static void +add_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) +{ + Selector s = { 0 }; + + s.selector_type = SELECTOR_RANGE; + s.additive = TRUE; + s.first_code_point = first; + s.last_code_point = last; + + g_array_append_val (symbol_map->selectors, s); +} + +static void +remove_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) +{ + Selector s = { 0 }; + + s.selector_type = SELECTOR_RANGE; + s.additive = FALSE; + s.first_code_point = first; + s.last_code_point = last; + + g_array_append_val (symbol_map->selectors, s); +} + +static gboolean +parse_code_point (const gchar *str, gint len, gint *parsed_len_out, gunichar *c_out) +{ + gint i = 0; + gunichar code = 0; + gboolean result = FALSE; - for (i = 0; chafa_symbols [i].c != 0; i++) + if (len >= 1 && (str [0] == 'u' || str [0] == 'U')) + i++; + + if (len >= 2 && str [0] == '0' && str [1] == 'x') + i += 2; + + for ( ; i < len; i++) { - if (chafa_symbols [i].sc & tags) - g_hash_table_remove (sym_ht, GINT_TO_POINTER (i)); + gint c = (gint) str [i]; + + if (c - '0' >= 0 && c - '0' <= 9) + { + code *= 16; + code += c - '0'; + } + else if (c - 'a' >= 0 && c - 'a' <= 5) + { + code *= 16; + code += c - 'a' + 10; + } + else if (c - 'A' >= 0 && c - 'A' <= 5) + { + code *= 16; + code += c - 'A' + 10; + } + else + break; + + result = TRUE; } + + *parsed_len_out = i; + *c_out = code; + return result; } typedef struct @@ -207,7 +791,9 @@ SymMapping; static gboolean -parse_symbol_tag (const gchar *name, gint len, ChafaSymbolTags *sc_out, GError **error) +parse_symbol_tag (const gchar *name, gint len, SelectorType *sel_type_out, + ChafaSymbolTags *sc_out, gunichar *first_out, gunichar *last_out, + GError **error) { const SymMapping map [] = { @@ -226,23 +812,66 @@ { "vhalf", CHAFA_SYMBOL_TAG_VHALF }, { "inverted", CHAFA_SYMBOL_TAG_INVERTED }, { "braille", CHAFA_SYMBOL_TAG_BRAILLE }, + { "sextant", CHAFA_SYMBOL_TAG_SEXTANT }, + { "wedge", CHAFA_SYMBOL_TAG_WEDGE }, { "technical", CHAFA_SYMBOL_TAG_TECHNICAL }, { "geometric", CHAFA_SYMBOL_TAG_GEOMETRIC }, { "ascii", CHAFA_SYMBOL_TAG_ASCII }, + { "alpha", CHAFA_SYMBOL_TAG_ALPHA }, + { "digit", CHAFA_SYMBOL_TAG_DIGIT }, + { "narrow", CHAFA_SYMBOL_TAG_NARROW }, + { "wide", CHAFA_SYMBOL_TAG_WIDE }, + { "ambiguous", CHAFA_SYMBOL_TAG_AMBIGUOUS }, + { "ugly", CHAFA_SYMBOL_TAG_UGLY }, { "extra", CHAFA_SYMBOL_TAG_EXTRA }, + { "alnum", CHAFA_SYMBOL_TAG_ALNUM }, + { "bad", CHAFA_SYMBOL_TAG_BAD }, + { "legacy", CHAFA_SYMBOL_TAG_LEGACY }, + { "latin", CHAFA_SYMBOL_TAG_LATIN }, + { NULL, 0 } }; + gint parsed_len; gint i; + /* Tag? */ + for (i = 0; map [i].name; i++) { if (!g_ascii_strncasecmp (map [i].name, name, len)) { *sc_out = map [i].sc; + *sel_type_out = SELECTOR_TAG; return TRUE; } } + /* Range? */ + + if (!parse_code_point (name, len, &parsed_len, first_out)) + goto err; + + if (len - parsed_len > 0) + { + gint parsed_last_len; + + if (len - parsed_len < 3 + || name [parsed_len] != '.' || name [parsed_len + 1] != '.' + || !parse_code_point (name + parsed_len + 2, len - parsed_len - 2, + &parsed_last_len, last_out) + || parsed_len + 2 + parsed_last_len != len) + goto err; + } + else + { + *last_out = *first_out; + } + + *sel_type_out = SELECTOR_RANGE; + return TRUE; + +err: + /* Bad input */ g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unrecognized symbol tag '%.*s'.", len, name); @@ -254,12 +883,13 @@ { const gchar *p0 = selectors; gboolean is_add = FALSE, is_remove = FALSE; - GHashTable *new_syms = NULL; gboolean result = FALSE; while (*p0) { + SelectorType sel_type; ChafaSymbolTags sc; + gunichar first, last; gint n; p0 += strspn (p0, " ,"); @@ -283,7 +913,7 @@ if (!*p0) break; - n = strspn (p0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + n = strspn (p0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."); if (!n) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, @@ -291,45 +921,38 @@ goto out; } - if (!parse_symbol_tag (p0, n, &sc, error)) + if (!parse_symbol_tag (p0, n, &sel_type, &sc, &first, &last, error)) goto out; p0 += n; - if (is_add) + if (!is_add && !is_remove) { - if (!new_syms) - new_syms = copy_hash_table (symbol_map->desired_symbols); - add_by_tags (new_syms, sc); + g_array_set_size (symbol_map->selectors, 0); + is_add = TRUE; } - else if (is_remove) + + if (sel_type == SELECTOR_TAG) { - if (!new_syms) - new_syms = copy_hash_table (symbol_map->desired_symbols); - remove_by_tags (new_syms, sc); + if (is_add) + add_by_tags (symbol_map, sc); + else if (is_remove) + remove_by_tags (symbol_map, sc); } else { - if (new_syms) - g_hash_table_unref (new_syms); - new_syms = g_hash_table_new (g_direct_hash, g_direct_equal); - add_by_tags (new_syms, sc); - is_add = TRUE; + if (is_add) + add_by_range (symbol_map, first, last); + else if (is_remove) + remove_by_range (symbol_map, first, last); } } - if (symbol_map->desired_symbols) - g_hash_table_unref (symbol_map->desired_symbols); - symbol_map->desired_symbols = new_syms; - new_syms = NULL; - symbol_map->need_rebuild = TRUE; result = TRUE; out: - if (new_syms) - g_hash_table_unref (new_syms); return result; } @@ -343,18 +966,35 @@ memset (symbol_map, 0, sizeof (*symbol_map)); symbol_map->refs = 1; + symbol_map->use_builtin_glyphs = TRUE; + symbol_map->glyphs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + symbol_map->glyphs2 = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + symbol_map->selectors = g_array_new (FALSE, FALSE, sizeof (Selector)); } void chafa_symbol_map_deinit (ChafaSymbolMap *symbol_map) { + gint i; + g_return_if_fail (symbol_map != NULL); - if (symbol_map->desired_symbols) - g_hash_table_unref (symbol_map->desired_symbols); + for (i = 0; i < symbol_map->n_symbols; i++) + g_free (symbol_map->symbols [i].coverage); + + for (i = 0; i < symbol_map->n_symbols2; i++) + { + g_free (symbol_map->symbols2 [i].sym [0].coverage); + g_free (symbol_map->symbols2 [i].sym [1].coverage); + } + g_hash_table_destroy (symbol_map->glyphs); + g_hash_table_destroy (symbol_map->glyphs2); + g_array_free (symbol_map->selectors, TRUE); g_free (symbol_map->symbols); + g_free (symbol_map->symbols2); g_free (symbol_map->packed_bitmaps); + g_free (symbol_map->packed_bitmaps2); } void @@ -365,11 +1005,13 @@ memcpy (dest, src, sizeof (*dest)); - if (dest->desired_symbols) - dest->desired_symbols = copy_hash_table (dest->desired_symbols); - + dest->glyphs = copy_glyph_table (dest->glyphs); + dest->glyphs2 = copy_glyph2_table (dest->glyphs2); + dest->selectors = copy_selector_array (dest->selectors); dest->symbols = NULL; + dest->symbols2 = NULL; dest->packed_bitmaps = NULL; + dest->packed_bitmaps2 = NULL; dest->need_rebuild = TRUE; dest->refs = 1; } @@ -383,7 +1025,6 @@ rebuild_symbols (symbol_map); } -/* FIXME: Use gunichars as keys in hash table instead, or use binary search here */ gboolean chafa_symbol_map_has_symbol (const ChafaSymbolMap *symbol_map, gunichar symbol) { @@ -391,14 +1032,22 @@ g_return_val_if_fail (symbol_map != NULL, FALSE); + /* FIXME: Use gunichars as keys in hash table instead */ + for (i = 0; i < symbol_map->n_symbols; i++) { const ChafaSymbol *sym = &symbol_map->symbols [i]; if (sym->c == symbol) return TRUE; - if (sym->c > symbol) - break; + } + + for (i = 0; i < symbol_map->n_symbols2; i++) + { + const ChafaSymbol2 *sym = &symbol_map->symbols2 [i]; + + if (sym->sym [0].c == symbol) + return TRUE; } return FALSE; @@ -443,11 +1092,13 @@ { 0, 65, FALSE }, { 0, 65, FALSE } }; - gint ham_dist [CHAFA_N_SYMBOLS_MAX]; + gint *ham_dist; gint i; g_return_if_fail (symbol_map != NULL); + ham_dist = g_new (gint, symbol_map->n_symbols + 1); + chafa_hamming_distance_vu64 (bitmap, symbol_map->packed_bitmaps, ham_dist, symbol_map->n_symbols); if (do_inverse) @@ -501,6 +1152,87 @@ i = *n_candidates_inout = MIN (i, *n_candidates_inout); memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); + + g_free (ham_dist); +} + +void +chafa_symbol_map_find_wide_candidates (const ChafaSymbolMap *symbol_map, const guint64 *bitmaps, + gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout) +{ + ChafaCandidate candidates [N_CANDIDATES_MAX] = + { + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE } + }; + gint *ham_dist; + gint i; + + g_return_if_fail (symbol_map != NULL); + + ham_dist = g_new (gint, symbol_map->n_symbols2 + 1); + + chafa_hamming_distance_2_vu64 (bitmaps, symbol_map->packed_bitmaps2, ham_dist, symbol_map->n_symbols2); + + if (do_inverse) + { + for (i = 0; i < symbol_map->n_symbols2; i++) + { + ChafaCandidate cand; + gint hd = ham_dist [i]; + + if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) + { + cand.symbol_index = i; + cand.hamming_distance = hd; + cand.is_inverted = FALSE; + insert_candidate (candidates, &cand); + } + + hd = 128 - hd; + + if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) + { + cand.symbol_index = i; + cand.hamming_distance = hd; + cand.is_inverted = TRUE; + insert_candidate (candidates, &cand); + } + } + } + else + { + for (i = 0; i < symbol_map->n_symbols2; i++) + { + ChafaCandidate cand; + gint hd = ham_dist [i]; + + if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) + { + cand.symbol_index = i; + cand.hamming_distance = hd; + cand.is_inverted = FALSE; + insert_candidate (candidates, &cand); + } + } + } + + for (i = 0; i < N_CANDIDATES_MAX; i++) + { + if (candidates [i].hamming_distance > 128) + break; + } + + i = *n_candidates_inout = MIN (i, *n_candidates_inout); + memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); + + g_free (ham_dist); } /* Assumes symbols are sorted by ascending popcount */ @@ -597,6 +1329,106 @@ memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); } +/* Assumes symbols are sorted by ascending popcount */ +static gint +find_closest_popcount_wide (const ChafaSymbolMap *symbol_map, gint popcount) +{ + gint i, j; + + g_assert (symbol_map->n_symbols2 > 0); + + i = 0; + j = symbol_map->n_symbols2 - 1; + + while (i < j) + { + gint k = (i + j + 1) / 2; + + if (popcount < symbol_map->symbols2 [k].sym [0].popcount + + symbol_map->symbols2 [k].sym [1].popcount) + j = k - 1; + else if (popcount >= symbol_map->symbols2 [k].sym [0].popcount + + symbol_map->symbols2 [k].sym [1].popcount) + i = k; + else + i = j = k; + } + + /* If we didn't find the exact popcount, the i+1'th element may be + * a closer match. */ + + if (i < symbol_map->n_symbols2 - 1 + && (abs (popcount - (symbol_map->symbols2 [i + 1].sym [0].popcount + + symbol_map->symbols2 [i + 1].sym [1].popcount)) + < abs (popcount - (symbol_map->symbols2 [i].sym [0].popcount + + symbol_map->symbols2 [i].sym [1].popcount)))) + { + i++; + } + + return i; +} + +/* Always returns zero or one candidates. We may want to do more in the future */ +void +chafa_symbol_map_find_wide_fill_candidates (const ChafaSymbolMap *symbol_map, gint popcount, + gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout) +{ + ChafaCandidate candidates [N_CANDIDATES_MAX] = + { + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE }, + { 0, 129, FALSE } + }; + gint sym, distance; + gint i; + + g_return_if_fail (symbol_map != NULL); + + if (!*n_candidates_inout) + return; + + if (symbol_map->n_symbols2 == 0) + { + *n_candidates_inout = 0; + return; + } + + sym = find_closest_popcount_wide (symbol_map, popcount); + candidates [0].symbol_index = sym; + candidates [0].hamming_distance = abs (popcount - (symbol_map->symbols2 [sym].sym [0].popcount + + symbol_map->symbols2 [sym].sym [1].popcount)); + candidates [0].is_inverted = FALSE; + + if (do_inverse && candidates [0].hamming_distance != 0) + { + sym = find_closest_popcount (symbol_map, 128 - popcount); + distance = abs (128 - popcount - (symbol_map->symbols2 [sym].sym [0].popcount + + symbol_map->symbols2 [sym].sym [1].popcount)); + + if (distance < candidates [0].hamming_distance) + { + candidates [0].symbol_index = sym; + candidates [0].hamming_distance = distance; + candidates [0].is_inverted = TRUE; + } + } + + for (i = 0; i < N_CANDIDATES_MAX; i++) + { + if (candidates [i].hamming_distance > 128) + break; + } + + i = *n_candidates_inout = MIN (i, *n_candidates_inout); + memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); +} + /* Public */ /** @@ -689,10 +1521,7 @@ g_return_if_fail (symbol_map != NULL); g_return_if_fail (symbol_map->refs > 0); - if (!symbol_map->desired_symbols) - symbol_map->desired_symbols = g_hash_table_new (g_direct_hash, g_direct_equal); - - add_by_tags (symbol_map->desired_symbols, tags); + add_by_tags (symbol_map, tags); symbol_map->need_rebuild = TRUE; } @@ -710,10 +1539,51 @@ g_return_if_fail (symbol_map != NULL); g_return_if_fail (symbol_map->refs > 0); - if (!symbol_map->desired_symbols) - symbol_map->desired_symbols = g_hash_table_new (g_direct_hash, g_direct_equal); + remove_by_tags (symbol_map, tags); + + symbol_map->need_rebuild = TRUE; +} + +/** + * chafa_symbol_map_add_by_range: + * @symbol_map: Symbol map to add symbols to + * @first: First code point to add, inclusive + * @last: Last code point to add, inclusive + * + * Adds symbols in the code point range starting with @first + * and ending with @last to @symbol_map. + * + * Since: 1.4 + **/ +void +chafa_symbol_map_add_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) +{ + g_return_if_fail (symbol_map != NULL); + g_return_if_fail (symbol_map->refs > 0); + + add_by_range (symbol_map, first, last); + + symbol_map->need_rebuild = TRUE; +} + +/** + * chafa_symbol_map_remove_by_range: + * @symbol_map: Symbol map to remove symbols from + * @first: First code point to remove, inclusive + * @last: Last code point to remove, inclusive + * + * Removes symbols in the code point range starting with @first + * and ending with @last from @symbol_map. + * + * Since: 1.4 + **/ +void +chafa_symbol_map_remove_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) +{ + g_return_if_fail (symbol_map != NULL); + g_return_if_fail (symbol_map->refs > 0); - remove_by_tags (symbol_map->desired_symbols, tags); + remove_by_range (symbol_map, first, last); symbol_map->need_rebuild = TRUE; } @@ -750,3 +1620,217 @@ return parse_selectors (symbol_map, selectors, error); } + +/* --- Glyphs --- */ + +/** + * chafa_symbol_map_get_allow_builtin_glyphs: + * @symbol_map: A symbol map + * + * Queries whether a symbol map is allowed to use built-in glyphs for + * symbol selection. This can be turned off if you want to use your + * own glyphs exclusively (see chafa_symbol_map_add_glyph()). + * + * Defaults to %TRUE. + * + * Returns: %TRUE if built-in glyphs are allowed + * + * Since: 1.4 + **/ +gboolean +chafa_symbol_map_get_allow_builtin_glyphs (ChafaSymbolMap *symbol_map) +{ + g_return_val_if_fail (symbol_map != NULL, FALSE); + + return symbol_map->use_builtin_glyphs; +} + +/** + * chafa_symbol_map_set_allow_builtin_glyphs: + * @symbol_map: A symbol map + * @use_builtin_glyphs: A boolean indicating whether to use built-in glyphs + * + * Controls whether a symbol map is allowed to use built-in glyphs for + * symbol selection. This can be turned off if you want to use your + * own glyphs exclusively (see chafa_symbol_map_add_glyph()). + * + * Defaults to %TRUE. + * + * Since: 1.4 + **/ +void +chafa_symbol_map_set_allow_builtin_glyphs (ChafaSymbolMap *symbol_map, + gboolean use_builtin_glyphs) +{ + g_return_if_fail (symbol_map != NULL); + + /* Avoid unnecessary rebuild */ + if (symbol_map->use_builtin_glyphs == use_builtin_glyphs) + return; + + symbol_map->use_builtin_glyphs = use_builtin_glyphs; + symbol_map->need_rebuild = TRUE; +} + +/** + * chafa_symbol_map_add_glyph: + * @symbol_map: A symbol map + * @code_point: The Unicode code point for this glyph + * @pixel_format: Glyph pixel format of @pixels + * @pixels: The glyph data + * @width: Width of glyph, in pixels + * @height: Height of glyph, in pixels + * @rowstride: Offset from start of one row to the next, in bytes + * + * Assigns a rendered glyph to a Unicode code point. This tells Chafa what the + * glyph looks like so the corresponding symbol can be used appropriately in + * output. + * + * Assigned glyphs override built-in glyphs and any earlier glyph that may + * have been assigned to the same code point. + * + * If the input is in a format with an alpha channel, the alpha channel will + * be used for the shape. If not, an average of the color channels will be used. + * + * Since: 1.4 + **/ +void +chafa_symbol_map_add_glyph (ChafaSymbolMap *symbol_map, + gunichar code_point, + ChafaPixelType pixel_format, + gpointer pixels, + gint width, gint height, + gint rowstride) +{ + g_return_if_fail (symbol_map != NULL); + + if (g_unichar_iswide (code_point)) + { + Glyph2 *glyph2; + + glyph2 = g_new (Glyph2, 1); + glyph2->c = code_point; + glyph_to_bitmap_wide (width, height, rowstride, pixel_format, pixels, + &glyph2->bitmap [0], &glyph2->bitmap [1]); + g_hash_table_insert (symbol_map->glyphs2, GUINT_TO_POINTER (code_point), glyph2); + } + else + { + Glyph *glyph; + + glyph = g_new (Glyph, 1); + glyph->c = code_point; + glyph->bitmap = glyph_to_bitmap (width, height, rowstride, pixel_format, pixels); + g_hash_table_insert (symbol_map->glyphs, GUINT_TO_POINTER (code_point), glyph); + } + + symbol_map->need_rebuild = TRUE; +} + +/** + * chafa_symbol_map_get_glyph: + * @symbol_map: A symbol map + * @code_point: A Unicode code point + * @pixel_format: Desired pixel format of @pixels_out + * @pixels_out: Storage for a pointer to exported glyph data + * @width_out: Storage for width of glyph, in pixels + * @height_out: Storage for height of glyph, in pixels + * @rowstride_out: Storage for offset from start of one row to the next, in bytes + * + * Returns data for the glyph corresponding to @code_point stored in @symbol_map. + * Any of @pixels_out, @width_out, @height_out and @rowstride_out can be NULL, + * in which case the corresponding data is not retrieved. + * + * If @pixels_out is not NULL, a pointer to freshly allocated memory containing + * height * rowstride bytes in the pixel format specified by @pixel_format + * will be stored at this address. It must be freed using g_free() when you're + * done with it. + * + * Monochrome glyphs (the only kind currently supported) will be rendered as + * opaque white on a transparent black background (0xffffffff for inked + * pixels and 0x00000000 for uninked). + * + * Since: 1.12 + **/ +gboolean +chafa_symbol_map_get_glyph (ChafaSymbolMap *symbol_map, + gunichar code_point, + ChafaPixelType pixel_format, + gpointer *pixels_out, + gint *width_out, gint *height_out, + gint *rowstride_out) +{ + gboolean success = FALSE; + gint width, height, rowstride; + + g_return_val_if_fail (symbol_map != NULL, FALSE); + + if (g_unichar_iswide (code_point)) + { + Glyph2 *glyph2; + + glyph2 = g_hash_table_lookup (symbol_map->glyphs2, GUINT_TO_POINTER (code_point)); + if (!glyph2) + goto out; + + g_assert (glyph2->c == code_point); + + if (pixels_out) + { + *pixels_out = g_malloc (CHAFA_SYMBOL_N_PIXELS * 4 * 2); + bitmap_to_argb (glyph2->bitmap [0], *pixels_out, + CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2); + bitmap_to_argb (glyph2->bitmap [1], ((guint8 *) *pixels_out) + CHAFA_SYMBOL_WIDTH_PIXELS * 4, + CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2); + } + + width = CHAFA_SYMBOL_WIDTH_PIXELS * 2; + height = CHAFA_SYMBOL_HEIGHT_PIXELS; + rowstride = CHAFA_SYMBOL_WIDTH_PIXELS * 2 * 4; + } + else + { + Glyph *glyph; + + glyph = g_hash_table_lookup (symbol_map->glyphs, GUINT_TO_POINTER (code_point)); + if (!glyph) + goto out; + + g_assert (glyph->c == code_point); + + if (pixels_out) + { + *pixels_out = g_malloc (CHAFA_SYMBOL_N_PIXELS * 4); + bitmap_to_argb (glyph->bitmap, *pixels_out, CHAFA_SYMBOL_WIDTH_PIXELS * 4); + } + + width = CHAFA_SYMBOL_WIDTH_PIXELS; + height = CHAFA_SYMBOL_HEIGHT_PIXELS; + rowstride = CHAFA_SYMBOL_WIDTH_PIXELS * 4; + } + + if (width_out) + *width_out = width; + if (height_out) + *height_out = height; + if (rowstride_out) + *rowstride_out = rowstride; + + if (pixels_out && pixel_format != CHAFA_PIXEL_ARGB8_PREMULTIPLIED) + { + gpointer temp_pixels = g_malloc (width * CHAFA_SYMBOL_HEIGHT_PIXELS * 4); + + /* Convert to desired pixel format */ + smol_scale_simple (SMOL_PIXEL_ARGB8_PREMULTIPLIED, *pixels_out, + width, height, rowstride, + (SmolPixelType) pixel_format, temp_pixels, + width, height, rowstride); + g_free (*pixels_out); + *pixels_out = temp_pixels; + } + + success = TRUE; + +out: + return success; +} diff -Nru chafa-1.2.1/chafa/chafa-symbol-map.h chafa-1.12.4/chafa/chafa-symbol-map.h --- chafa-1.2.1/chafa/chafa-symbol-map.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-symbol-map.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -49,10 +49,20 @@ CHAFA_SYMBOL_TAG_TECHNICAL = (1 << 12), CHAFA_SYMBOL_TAG_GEOMETRIC = (1 << 13), CHAFA_SYMBOL_TAG_ASCII = (1 << 14), - + CHAFA_SYMBOL_TAG_ALPHA = (1 << 15), + CHAFA_SYMBOL_TAG_DIGIT = (1 << 16), + CHAFA_SYMBOL_TAG_ALNUM = CHAFA_SYMBOL_TAG_ALPHA | CHAFA_SYMBOL_TAG_DIGIT, + CHAFA_SYMBOL_TAG_NARROW = (1 << 17), + CHAFA_SYMBOL_TAG_WIDE = (1 << 18), + CHAFA_SYMBOL_TAG_AMBIGUOUS = (1 << 19), + CHAFA_SYMBOL_TAG_UGLY = (1 << 20), + CHAFA_SYMBOL_TAG_LEGACY = (1 << 21), + CHAFA_SYMBOL_TAG_SEXTANT = (1 << 22), + CHAFA_SYMBOL_TAG_WEDGE = (1 << 23), + CHAFA_SYMBOL_TAG_LATIN = (1 << 24), CHAFA_SYMBOL_TAG_EXTRA = (1 << 30), - - CHAFA_SYMBOL_TAG_ALL = 0x7fffffff + CHAFA_SYMBOL_TAG_BAD = CHAFA_SYMBOL_TAG_AMBIGUOUS | CHAFA_SYMBOL_TAG_UGLY, + CHAFA_SYMBOL_TAG_ALL = ~(CHAFA_SYMBOL_TAG_EXTRA | CHAFA_SYMBOL_TAG_BAD) } ChafaSymbolTags; @@ -67,15 +77,48 @@ CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_unref (ChafaSymbolMap *symbol_map); +/* --- Selectors --- */ + CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_add_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags); CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_remove_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags); +CHAFA_AVAILABLE_IN_1_4 +void chafa_symbol_map_add_by_range (ChafaSymbolMap *symbol_map, + gunichar first, gunichar last); +CHAFA_AVAILABLE_IN_1_4 +void chafa_symbol_map_remove_by_range (ChafaSymbolMap *symbol_map, + gunichar first, gunichar last); + CHAFA_AVAILABLE_IN_ALL gboolean chafa_symbol_map_apply_selectors (ChafaSymbolMap *symbol_map, const gchar *selectors, GError **error); +/* --- Glyphs --- */ + +CHAFA_AVAILABLE_IN_1_4 +gboolean chafa_symbol_map_get_allow_builtin_glyphs (ChafaSymbolMap *symbol_map); +CHAFA_AVAILABLE_IN_1_4 +void chafa_symbol_map_set_allow_builtin_glyphs (ChafaSymbolMap *symbol_map, + gboolean use_builtin_glyphs); + +CHAFA_AVAILABLE_IN_1_4 +void chafa_symbol_map_add_glyph (ChafaSymbolMap *symbol_map, + gunichar code_point, + ChafaPixelType pixel_format, + gpointer pixels, + gint width, gint height, + gint rowstride); + +CHAFA_AVAILABLE_IN_1_12 +gboolean chafa_symbol_map_get_glyph (ChafaSymbolMap *symbol_map, + gunichar code_point, + ChafaPixelType pixel_format, + gpointer *pixels_out, + gint *width_out, gint *height_out, + gint *rowstride_out); + G_END_DECLS #endif /* __CHAFA_SYMBOL_MAP_H__ */ diff -Nru chafa-1.2.1/chafa/chafa-symbols-ascii.h chafa-1.12.4/chafa/chafa-symbols-ascii.h --- chafa-1.2.1/chafa/chafa-symbols-ascii.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-symbols-ascii.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,1194 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -/* This is meant to be #included in the symbol definition table of - * chafa-symbols.c. It's kept in a separate file due to its size. - * - * The symbol bitmaps are derived from https://github.com/dhepper/font8x8 by - * Daniel Hepper . Excerpt from the accompanying README: - * - * 8x8 monochrome bitmap font for rendering - * ======================================== - * - * A collection of header files containing a 8x8 bitmap font. - * - * [...] - * - * Author: Daniel Hepper - * License: Public Domain - * - * Credits - * ======= - * - * These header files are directly derived from an assembler file fetched from: - * http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm - * - * Original header: - * - * ; Summary: font8_8.asm - * ; 8x8 monochrome bitmap fonts for rendering - * ; - * ; Author: - * ; Marcel Sondaar - * ; International Business Machines (public domain VGA fonts) - * ; - * ; License: - * ; Public Domain - */ - - { - CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_SPACE, - ' ', - " " - " " - " " - " " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '!', - " XX " - " XXXX " - " XXXX " - " XX " - " XX " - " " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '"', - " XX XX " - " XX XX " - " " - " " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '#', - " XX XX " - " XX XX " - "XXXXXXX " - " XX XX " - "XXXXXXX " - " XX XX " - " XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '$', - " XX " - " XXXXX " - "XX " - " XXXX " - " XX " - "XXXXX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '%', - " " - "XX XX " - "XX XX " - " XX " - " XX " - " XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '&', - " XXX " - " XX XX " - " XXX " - " XXX XX " - "XX XXX " - "XX XX " - " XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 0x27, - " XX " - " XX " - "XX " - " " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '(', - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - ')', - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '*', - " " - " XX XX " - " XXXX " - "XXXXXXXX" - " XXXX " - " XX XX " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '+', - " " - " XX " - " XX " - "XXXXXX " - " XX " - " XX " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - ',', - " " - " " - " " - " " - " " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '-', - " " - " " - " " - "XXXXXX " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '.', - " " - " " - " " - " " - " " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '/', - " XX " - " XX " - " XX " - " XX " - " XX " - "XX " - "X " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '0', - " XXXXX " - "XX XX " - "XX XXX " - "XX XXXX " - "XXXX XX " - "XXX XX " - " XXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '1', - " XX " - " XXX " - " XX " - " XX " - " XX " - " XX " - "XXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '2', - " XXXX " - "XX XX " - " XX " - " XXX " - " XX " - "XX XX " - "XXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '3', - " XXXX " - "XX XX " - " XX " - " XXX " - " XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '4', - " XXX " - " XXXX " - " XX XX " - "XX XX " - "XXXXXXX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '5', - "XXXXXX " - "XX " - "XXXXX " - " XX " - " XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '6', - " XXX " - " XX " - "XX " - "XXXXX " - "XX XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '7', - "XXXXXX " - "XX XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '8', - " XXXX " - "XX XX " - "XX XX " - " XXXX " - "XX XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '9', - " XXXX " - "XX XX " - "XX XX " - " XXXXX " - " XX " - " XX " - " XXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - ':', - " " - " XX " - " XX " - " " - " " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - ';', - " " - " XX " - " XX " - " " - " " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '<', - " XX " - " XX " - " XX " - "XX " - " XX " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '=', - " " - " " - "XXXXXX " - " " - " " - "XXXXXX " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '>', - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '?', - " XXXX " - "XX XX " - " XX " - " XX " - " XX " - " " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '@', - " XXXXX " - "XX XX " - "XX XXXX " - "XX XXXX " - "XX XXXX " - "XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'A', - " XX " - " XXXX " - "XX XX " - "XX XX " - "XXXXXX " - "XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'B', - "XXXXXX " - " XX XX " - " XX XX " - " XXXXX " - " XX XX " - " XX XX " - "XXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'C', - " XXXX " - " XX XX " - "XX " - "XX " - "XX " - " XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'D', - "XXXXX " - " XX XX " - " XX XX " - " XX XX " - " XX XX " - " XX XX " - "XXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'E', - "XXXXXXX " - " XX X " - " XX X " - " XXXX " - " XX X " - " XX X " - "XXXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'F', - "XXXXXXX " - " XX X " - " XX X " - " XXXX " - " XX X " - " XX " - "XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'G', - " XXXX " - " XX XX " - "XX " - "XX " - "XX XXX " - " XX XX " - " XXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'H', - "XX XX " - "XX XX " - "XX XX " - "XXXXXX " - "XX XX " - "XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'I', - " XXXX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'J', - " XXXX " - " XX " - " XX " - " XX " - "XX XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'K', - "XXX XX " - " XX XX " - " XX XX " - " XXXX " - " XX XX " - " XX XX " - "XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'L', - "XXXX " - " XX " - " XX " - " XX " - " XX X " - " XX XX " - "XXXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'M', - "XX XX " - "XXX XXX " - "XXXXXXX " - "XXXXXXX " - "XX X XX " - "XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'N', - "XX XX " - "XXX XX " - "XXXX XX " - "XX XXXX " - "XX XXX " - "XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'O', - " XXX " - " XX XX " - "XX XX " - "XX XX " - "XX XX " - " XX XX " - " XXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'P', - "XXXXXX " - " XX XX " - " XX XX " - " XXXXX " - " XX " - " XX " - "XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'Q', - " XXXX " - "XX XX " - "XX XX " - "XX XX " - "XX XXX " - " XXXX " - " XXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'R', - "XXXXXX " - " XX XX " - " XX XX " - " XXXXX " - " XX XX " - " XX XX " - "XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'S', - " XXXX " - "XX XX " - "XXX " - " XXX " - " XXX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'T', - "XXXXXX " - "X XX X " - " XX " - " XX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'U', - "XX XX " - "XX XX " - "XX XX " - "XX XX " - "XX XX " - "XX XX " - "XXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'V', - "XX XX " - "XX XX " - "XX XX " - "XX XX " - "XX XX " - " XXXX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'W', - "XX XX " - "XX XX " - "XX XX " - "XX X XX " - "XXXXXXX " - "XXX XXX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'X', - "XX XX " - "XX XX " - " XX XX " - " XXX " - " XXX " - " XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'Y', - "XX XX " - "XX XX " - "XX XX " - " XXXX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'Z', - "XXXXXXX " - "XX XX " - "X XX " - " XX " - " XX X " - " XX XX " - "XXXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '[', - " XXXX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '\\', - "XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " X " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - ']', - " XXXX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '^', - " X " - " XXX " - " XX XX " - "XX XX " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '_', - " " - " " - " " - " " - " " - " " - "XXXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '`', - " XX " - " XX " - " XX " - " " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'a', - " " - " " - " XXXX " - " XX " - " XXXXX " - "XX XX " - " XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'b', - "XXX " - " XX " - " XX " - " XXXXX " - " XX XX " - " XX XX " - "XX XXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'c', - " " - " " - " XXXX " - "XX XX " - "XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'd', - " XXX " - " XX " - " XX " - " XXXXX " - "XX XX " - "XX XX " - " XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'e', - " " - " " - " XXXX " - "XX XX " - "XXXXXX " - "XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'f', - " XXX " - " XX XX " - " XX " - "XXXX " - " XX " - " XX " - "XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'g', - " " - " " - " XXX XX " - "XX XX " - "XX XX " - " XXXXX " - " XX " - "XXXXX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'h', - "XXX " - " XX " - " XX XX " - " XXX XX " - " XX XX " - " XX XX " - "XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'i', - " XX " - " " - " XXX " - " XX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'j', - " XX " - " " - " XX " - " XX " - " XX " - "XX XX " - "XX XX " - " XXXX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'k', - "XXX " - " XX " - " XX XX " - " XX XX " - " XXXX " - " XX XX " - "XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'l', - " XXX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'm', - " " - " " - "XX XX " - "XXXXXXX " - "XXXXXXX " - "XX X XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'n', - " " - " " - "XXXXX " - "XX XX " - "XX XX " - "XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'o', - " " - " " - " XXXX " - "XX XX " - "XX XX " - "XX XX " - " XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'p', - " " - " " - "XX XXX " - " XX XX " - " XX XX " - " XXXXX " - " XX " - "XXXX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'q', - " " - " " - " XXX XX " - "XX XX " - "XX XX " - " XXXXX " - " XX " - " XXXX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'r', - " " - " " - "XX XXX " - " XXX XX " - " XX XX " - " XX " - "XXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 's', - " " - " " - " XXXXX " - "XX " - " XXXX " - " XX " - "XXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 't', - " X " - " XX " - " XXXXX " - " XX " - " XX " - " XX X " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'u', - " " - " " - "XX XX " - "XX XX " - "XX XX " - "XX XX " - " XXX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'v', - " " - " " - "XX XX " - "XX XX " - "XX XX " - " XXXX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'w', - " " - " " - "XX XX " - "XX X XX " - "XXXXXXX " - "XXXXXXX " - " XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'x', - " " - " " - "XX XX " - " XX XX " - " XXX " - " XX XX " - "XX XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'y', - " " - " " - "XX XX " - "XX XX " - "XX XX " - " XXXXX " - " XX " - "XXXXX " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - 'z', - " " - " " - "XXXXXX " - "X XX " - " XX " - " XX X " - "XXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '{', - " XXX " - " XX " - " XX " - "XXX " - " XX " - " XX " - " XXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '|', - " XX " - " XX " - " XX " - " " - " XX " - " XX " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '}', - "XXX " - " XX " - " XX " - " XXX " - " XX " - " XX " - "XXX " - " " - }, - { - CHAFA_SYMBOL_TAG_ASCII, - '~', - " XXX XX " - "XX XXX " - " " - " " - " " - " " - " " - " " - }, diff -Nru chafa-1.2.1/chafa/chafa-symbols.c chafa-1.12.4/chafa/chafa-symbols.c --- chafa-1.2.1/chafa/chafa-symbols.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-symbols.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,2038 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2018 Hans Petter Jansson - * - * This file is part of Chafa, a program that turns images into character art. - * - * Chafa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chafa is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chafa. If not, see . */ - -#include "config.h" - -#include /* memset */ -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" - -typedef struct -{ - ChafaSymbolTags sc; - gunichar c; - gchar *coverage; -} -ChafaSymbolDef; - -ChafaSymbol *chafa_symbols; -static gboolean symbols_initialized; - -static const ChafaSymbolDef symbol_defs [] = -{ -#include "chafa-symbols-ascii.h" - { - /* Horizontal Scan Line 1 */ - CHAFA_SYMBOL_TAG_TECHNICAL, - 0x23ba, - "XXXXXXXX" - " " - " " - " " - " " - " " - " " - " " - }, - { - /* Horizontal Scan Line 3 */ - CHAFA_SYMBOL_TAG_TECHNICAL, - 0x23bb, - " " - " " - "XXXXXXXX" - " " - " " - " " - " " - " " - }, - { - /* Horizontal Scan Line 7 */ - CHAFA_SYMBOL_TAG_TECHNICAL, - 0x23bc, - " " - " " - " " - " " - " " - "XXXXXXXX" - " " - " " - }, - { - /* Horizontal Scan Line 9 */ - CHAFA_SYMBOL_TAG_TECHNICAL, - 0x23bd, - " " - " " - " " - " " - " " - " " - " " - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_VHALF | CHAFA_SYMBOL_TAG_INVERTED, - 0x2580, - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2581, - " " - " " - " " - " " - " " - " " - " " - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2582, - " " - " " - " " - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2583, - " " - " " - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_VHALF, - 0x2584, - " " - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2585, - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2586, - " " - " " - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2587, - " " - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - /* Full block */ - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_SOLID, - 0x2588, - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x2589, - "XXXXXXX " - "XXXXXXX " - "XXXXXXX " - "XXXXXXX " - "XXXXXXX " - "XXXXXXX " - "XXXXXXX " - "XXXXXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x258a, - "XXXXXX " - "XXXXXX " - "XXXXXX " - "XXXXXX " - "XXXXXX " - "XXXXXX " - "XXXXXX " - "XXXXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x258b, - "XXXXX " - "XXXXX " - "XXXXX " - "XXXXX " - "XXXXX " - "XXXXX " - "XXXXX " - "XXXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_HHALF, - 0x258c, - "XXXX " - "XXXX " - "XXXX " - "XXXX " - "XXXX " - "XXXX " - "XXXX " - "XXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x258d, - "XXX " - "XXX " - "XXX " - "XXX " - "XXX " - "XXX " - "XXX " - "XXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x258e, - "XX " - "XX " - "XX " - "XX " - "XX " - "XX " - "XX " - "XX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK, - 0x258f, - "X " - "X " - "X " - "X " - "X " - "X " - "X " - "X " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_HHALF | CHAFA_SYMBOL_TAG_INVERTED, - 0x2590, - " XXXX" - " XXXX" - " XXXX" - " XXXX" - " XXXX" - " XXXX" - " XXXX" - " XXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_INVERTED, - 0x2594, - "XXXXXXXX" - " " - " " - " " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_INVERTED, - 0x2595, - " X" - " X" - " X" - " X" - " X" - " X" - " X" - " X" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, - 0x2596, - " " - " " - " " - " " - "XXXX " - "XXXX " - "XXXX " - "XXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, - 0x2597, - " " - " " - " " - " " - " XXXX" - " XXXX" - " XXXX" - " XXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, - 0x2598, - "XXXX " - "XXXX " - "XXXX " - "XXXX " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, - 0x2599, - "XXXX " - "XXXX " - "XXXX " - "XXXX " - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, - 0x259a, - "XXXX " - "XXXX " - "XXXX " - "XXXX " - " XXXX" - " XXXX" - " XXXX" - " XXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, - 0x259b, - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXX " - "XXXX " - "XXXX " - "XXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, - 0x259c, - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - " XXXX" - " XXXX" - " XXXX" - " XXXX" - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, - 0x259d, - " XXXX" - " XXXX" - " XXXX" - " XXXX" - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, - 0x259e, - " XXXX" - " XXXX" - " XXXX" - " XXXX" - "XXXX " - "XXXX " - "XXXX " - "XXXX " - }, - { - CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, - 0x259f, - " XXXX" - " XXXX" - " XXXX" - " XXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - "XXXXXXXX" - }, - /* Begin box drawing characters */ - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2500, - " " - " " - " " - " " - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2501, - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2502, - " X " - " X " - " X " - " X " - " X " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2503, - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2504, - " " - " " - " " - " " - "XX XX XX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2505, - " " - " " - " " - "XX XX XX" - "XX XX XX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2506, - " X " - " X " - " " - " X " - " X " - " " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2507, - " XX " - " XX " - " " - " XX " - " XX " - " " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2508, - " " - " " - " " - " " - "X X X X " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2509, - " " - " " - " " - "X X X X " - "X X X X " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x250a, - " X " - " " - " X " - " " - " X " - " " - " X " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x250b, - " XX " - " " - " XX " - " " - " XX " - " " - " XX " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x250c, - " " - " " - " " - " " - " XXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x250d, - " " - " " - " " - " XXXX" - " XXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x250e, - " " - " " - " " - " " - " XXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x250f, - " " - " " - " " - " XXXXX" - " XXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2510, - " " - " " - " " - " " - "XXXXX " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2511, - " " - " " - " " - "XXXXX " - "XXXXX " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2512, - " " - " " - " " - " " - "XXXXX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2513, - " " - " " - " " - "XXXXX " - "XXXXX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2514, - " X " - " X " - " X " - " X " - " XXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2515, - " X " - " X " - " X " - " XXXX" - " XXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2516, - " XX " - " XX " - " XX " - " XX " - " XXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2517, - " XX " - " XX " - " XX " - " XXXXX" - " XXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2518, - " X " - " X " - " X " - " X " - "XXXXX " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2519, - " X " - " X " - " X " - "XXXXX " - "XXXXX " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x251a, - " XX " - " XX " - " XX " - " XX " - "XXXXX " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x251b, - " XX " - " XX " - " XX " - "XXXXX " - "XXXXX " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x251c, - " X " - " X " - " X " - " X " - " XXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x251d, - " X " - " X " - " X " - " XXXX" - " XXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x251e, - " XX " - " XX " - " XX " - " XX " - " XXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x251f, - " X " - " X " - " X " - " X " - " XXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2520, - " XX " - " XX " - " XX " - " XX " - " XXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2521, - " XX " - " XX " - " XX " - " XXXXX" - " XXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2522, - " X " - " X " - " X " - " XXXXX" - " XXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2523, - " XX " - " XX " - " XX " - " XXXXX" - " XXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2524, - " X " - " X " - " X " - " X " - "XXXXX " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2525, - " X " - " X " - " X " - "XXXXX " - "XXXXX " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2526, - " XX " - " XX " - " XX " - " XX " - "XXXXX " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2527, - " X " - " X " - " X " - " X " - "XXXXX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2528, - " XX " - " XX " - " XX " - " XX " - "XXXXX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2529, - " XX " - " XX " - " XX " - "XXXXX " - "XXXXX " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x252a, - " X " - " X " - " X " - "XXXXX " - "XXXXX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x252b, - " XX " - " XX " - " XX " - "XXXXX " - "XXXXX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x252c, - " " - " " - " " - " " - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x252d, - " " - " " - " " - "XXXXX " - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x252e, - " " - " " - " " - " XXXX" - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x252f, - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2530, - " " - " " - " " - " " - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2531, - " " - " " - " " - "XXXXX " - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2532, - " " - " " - " " - " XXXXX" - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2533, - " " - " " - " " - "XXXXXXXX" - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2534, - " X " - " X " - " X " - " X " - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2535, - " X " - " X " - " X " - "XXXXX " - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2536, - " X " - " X " - " X " - " XXXX" - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2537, - " X " - " X " - " X " - "XXXXXXXX" - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2538, - " XX " - " XX " - " XX " - " XX " - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2539, - " XX " - " XX " - " XX " - "XXXXX " - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x253a, - " XX " - " XX " - " XX " - " XXXXX" - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x253b, - " XX " - " XX " - " XX " - "XXXXXXXX" - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x253c, - " X " - " X " - " X " - " X " - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x253d, - " X " - " X " - " X " - "XXXXX " - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x253e, - " X " - " X " - " X " - " XXXX" - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x253f, - " X " - " X " - " X " - "XXXXXXXX" - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2540, - " XX " - " XX " - " XX " - " XX " - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2541, - " X " - " X " - " X " - " X " - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2542, - " XX " - " XX " - " XX " - " XX " - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2543, - " XX " - " XX " - " XX " - "XXXXX " - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2544, - " XX " - " XX " - " XX " - " XXXXX" - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2545, - " X " - " X " - " X " - "XXXXX " - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2546, - " X " - " X " - " X " - " XXXXX" - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2547, - " XX " - " XX " - " XX " - "XXXXXXXX" - "XXXXXXXX" - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2548, - " X " - " X " - " X " - "XXXXXXXX" - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x2549, - " XX " - " XX " - " XX " - "XXXXX " - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x254a, - " XX " - " XX " - " XX " - " XXXXX" - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x254b, - " XX " - " XX " - " XX " - "XXXXXXXX" - "XXXXXXXX" - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x254c, - " " - " " - " " - " " - "XXX XXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x254d, - " " - " " - " " - "XXX XXX" - "XXX XXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x254e, - " X " - " X " - " X " - " " - " " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x254f, - " XX " - " XX " - " XX " - " " - " " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, - 0x2571, - " X" - " X " - " X " - " X " - " X " - " X " - " X " - "X " - }, - { - /* Variant */ - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, - 0x2571, - " XX" - " XXX" - " XXX " - " XXX " - " XXX " - " XXX " - "XXX " - "XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, - 0x2572, - "X " - " X " - " X " - " X " - " X " - " X " - " X " - " X" - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, - 0x2572, - "XX " - "XXX " - " XXX " - " XXX " - " XXX " - " XXX " - " XXX" - " XX" - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, - 0x2573, - "X X" - " X X " - " X X " - " XX " - " XX " - " X X " - " X X " - "X X" - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2574, - " " - " " - " " - " " - "XXXX " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2575, - " X " - " X " - " X " - " X " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2576, - " " - " " - " " - " " - " XXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2577, - " " - " " - " " - " " - " X " - " X " - " X " - " X " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2578, - " " - " " - " " - "XXXX " - "XXXX " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x2579, - " XX " - " XX " - " XX " - " XX " - " " - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x257a, - " " - " " - " " - " XXXX" - " XXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, - 0x257b, - " " - " " - " " - " " - " XX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x257c, - " " - " " - " " - " XXXX" - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x257d, - " X " - " X " - " X " - " X " - " XX " - " XX " - " XX " - " XX " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x257e, - " " - " " - " " - "XXXX " - "XXXXXXXX" - " " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_BORDER, - 0x257f, - " XX " - " XX " - " XX " - " XX " - " X " - " X " - " X " - " X " - }, - /* Begin dot characters*/ - { - CHAFA_SYMBOL_TAG_DOT, - 0x25ae, /* Black vertical rectangle */ - " " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " " - }, - { - CHAFA_SYMBOL_TAG_DOT, - 0x25a0, /* Black square */ - " " - " " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_DOT, - 0x25aa, /* Black small square */ - " " - " " - " XXXX " - " XXXX " - " XXXX " - " XXXX " - " " - " " - }, - { - /* Black up-pointing triangle */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25b2, - " " - " XX " - " XXXX " - " XXXXXX " - " XXXXXX " - "XXXXXXXX" - " " - " " - }, - { - /* Black right-pointing triangle */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25b6, - " X " - " XXX " - " XXXX " - " XXXXXX " - " XXXX " - " XXX " - " X " - " " - }, - { - /* Black down-pointing triangle */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25bc, - " " - "XXXXXXXX" - " XXXXXX " - " XXXXXX " - " XXXX " - " XX " - " " - " " - }, - { - /* Black left-pointing triangle */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25c0, - " X " - " XXX " - " XXXX " - " XXXXXX " - " XXXX " - " XXX " - " X " - " " - }, - { - /* Black diamond */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25c6, - " " - " XX " - " XXXX " - " XXXXXX " - " XXXX " - " XX " - " " - " " - }, - { - /* Black Circle */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25cf, - " " - " XXXX " - " XXXXXX " - " XXXXXX " - " XXXXXX " - " XXXX " - " " - " " - }, - { - /* Black Lower Right Triangle */ - /* CHAFA_SYMBOL_TAG_GEOMETRIC, */ - CHAFA_SYMBOL_TAG_EXTRA, - 0x25e2, - " " - " X " - " XX " - " XXX " - " XXXX " - " XXXXX " - " XXXXXX " - " " - }, - { - /* Black Lower Left Triangle */ - /* CHAFA_SYMBOL_TAG_GEOMETRIC, */ - CHAFA_SYMBOL_TAG_EXTRA, - 0x25e3, - " " - " X " - " XX " - " XXX " - " XXXX " - " XXXXX " - " XXXXXX " - " " - }, - { - /* Black Upper Left Triangle */ - /* CHAFA_SYMBOL_TAG_GEOMETRIC, */ - CHAFA_SYMBOL_TAG_EXTRA, - 0x25e4, - " " - " XXXXXX " - " XXXXX " - " XXXX " - " XXX " - " XX " - " X " - " " - }, - { - /* Black Upper Right Triangle */ - /* CHAFA_SYMBOL_TAG_GEOMETRIC, */ - CHAFA_SYMBOL_TAG_EXTRA, - 0x25e5, - " " - " XXXXXX " - " XXXXX " - " XXXX " - " XXX " - " XX " - " X " - " " - }, - { - /* Black Medium Square */ - CHAFA_SYMBOL_TAG_GEOMETRIC, - 0x25fc, - " " - " " - " XXXX " - " XXXX " - " XXXX " - " XXXX " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_DOT, - 0x00b7, /* Middle dot */ - " " - " " - " " - " XX " - " XX " - " " - " " - " " - }, - { - /* Variant */ - CHAFA_SYMBOL_TAG_DOT, - 0x00b7, /* Middle dot */ - " " - " " - " " - " XX " - " XX " - " " - " " - " " - }, - { - /* Variant */ - CHAFA_SYMBOL_TAG_DOT, - 0x00b7, /* Middle dot */ - " " - " " - " " - " XX " - " XX " - " " - " " - " " - }, - { - /* Variant */ - CHAFA_SYMBOL_TAG_DOT, - 0x00b7, /* Middle dot */ - " " - " " - " XX " - " XX " - " " - " " - " " - " " - }, - { - /* Variant */ - CHAFA_SYMBOL_TAG_DOT, - 0x00b7, /* Middle dot */ - " " - " " - " " - " " - " XX " - " XX " - " " - " " - }, - { - CHAFA_SYMBOL_TAG_STIPPLE, - 0x2591, - "X X " - " X X " - "X X " - " X X " - "X X " - " X X " - "X X " - " X X " - }, - { - CHAFA_SYMBOL_TAG_STIPPLE, - 0x2592, - "X X X X " - " X X X X" - "X X X X " - " X X X X" - "X X X X " - " X X X X" - "X X X X " - " X X X X" - }, - { - CHAFA_SYMBOL_TAG_STIPPLE, - 0x2593, - " XXX XXX" - "XX XXX X" - " XXX XXX" - "XX XXX X" - " XXX XXX" - "XX XXX X" - " XXX XXX" - "XX XXX X" - }, - { - /* Greek Capital Letter Xi */ - CHAFA_SYMBOL_TAG_EXTRA, - 0x039e, - " " - " XXXXXX " - " " - " XXXX " - " " - " XXXXXX " - " " - " " - }, - { - 0, 0, "" - } -}; - -static void -calc_weights (ChafaSymbol *sym) -{ - gint i; - - sym->fg_weight = 0; - sym->bg_weight = 0; - - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - guchar p = sym->coverage [i]; - - sym->fg_weight += p; - sym->bg_weight += 1 - p; - } -} - -static void -xlate_coverage (const gchar *coverage_in, gchar *coverage_out) -{ - gchar xlate [256]; - gint i; - - xlate [' '] = 0; - xlate ['X'] = 1; - - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - guchar p = (guchar) coverage_in [i]; - coverage_out [i] = xlate [p]; - } -} - -static guint64 -coverage_to_bitmap (const gchar *cov) -{ - guint64 bitmap = 0; - gint i; - - for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) - { - bitmap <<= 1; - - if (cov [i]) - bitmap |= 1; - } - - return bitmap; -} - -static void -gen_braille_sym (gchar *cov, guint8 val) -{ - memset (cov, 0, CHAFA_SYMBOL_N_PIXELS); - - cov [1] = cov [2] = (val & 1); - cov [5] = cov [6] = ((val >> 3) & 1); - cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; - - cov [1] = cov [2] = ((val >> 1) & 1); - cov [5] = cov [6] = ((val >> 4) & 1); - cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; - - cov [1] = cov [2] = ((val >> 2) & 1); - cov [5] = cov [6] = ((val >> 5) & 1); - cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; - - cov [1] = cov [2] = ((val >> 6) & 1); - cov [5] = cov [6] = ((val >> 7) & 1); -} - -static void -generate_braille_syms (ChafaSymbol *syms, gint first_ofs) -{ - gunichar c; - gint i = first_ofs; - - /* Braille 2x4 range */ - - c = 0x2800; - - for (i = first_ofs; c < 0x2900; c++, i++) - { - ChafaSymbol *sym = &syms [i]; - - sym->sc = CHAFA_SYMBOL_TAG_BRAILLE; - sym->c = c; - sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); - - gen_braille_sym (sym->coverage, c - 0x2800); - calc_weights (&syms [i]); - syms [i].bitmap = coverage_to_bitmap (syms [i].coverage); - syms [i].popcount = chafa_population_count_u64 (syms [i].bitmap); - } -} - -static ChafaSymbol * -init_symbol_array (const ChafaSymbolDef *defs) -{ - gint i; - ChafaSymbol *syms; - - syms = g_new0 (ChafaSymbol, CHAFA_N_SYMBOLS_MAX); - - for (i = 0; defs [i].c; i++) - { - syms [i].sc = defs [i].sc; - syms [i].c = defs [i].c; - syms [i].coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); - - xlate_coverage (defs [i].coverage, syms [i].coverage); - calc_weights (&syms [i]); - syms [i].bitmap = coverage_to_bitmap (syms [i].coverage); - syms [i].popcount = chafa_population_count_u64 (syms [i].bitmap); - } - - generate_braille_syms (syms, i); - return syms; -} - -void -chafa_init_symbols (void) -{ - if (symbols_initialized) - return; - - chafa_symbols = init_symbol_array (symbol_defs); - - symbols_initialized = TRUE; -} diff -Nru chafa-1.2.1/chafa/chafa-term-db.c chafa-1.12.4/chafa/chafa-term-db.c --- chafa-1.2.1/chafa/chafa-term-db.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-term-db.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,603 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include "chafa.h" +#include /* strtoul */ +#include /* strlen, strncmp, strcmp, memcpy */ + +/** + * SECTION:chafa-term-db + * @title: ChafaTermDb + * @short_description: A database of terminal information + * + * A #ChafaTermDb contains information on terminals, and can be used to obtain + * a suitable #ChafaTermInfo for a terminal environment. + **/ + +/* This is a very naïve implementation, but perhaps good enough for most + * contemporary terminal emulators. I've kept the API minimal so actual + * termcap/terminfo subset parsing can be added later if needed without + * breaking existing applications. */ + +struct ChafaTermDb +{ + gint refs; +}; + +typedef struct +{ + ChafaTermSeq seq; + const gchar *str; +} +SeqStr; + +static const SeqStr vt220_seqs [] = +{ + { CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT, "\033[!p" }, + { CHAFA_TERM_SEQ_RESET_TERMINAL_HARD, "\033c" }, + { CHAFA_TERM_SEQ_RESET_ATTRIBUTES, "\033[0m" }, + { CHAFA_TERM_SEQ_CLEAR, "\033[2J" }, + { CHAFA_TERM_SEQ_ENABLE_BOLD, "\033[1m" }, + { CHAFA_TERM_SEQ_INVERT_COLORS, "\033[7m" }, + { CHAFA_TERM_SEQ_CURSOR_TO_TOP_LEFT, "\033[0H" }, + { CHAFA_TERM_SEQ_CURSOR_TO_BOTTOM_LEFT, "\033[9999;1H" }, + { CHAFA_TERM_SEQ_CURSOR_TO_POS, "\033[%2;%1H" }, + { CHAFA_TERM_SEQ_CURSOR_UP, "\033[%1A" }, + { CHAFA_TERM_SEQ_CURSOR_UP_1, "\033[A" }, + { CHAFA_TERM_SEQ_CURSOR_DOWN, "\033[%1B" }, + { CHAFA_TERM_SEQ_CURSOR_DOWN_1, "\033[B" }, + { CHAFA_TERM_SEQ_CURSOR_LEFT, "\033[%1D" }, + { CHAFA_TERM_SEQ_CURSOR_LEFT_1, "\033[D" }, + { CHAFA_TERM_SEQ_CURSOR_RIGHT, "\033[%1C" }, + { CHAFA_TERM_SEQ_CURSOR_RIGHT_1, "\033[C" }, + { CHAFA_TERM_SEQ_CURSOR_UP_SCROLL, "\033D" }, + { CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL, "\033M" }, + { CHAFA_TERM_SEQ_INSERT_CELLS, "\033[%1@" }, + { CHAFA_TERM_SEQ_DELETE_CELLS, "\033[%1P" }, + { CHAFA_TERM_SEQ_INSERT_ROWS, "\033[%1L" }, + { CHAFA_TERM_SEQ_DELETE_ROWS, "\033[%1M" }, + { CHAFA_TERM_SEQ_SET_SCROLLING_ROWS, "\033[%1;%2r" }, + { CHAFA_TERM_SEQ_ENABLE_INSERT, "\033[4h" }, + { CHAFA_TERM_SEQ_DISABLE_INSERT,"\033[4l" }, + { CHAFA_TERM_SEQ_ENABLE_CURSOR, "\033[?25h" }, + { CHAFA_TERM_SEQ_DISABLE_CURSOR, "\033[?25l" }, + { CHAFA_TERM_SEQ_ENABLE_ECHO, "\033[12l" }, + { CHAFA_TERM_SEQ_DISABLE_ECHO, "\033[12h" }, + { CHAFA_TERM_SEQ_ENABLE_WRAP, "\033[?7h" }, + { CHAFA_TERM_SEQ_DISABLE_WRAP, "\033[?7l" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr rep_seqs [] = +{ + { CHAFA_TERM_SEQ_REPEAT_CHAR, "\033[%1b" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr sixel_seqs [] = +{ + { CHAFA_TERM_SEQ_BEGIN_SIXELS, "\033P%1;%2;%3q" }, + { CHAFA_TERM_SEQ_END_SIXELS, "\033\\" }, + { CHAFA_TERM_SEQ_ENABLE_SIXEL_SCROLLING, "\033[?80l" }, + { CHAFA_TERM_SEQ_DISABLE_SIXEL_SCROLLING, "\033[?80h" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr color_direct_seqs [] = +{ + { CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT, "\033[38;2;%1;%2;%3m" }, + { CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT, "\033[48;2;%1;%2;%3m" }, + { CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT, "\033[38;2;%1;%2;%3;48;2;%4;%5;%6m" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr color_256_seqs [] = +{ + { CHAFA_TERM_SEQ_SET_COLOR_FG_256, "\033[38;5;%1m" }, + { CHAFA_TERM_SEQ_SET_COLOR_BG_256, "\033[48;5;%1m" }, + { CHAFA_TERM_SEQ_SET_COLOR_FGBG_256, "\033[38;5;%1;48;5;%2m" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr color_16_seqs [] = +{ + { CHAFA_TERM_SEQ_SET_COLOR_FG_16, "\033[%1m" }, + { CHAFA_TERM_SEQ_SET_COLOR_BG_16, "\033[%1m" }, + { CHAFA_TERM_SEQ_SET_COLOR_FGBG_16, "\033[%1;%2m" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr color_8_seqs [] = +{ + { CHAFA_TERM_SEQ_SET_COLOR_FG_8, "\033[%1m" }, + { CHAFA_TERM_SEQ_SET_COLOR_BG_8, "\033[%1m" }, + { CHAFA_TERM_SEQ_SET_COLOR_FGBG_8, "\033[%1;%2m" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr *color_direct_list [] = +{ + color_direct_seqs, + color_256_seqs, + color_16_seqs, + color_8_seqs, + NULL +}; + +static const SeqStr *color_256_list [] = +{ + color_256_seqs, + color_16_seqs, + color_8_seqs, + NULL +}; + +static const SeqStr *color_16_list [] = +{ + color_16_seqs, + color_8_seqs, + NULL +}; + +static const SeqStr color_fbterm_seqs [] = +{ + { CHAFA_TERM_SEQ_SET_COLOR_FG_16, "\033[1;%1}" }, + { CHAFA_TERM_SEQ_SET_COLOR_BG_16, "\033[2;%1}" }, + { CHAFA_TERM_SEQ_SET_COLOR_FGBG_16, "\033[1;%1}\033[2;%2}" }, + { CHAFA_TERM_SEQ_SET_COLOR_FG_256, "\033[1;%1}" }, + { CHAFA_TERM_SEQ_SET_COLOR_BG_256, "\033[2;%1}" }, + { CHAFA_TERM_SEQ_SET_COLOR_FGBG_256, "\033[1;%1}\033[2;%2}" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr *color_fbterm_list [] = +{ + color_fbterm_seqs, + color_8_seqs, + NULL +}; + +static const SeqStr kitty_seqs [] = +{ + { CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1, "\033_Ga=T,f=%1,s=%2,v=%3,c=%4,r=%5,m=1\033\\" }, + { CHAFA_TERM_SEQ_END_KITTY_IMAGE, "\033_Gm=0\033\\" }, + { CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK, "\033_Gm=1;" }, + { CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK, "\033\\" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr iterm2_seqs [] = +{ + { CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE, "\033]1337;File=inline=1;width=%1;height=%2;preserveAspectRatio=0:" }, + { CHAFA_TERM_SEQ_END_ITERM2_IMAGE, "\a" }, + + { CHAFA_TERM_SEQ_MAX, NULL } +}; + +static const SeqStr *fallback_list [] = +{ + vt220_seqs, + color_direct_seqs, + color_256_seqs, + color_16_seqs, + color_8_seqs, + sixel_seqs, + kitty_seqs, + iterm2_seqs, + NULL +}; + +static void +add_seqs (ChafaTermInfo *ti, const SeqStr *seqstr) +{ + gint i; + + if (!seqstr) + return; + + for (i = 0; seqstr [i].str; i++) + { + chafa_term_info_set_seq (ti, seqstr [i].seq, seqstr [i].str, NULL); + } +} + +static void +add_seq_list (ChafaTermInfo *ti, const SeqStr **seqlist) +{ + gint i; + + if (!seqlist) + return; + + for (i = 0; seqlist [i]; i++) + { + add_seqs (ti, seqlist [i]); + } +} + +static void +detect_capabilities (ChafaTermInfo *ti, gchar **envp) +{ + const gchar *term; + const gchar *colorterm; + const gchar *konsole_version; + const gchar *vte_version; + const gchar *term_program; + const gchar *term_name; + const gchar *tmux; + const gchar *ctx_backend; + const gchar *lc_terminal; + gchar *comspec = NULL; + const SeqStr **color_seq_list = color_256_list; + const SeqStr *gfx_seqs = NULL; + const SeqStr *rep_seqs_local = NULL; + + add_seqs (ti, vt220_seqs); + + term = g_environ_getenv (envp, "TERM"); + if (!term) term = ""; + + colorterm = g_environ_getenv (envp, "COLORTERM"); + if (!colorterm) colorterm = ""; + + konsole_version = g_environ_getenv (envp, "KONSOLE_VERSION"); + if (!konsole_version) konsole_version = ""; + + vte_version = g_environ_getenv (envp, "VTE_VERSION"); + if (!vte_version) vte_version = ""; + + term_program = g_environ_getenv (envp, "TERM_PROGRAM"); + if (!term_program) term_program = ""; + + term_name = g_environ_getenv (envp, "TERMINAL_NAME"); + if (!term_name) term_name = ""; + + tmux = g_environ_getenv (envp, "TMUX"); + if (!tmux) tmux = ""; + + ctx_backend = g_environ_getenv (envp, "CTX_BACKEND"); + if (!ctx_backend) ctx_backend = ""; + + lc_terminal = g_environ_getenv (envp, "LC_TERMINAL"); + if (!lc_terminal) lc_terminal = ""; + + /* The MS Windows 10 TH2 (v1511+) console supports ANSI escape codes, + * including AIX and DirectColor sequences. We detect this early and allow + * TERM to override, if present. */ + comspec = (gchar *) g_environ_getenv (envp, "ComSpec"); + if (comspec) + { + comspec = g_ascii_strdown (comspec, -1); + if (g_str_has_suffix (comspec, "\\cmd.exe")) + color_seq_list = color_direct_list; + g_free (comspec); + comspec = NULL; + } + + /* Some terminals set COLORTERM=truecolor. However, this env var can + * make its way into environments where truecolor is not desired + * (e.g. screen sessions), so check it early on and override it later. */ + if (!g_ascii_strcasecmp (colorterm, "truecolor") + || !g_ascii_strcasecmp (colorterm, "gnome-terminal") + || !g_ascii_strcasecmp (colorterm, "xfce-terminal")) + color_seq_list = color_direct_list; + + /* In a modern VTE we can rely on VTE_VERSION. It's a great terminal emulator + * which supports truecolor. */ + if (strlen (vte_version) > 0) + { + color_seq_list = color_direct_list; + + /* Newer VTE versions understand REP */ + if (g_ascii_strtoull (vte_version, NULL, 10) >= 5202 + && !strcmp (term, "xterm-256color")) + rep_seqs_local = rep_seqs; + } + + /* Konsole exports KONSOLE_VERSION */ + if (strtoul (konsole_version, NULL, 10) >= 220370) + { + /* Konsole version 22.03.70+ supports sixel graphics */ + gfx_seqs = sixel_seqs; + } + + /* The ctx terminal (https://ctx.graphics/) understands REP */ + if (strlen (ctx_backend) > 0) + rep_seqs_local = rep_seqs; + + /* Terminals that advertise 256 colors usually support truecolor too, + * (VTE, xterm) although some (xterm) may quantize to an indexed palette + * regardless. */ + if (!strcmp (term, "xterm-256color") + || !strcmp (term, "xterm-direct") + || !strcmp (term, "xterm-direct2") + || !strcmp (term, "xterm-direct16") + || !strcmp (term, "xterm-direct256") + || !strcmp (term, "xterm-kitty") + || !strcmp (term, "st-256color")) + color_seq_list = color_direct_list; + + /* Kitty has a unique graphics protocol */ + if (!strcmp (term, "xterm-kitty")) + gfx_seqs = kitty_seqs; + + /* iTerm2 supports truecolor and has a unique graphics protocol */ + if (!g_ascii_strcasecmp (lc_terminal, "iTerm2") + || !g_ascii_strcasecmp (term_program, "iTerm.app")) + { + color_seq_list = color_direct_list; + gfx_seqs = iterm2_seqs; + } + + if (!g_ascii_strcasecmp (term_program, "WezTerm")) + { + gfx_seqs = sixel_seqs; + } + + if (!g_ascii_strcasecmp (term_name, "contour")) + { + gfx_seqs = sixel_seqs; + } + + /* Apple Terminal sets TERM=xterm-256color, and does not support truecolor */ + if (!g_ascii_strcasecmp (term_program, "Apple_Terminal")) + color_seq_list = color_256_list; + + /* mlterm's truecolor support seems to be broken; it looks like a color + * allocation issue. This affects character cells, but not sixels. + * + * yaft supports sixels and truecolor escape codes, but it remaps cell + * colors to a 256-color palette. */ + if (!strcmp (term, "mlterm") + || !strcmp (term, "yaft") + || !strcmp (term, "yaft-256color")) + { + /* The default canvas mode is truecolor for sixels. 240 colors is + * the default for symbols. */ + color_seq_list = color_256_list; + gfx_seqs = sixel_seqs; + } + + if (!strcmp (term, "foot") || !strncmp (term, "foot-", 5)) + gfx_seqs = sixel_seqs; + + /* rxvt 256-color really is 256 colors only */ + if (!strcmp (term, "rxvt-unicode-256color")) + color_seq_list = color_256_list; + + /* Regular rxvt supports 16 colors at most */ + if (!strcmp (term, "rxvt-unicode")) + color_seq_list = color_16_list; + + /* 'screen' does not like truecolor at all, but 256 colors works fine. + * Sometimes we'll see the outer terminal appended to the TERM string, + * like so: screen.xterm-256color */ + if (!strncmp (term, "screen", 6)) + { + color_seq_list = color_256_list; + + /* 'tmux' also sets TERM=screen, but it supports truecolor codes. + * You may have to add the following to .tmux.conf to prevent + * remapping to 256 colors: + * + * tmux set-option -ga terminal-overrides ",screen-256color:Tc" */ + if (strlen (tmux) > 0) + color_seq_list = color_direct_list; + + /* screen and older tmux do not support REP. Newer tmux does, + * but there's no reliable way to tell which version we're dealing with. */ + rep_seqs_local = NULL; + } + + /* If TERM is "linux", we're probably on the Linux console, which supports + * 16 colors only. It also sets COLORTERM=1. + * + * https://github.com/torvalds/linux/commit/cec5b2a97a11ade56a701e83044d0a2a984c67b4 + * + * In theory we could emit truecolor codes and let the console remap, + * but we get better results if we do the conversion ourselves, since we + * can apply preprocessing and exotic color spaces. */ + if (!strcmp (term, "linux")) + color_seq_list = color_16_list; + + /* FbTerm can use 256 colors through a private extension; see fbterm(1) */ + if (!strcmp (term, "fbterm")) + color_seq_list = color_fbterm_list; + + add_seq_list (ti, color_seq_list); + add_seqs (ti, gfx_seqs); + add_seqs (ti, rep_seqs_local); +} + +static ChafaTermDb * +instantiate_singleton (G_GNUC_UNUSED gpointer data) +{ + return chafa_term_db_new (); +} + +/* Public */ + +/** + * chafa_term_db_new: + * + * Creates a new, blank #ChafaTermDb. + * + * Returns: The new #ChafaTermDb + * + * Since: 1.6 + **/ +ChafaTermDb * +chafa_term_db_new (void) +{ + ChafaTermDb *term_db; + + term_db = g_new0 (ChafaTermDb, 1); + term_db->refs = 1; + + return term_db; +} + +/** + * chafa_term_db_copy: + * @term_db: A #ChafaTermDb to copy. + * + * Creates a new #ChafaTermDb that's a copy of @term_db. + * + * Returns: The new #ChafaTermDb + * + * Since: 1.6 + **/ +ChafaTermDb * +chafa_term_db_copy (const ChafaTermDb *term_db) +{ + ChafaTermDb *new_term_db; + + new_term_db = g_new (ChafaTermDb, 1); + memcpy (new_term_db, term_db, sizeof (ChafaTermDb)); + new_term_db->refs = 1; + + return new_term_db; +} + +/** + * chafa_term_db_ref: + * @term_db: #ChafaTermDb to add a reference to. + * + * Adds a reference to @term_db. + * + * Since: 1.6 + **/ +void +chafa_term_db_ref (ChafaTermDb *term_db) +{ + gint refs; + + g_return_if_fail (term_db != NULL); + refs = g_atomic_int_get (&term_db->refs); + g_return_if_fail (refs > 0); + + g_atomic_int_inc (&term_db->refs); +} + +/** + * chafa_term_db_unref: + * @term_db: #ChafaTermDb to remove a reference from. + * + * Removes a reference from @term_db. + * + * Since: 1.6 + **/ +void +chafa_term_db_unref (ChafaTermDb *term_db) +{ + gint refs; + + g_return_if_fail (term_db != NULL); + refs = g_atomic_int_get (&term_db->refs); + g_return_if_fail (refs > 0); + + if (g_atomic_int_dec_and_test (&term_db->refs)) + { + g_free (term_db); + } +} + +/** + * chafa_term_db_get_default: + * + * Gets the global #ChafaTermDb. This can normally be used safely + * in a read-only capacity. The caller should not unref the + * returned object. + * + * Returns: The global #ChafaTermDb + * + * Since: 1.6 + **/ +ChafaTermDb * +chafa_term_db_get_default (void) +{ + static GOnce my_once = G_ONCE_INIT; + + g_once (&my_once, (GThreadFunc) instantiate_singleton, NULL); + return my_once.retval; +} + +/** + * chafa_term_db_detect: + * @term_db: A #ChafaTermDb. + * @envp: A strv of environment variables. + * + * Builds a new #ChafaTermInfo with capabilities implied by the provided + * environment variables (principally the TERM variable, but also others). + * + * @envp can be gotten from g_get_environ(). + * + * Returns: A new #ChafaTermInfo. + * + * Since: 1.6 + **/ +ChafaTermInfo * +chafa_term_db_detect (ChafaTermDb *term_db, gchar **envp) +{ + ChafaTermInfo *ti; + + g_return_val_if_fail (term_db != NULL, NULL); + + ti = chafa_term_info_new (); + detect_capabilities (ti, envp); + return ti; +} + +/** + * chafa_term_db_get_fallback_info: + * @term_db: A #ChafaTermDb. + * + * Builds a new #ChafaTermInfo with fallback control sequences. This + * can be used with unknown but presumably modern terminals, or to + * supplement missing capabilities in a detected terminal. + * + * Fallback control sequences may cause unpredictable behavior and + * should only be used as a last resort. + * + * Returns: A new #ChafaTermInfo. + * + * Since: 1.6 + **/ +ChafaTermInfo * +chafa_term_db_get_fallback_info (ChafaTermDb *term_db) +{ + ChafaTermInfo *ti; + + g_return_val_if_fail (term_db != NULL, NULL); + + ti = chafa_term_info_new (); + add_seq_list (ti, fallback_list); + + return ti; +} diff -Nru chafa-1.2.1/chafa/chafa-term-db.h chafa-1.12.4/chafa/chafa-term-db.h --- chafa-1.2.1/chafa/chafa-term-db.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-term-db.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_TERM_DB_H__ +#define __CHAFA_TERM_DB_H__ + +#if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) +# error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +typedef struct ChafaTermDb ChafaTermDb; + +CHAFA_AVAILABLE_IN_1_6 +ChafaTermDb *chafa_term_db_new (void); +CHAFA_AVAILABLE_IN_1_6 +ChafaTermDb *chafa_term_db_copy (const ChafaTermDb *term_db); +CHAFA_AVAILABLE_IN_1_6 +void chafa_term_db_ref (ChafaTermDb *term_db); +CHAFA_AVAILABLE_IN_1_6 +void chafa_term_db_unref (ChafaTermDb *term_db); + +CHAFA_AVAILABLE_IN_1_6 +ChafaTermDb *chafa_term_db_get_default (void); + +CHAFA_AVAILABLE_IN_1_6 +ChafaTermInfo *chafa_term_db_detect (ChafaTermDb *term_db, gchar **envp); +CHAFA_AVAILABLE_IN_1_6 +ChafaTermInfo *chafa_term_db_get_fallback_info (ChafaTermDb *term_db); + +G_END_DECLS + +#endif /* __CHAFA_TERM_DB_H__ */ diff -Nru chafa-1.2.1/chafa/chafa-term-info.c chafa-1.12.4/chafa/chafa-term-info.c --- chafa-1.2.1/chafa/chafa-term-info.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-term-info.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,682 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "chafa.h" +#include "internal/chafa-private.h" +#include "internal/chafa-string-util.h" + +/** + * SECTION:chafa-term-info + * @title: ChafaTermInfo + * @short_description: Describes a particular terminal type + * + * A #ChafaTermInfo describes the characteristics of one particular kind + * of display terminal. It stores control sequences that can be used to + * move the cursor, change text attributes, mark the beginning and end of + * sixel graphics data, etc. + * + * #ChafaTermInfo also implements an efficient low-level API for formatting + * these sequences with marshaled arguments so they can be sent to the + * terminal. + **/ + +/** + * ChafaTermSeq: + * @CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT: Reset the terminal to configured defaults. + * @CHAFA_TERM_SEQ_RESET_TERMINAL_HARD: Reset the terminal to factory defaults. + * @CHAFA_TERM_SEQ_RESET_ATTRIBUTES: Reset active graphics rendition (colors and other attributes) to terminal defaults. + * @CHAFA_TERM_SEQ_CLEAR: Clear the screen. + * @CHAFA_TERM_SEQ_INVERT_COLORS: Invert foreground and background colors (disable with RESET_ATTRIBUTES). + * @CHAFA_TERM_SEQ_CURSOR_TO_TOP_LEFT: Move cursor to top left of screen. + * @CHAFA_TERM_SEQ_CURSOR_TO_BOTTOM_LEFT: Move cursor to bottom left of screen. + * @CHAFA_TERM_SEQ_CURSOR_TO_POS: Move cursor to specific position. + * @CHAFA_TERM_SEQ_CURSOR_UP_1: Move cursor up one cell. + * @CHAFA_TERM_SEQ_CURSOR_UP: Move cursor up N cells. + * @CHAFA_TERM_SEQ_CURSOR_DOWN_1: Move cursor down one cell. + * @CHAFA_TERM_SEQ_CURSOR_DOWN: Move cursor down N cells. + * @CHAFA_TERM_SEQ_CURSOR_LEFT_1: Move cursor left one cell. + * @CHAFA_TERM_SEQ_CURSOR_LEFT: Move cursor left N cells. + * @CHAFA_TERM_SEQ_CURSOR_RIGHT_1: Move cursor right one cell. + * @CHAFA_TERM_SEQ_CURSOR_RIGHT: Move cursor right N cells. + * @CHAFA_TERM_SEQ_CURSOR_UP_SCROLL: Move cursor up one cell. Scroll area contents down when at the edge. + * @CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL: Move cursor down one cell. Scroll area contents up when at the edge. + * @CHAFA_TERM_SEQ_INSERT_CELLS: Insert blank cells at cursor position. + * @CHAFA_TERM_SEQ_DELETE_CELLS: Delete cells at cursor position. + * @CHAFA_TERM_SEQ_INSERT_ROWS: Insert rows at cursor position. + * @CHAFA_TERM_SEQ_DELETE_ROWS: Delete rows at cursor position. + * @CHAFA_TERM_SEQ_SET_SCROLLING_ROWS: Set scrolling area extents. + * @CHAFA_TERM_SEQ_ENABLE_INSERT: Enable insert mode. + * @CHAFA_TERM_SEQ_DISABLE_INSERT: Disable insert mode. + * @CHAFA_TERM_SEQ_ENABLE_CURSOR: Show the cursor. + * @CHAFA_TERM_SEQ_DISABLE_CURSOR: Hide the cursor. + * @CHAFA_TERM_SEQ_ENABLE_ECHO: Make the terminal echo input locally. + * @CHAFA_TERM_SEQ_DISABLE_ECHO: Don't echo input locally. + * @CHAFA_TERM_SEQ_ENABLE_WRAP: Make cursor wrap around to the next row after output in the final column. + * @CHAFA_TERM_SEQ_DISABLE_WRAP: Make cursor stay in place after output to the final column. + * @CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT: Set foreground color (directcolor/truecolor). + * @CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT: Set background color (directcolor/truecolor). + * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT: Set foreground and background color (directcolor/truecolor). + * @CHAFA_TERM_SEQ_SET_COLOR_FG_256: Set foreground color (256 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_BG_256: Set background color (256 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_256: Set foreground and background colors (256 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_FG_16: Set foreground color (16 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_BG_16: Set background color (16 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_16: Set foreground and background colors (16 colors). + * @CHAFA_TERM_SEQ_BEGIN_SIXELS: Begin sixel image data. + * @CHAFA_TERM_SEQ_END_SIXELS: End sixel image data. + * @CHAFA_TERM_SEQ_REPEAT_CHAR: Repeat previous character N times. + * @CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1: Begin upload of Kitty image for immediate display at cursor. + * @CHAFA_TERM_SEQ_END_KITTY_IMAGE: End of Kitty image upload. + * @CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK: Begin Kitty image data chunk. + * @CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK: End Kitty image data chunk. + * @CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE: Begin iTerm2 image data. + * @CHAFA_TERM_SEQ_END_ITERM2_IMAGE: End of iTerm2 image data. + * @CHAFA_TERM_SEQ_ENABLE_SIXEL_SCROLLING: Enable sixel scrolling. + * @CHAFA_TERM_SEQ_DISABLE_SIXEL_SCROLLING: Disable sixel scrolling. + * @CHAFA_TERM_SEQ_ENABLE_BOLD: Enable boldface (disable with RESET_ATTRIBUTES). + * @CHAFA_TERM_SEQ_SET_COLOR_FG_8: Set foreground color (8 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_BG_8: Set background color (8 colors). + * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_8: Set foreground and background colors (8 colors). + * @CHAFA_TERM_SEQ_MAX: Last control sequence plus one. + * + * An enumeration of the control sequences supported by #ChafaTermInfo. + **/ + +/* Maximum number of arguments + 1 for the sentinel */ +#define CHAFA_TERM_SEQ_ARGS_MAX 8 +#define ARG_INDEX_SENTINEL 255 + +typedef struct +{ + guint8 pre_len; + guint8 arg_index; +} +SeqArgInfo; + +struct ChafaTermInfo +{ + gint refs; + gchar seq_str [CHAFA_TERM_SEQ_MAX] [CHAFA_TERM_SEQ_LENGTH_MAX]; + SeqArgInfo seq_args [CHAFA_TERM_SEQ_MAX] [CHAFA_TERM_SEQ_ARGS_MAX]; + gchar *unparsed_str [CHAFA_TERM_SEQ_MAX]; +}; + +typedef struct +{ + guint n_args; + guint type_size; +} +SeqMeta; + +static const SeqMeta seq_meta [CHAFA_TERM_SEQ_MAX] = +{ +#define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ + [CHAFA_TERM_SEQ_##NAME] = { n_args, sizeof (arg_type) }, +#include +#undef CHAFA_TERM_SEQ_DEF +}; + +/* Avoid memcpy() because it inlines to a sizeable amount of code (it + * doesn't know our strings are always short). We also take the liberty + * of unconditionally copying a byte even if n=0. This simplifies the + * generated assembly quite a bit. */ +static inline void +copy_bytes (gchar *out, const gchar *in, gint n) +{ + gint i = 0; + + do + { + *(out++) = *(in++); + i++; + } + while (i < n); +} + +static gboolean +parse_seq_args (gchar *out, SeqArgInfo *arg_info, const gchar *in, + gint n_args, gint arg_len_max, GError **error) +{ + gint i, j, k; + gint pre_len = 0; + gint result = FALSE; + + g_assert (n_args < CHAFA_TERM_SEQ_ARGS_MAX); + + for (k = 0; k < CHAFA_TERM_SEQ_ARGS_MAX; k++) + { + arg_info [k].pre_len = 0; + arg_info [k].arg_index = ARG_INDEX_SENTINEL; + } + + for (i = 0, j = 0, k = 0; + j < CHAFA_TERM_SEQ_LENGTH_MAX && k < CHAFA_TERM_SEQ_ARGS_MAX && in [i]; + i++) + { + gchar c = in [i]; + + if (c == '%') + { + i++; + c = in [i]; + + if (c == '%') + { + /* "%%" -> literal "%" */ + out [j++] = '%'; + pre_len++; + } + else if (c >= '1' && c <= '8') /* arg # 0-7 */ + { + /* "%n" -> argument n-1 */ + + arg_info [k].arg_index = c - '1'; + arg_info [k].pre_len = pre_len; + + if (arg_info [k].arg_index >= n_args) + { + /* Bad argument index (out of range) */ + g_set_error (error, CHAFA_TERM_INFO_ERROR, CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS, + "Control sequence had too many arguments."); + goto out; + } + + pre_len = 0; + k++; + } + else + { + /* Bad "%?" escape */ + goto out; + } + } + else + { + out [j++] = c; + pre_len++; + } + } + + if (k == CHAFA_TERM_SEQ_ARGS_MAX) + { + /* Too many argument expansions */ + g_set_error (error, CHAFA_TERM_INFO_ERROR, CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS, + "Control sequence had too many arguments."); + goto out; + } + + /* Reserve an extra byte for copy_byte() and chafa_format_dec_u8() excess. */ + if (j + k * arg_len_max + 1 > CHAFA_TERM_SEQ_LENGTH_MAX) + { + /* Formatted string may be too long */ + g_set_error (error, CHAFA_TERM_INFO_ERROR, CHAFA_TERM_INFO_ERROR_SEQ_TOO_LONG, + "Control sequence too long."); + goto out; + } + + arg_info [k].pre_len = pre_len; + arg_info [k].arg_index = ARG_INDEX_SENTINEL; + + result = TRUE; + +out: + return result; +} + +#define EMIT_SEQ_DEF(inttype, intformatter) \ + static gchar * \ + emit_seq_##inttype (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, \ + inttype *args, gint n_args) \ + { \ + const gchar *seq_str; \ + const SeqArgInfo *seq_args; \ + gint ofs = 0; \ + gint i; \ + \ + seq_str = &term_info->seq_str [seq] [0]; \ + seq_args = &term_info->seq_args [seq] [0]; \ + \ + if (seq_args [0].arg_index == ARG_INDEX_SENTINEL) \ + return out; \ + \ + for (i = 0; i < n_args; i++) \ + { \ + copy_bytes (out, &seq_str [ofs], seq_args [i].pre_len); \ + out += seq_args [i].pre_len; \ + ofs += seq_args [i].pre_len; \ + out = intformatter (out, args [seq_args [i].arg_index]); \ + } \ + \ + copy_bytes (out, &seq_str [ofs], seq_args [i].pre_len); \ + out += seq_args [i].pre_len; \ + \ + return out; \ + } + +EMIT_SEQ_DEF(guint, chafa_format_dec_uint_0_to_9999) +EMIT_SEQ_DEF(guint8, chafa_format_dec_u8) + +static gchar * +emit_seq_0_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq) +{ + copy_bytes (out, &term_info->seq_str [seq] [0], term_info->seq_args [seq] [0].pre_len); + return out + term_info->seq_args [seq] [0].pre_len; +} + +static gchar * +emit_seq_1_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0) +{ + return emit_seq_guint (term_info, out, seq, &arg0, 1); +} + +static gchar * +emit_seq_1_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint8 arg0) +{ + return emit_seq_guint8 (term_info, out, seq, &arg0, 1); +} + +static gchar * +emit_seq_2_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1) +{ + guint args [2]; + + args [0] = arg0; + args [1] = arg1; + return emit_seq_guint (term_info, out, seq, args, 2); +} + +static gchar * +emit_seq_2_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1) +{ + guint8 args [2]; + + args [0] = arg0; + args [1] = arg1; + return emit_seq_guint8 (term_info, out, seq, args, 2); +} + +static gchar * +emit_seq_3_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1, guint arg2) +{ + guint args [3]; + + args [0] = arg0; + args [1] = arg1; + args [2] = arg2; + return emit_seq_guint (term_info, out, seq, args, 3); +} + +static gchar * +emit_seq_5_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1, guint arg2, guint arg3, guint arg4) +{ + guint args [5]; + + args [0] = arg0; + args [1] = arg1; + args [2] = arg2; + args [3] = arg3; + args [4] = arg4; + return emit_seq_guint (term_info, out, seq, args, 5); +} + +static gchar * +emit_seq_3_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint8 arg0, guint8 arg1, guint8 arg2) +{ + guint8 args [3]; + + args [0] = arg0; + args [1] = arg1; + args [2] = arg2; + return emit_seq_guint8 (term_info, out, seq, args, 3); +} + +static gchar * +emit_seq_6_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint8 arg0, guint8 arg1, guint8 arg2, guint8 arg3, guint8 arg4, guint8 arg5) +{ + guint8 args [6]; + + args [0] = arg0; + args [1] = arg1; + args [2] = arg2; + args [3] = arg3; + args [4] = arg4; + args [5] = arg5; + return emit_seq_guint8 (term_info, out, seq, args, 6); +} + +/* Public */ + +G_DEFINE_QUARK (chafa-term-info-error-quark, chafa_term_info_error) + +/** + * chafa_term_info_new: + * + * Creates a new, blank #ChafaTermInfo. + * + * Returns: The new #ChafaTermInfo + * + * Since: 1.6 + **/ +ChafaTermInfo * +chafa_term_info_new (void) +{ + ChafaTermInfo *term_info; + gint i; + + term_info = g_new0 (ChafaTermInfo, 1); + term_info->refs = 1; + + for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) + { + term_info->seq_args [i] [0].arg_index = ARG_INDEX_SENTINEL; + } + + return term_info; +} + +/** + * chafa_term_info_copy: + * @term_info: A #ChafaTermInfo to copy. + * + * Creates a new #ChafaTermInfo that's a copy of @term_info. + * + * Returns: The new #ChafaTermInfo + * + * Since: 1.6 + **/ +ChafaTermInfo * +chafa_term_info_copy (const ChafaTermInfo *term_info) +{ + ChafaTermInfo *new_term_info; + gint i; + + g_return_val_if_fail (term_info != NULL, NULL); + + new_term_info = g_new (ChafaTermInfo, 1); + memcpy (new_term_info, term_info, sizeof (ChafaTermInfo)); + new_term_info->refs = 1; + + for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) + { + if (new_term_info->unparsed_str [i]) + new_term_info->unparsed_str [i] = g_strdup (new_term_info->unparsed_str [i]); + } + + return new_term_info; +} + +/** + * chafa_term_info_ref: + * @term_info: #ChafaTermInfo to add a reference to. + * + * Adds a reference to @term_info. + * + * Since: 1.6 + **/ +void +chafa_term_info_ref (ChafaTermInfo *term_info) +{ + gint refs; + + g_return_if_fail (term_info != NULL); + refs = g_atomic_int_get (&term_info->refs); + g_return_if_fail (refs > 0); + + g_atomic_int_inc (&term_info->refs); +} + +/** + * chafa_term_info_unref: + * @term_info: #ChafaTermInfo to remove a reference from. + * + * Removes a reference from @term_info. + * + * Since: 1.6 + **/ +void +chafa_term_info_unref (ChafaTermInfo *term_info) +{ + gint refs; + + g_return_if_fail (term_info != NULL); + refs = g_atomic_int_get (&term_info->refs); + g_return_if_fail (refs > 0); + + if (g_atomic_int_dec_and_test (&term_info->refs)) + { + gint i; + + for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) + g_free (term_info->unparsed_str [i]); + + g_free (term_info); + } +} + +/** + * chafa_term_info_have_seq: + * @term_info: A #ChafaTermInfo. + * @seq: A #ChafaTermSeq to query for. + * + * Checks if @term_info can emit @seq. + * + * Returns: %TRUE if @seq can be emitted, %FALSE otherwise + * + * Since: 1.6 + **/ +gboolean +chafa_term_info_have_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq) +{ + g_return_val_if_fail (term_info != NULL, FALSE); + g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, FALSE); + + return term_info->unparsed_str [seq] ? TRUE : FALSE; +} + +/** + * chafa_term_info_get_seq: + * @term_info: A #ChafaTermInfo. + * @seq: A #ChafaTermSeq to query for. + * + * Gets the string equivalent of @seq stored in @term_info. + * + * Returns: An unformatted string sequence, or %NULL if not set. + * + * Since: 1.6 + **/ +const gchar * +chafa_term_info_get_seq (ChafaTermInfo *term_info, ChafaTermSeq seq) +{ + g_return_val_if_fail (term_info != NULL, NULL); + g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, NULL); + + return term_info->unparsed_str [seq]; +} + +/** + * chafa_term_info_set_seq: + * @term_info: A #ChafaTermInfo. + * @seq: A #ChafaTermSeq to query for. + * @str: A control sequence string, or %NULL to clear. + * @error: A return location for error details, or %NULL. + * + * Sets the control sequence string equivalent of @seq stored in @term_info to @str. + * + * The string may contain argument indexes to be substituted with integers on + * formatting. The indexes are preceded by a percentage character and start at 1, + * i.e. \%1, \%2, \%3, etc. + * + * The string's length after formatting must not exceed %CHAFA_TERM_SEQ_LENGTH_MAX + * bytes. Each argument can add up to four digits, or three for those specified as + * 8-bit integers. If the string could potentially exceed this length when + * formatted, chafa_term_info_set_seq() will return %FALSE. + * + * If parsing fails or @str is too long, any previously existing sequence + * will be left untouched. + * + * Passing %NULL for @str clears the corresponding control sequence. + * + * Returns: %TRUE if parsing succeeded, %FALSE otherwise + * + * Since: 1.6 + **/ +gboolean +chafa_term_info_set_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, const gchar *str, + GError **error) +{ + gchar seq_str [CHAFA_TERM_SEQ_LENGTH_MAX]; + SeqArgInfo seq_args [CHAFA_TERM_SEQ_ARGS_MAX]; + gboolean result = FALSE; + + g_return_val_if_fail (term_info != NULL, FALSE); + g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, FALSE); + + if (!str) + { + term_info->seq_str [seq] [0] = '\0'; + term_info->seq_args [seq] [0].pre_len = 0; + term_info->seq_args [seq] [0].arg_index = ARG_INDEX_SENTINEL; + + g_free (term_info->unparsed_str [seq]); + term_info->unparsed_str [seq] = NULL; + result = TRUE; + } + else + { + result = parse_seq_args (&seq_str [0], &seq_args [0], str, + seq_meta [seq].n_args, + seq_meta [seq].type_size == 1 ? 3 : 4, + error); + + if (result == TRUE) + { + memcpy (&term_info->seq_str [seq] [0], &seq_str [0], + CHAFA_TERM_SEQ_LENGTH_MAX); + memcpy (&term_info->seq_args [seq] [0], &seq_args [0], + CHAFA_TERM_SEQ_ARGS_MAX * sizeof (SeqArgInfo)); + + g_free (term_info->unparsed_str [seq]); + term_info->unparsed_str [seq] = g_strdup (str); + } + } + + return result; +} + +/** + * chafa_term_info_supplement: + * @term_info: A #ChafaTermInfo to supplement + * @source: A #ChafaTermInfo to copy from + * + * Supplements missing sequences in @term_info with ones copied + * from @source. + * + * Since: 1.6 + **/ +void +chafa_term_info_supplement (ChafaTermInfo *term_info, ChafaTermInfo *source) +{ + gint i; + + g_return_if_fail (term_info != NULL); + g_return_if_fail (source != NULL); + + for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) + { + if (!term_info->unparsed_str [i] && source->unparsed_str [i]) + { + term_info->unparsed_str [i] = g_strdup (source->unparsed_str [i]); + memcpy (&term_info->seq_str [i] [0], &source->seq_str [i] [0], + CHAFA_TERM_SEQ_LENGTH_MAX); + memcpy (&term_info->seq_args [i] [0], &source->seq_args [i] [0], + CHAFA_TERM_SEQ_ARGS_MAX * sizeof (SeqArgInfo)); + } + } +} + +#define DEFINE_EMIT_SEQ_0_none_char(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest) \ +{ return emit_seq_0_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name); } + +#define DEFINE_EMIT_SEQ_1_none_guint(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg) \ +{ return emit_seq_1_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg); } + +#define DEFINE_EMIT_SEQ_1_none_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ +{ return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg); } + +#define DEFINE_EMIT_SEQ_1_8fg_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ +{ return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg + 30); } + +#define DEFINE_EMIT_SEQ_1_8bg_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ +{ return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg + 40); } + +#define DEFINE_EMIT_SEQ_1_aix16fg_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ +{ return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg + (arg < 8 ? 30 : (90 - 8))); } + +#define DEFINE_EMIT_SEQ_1_aix16bg_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ +{ return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg + (arg < 8 ? 40 : (100 - 8))); } + +#define DEFINE_EMIT_SEQ_2_none_guint(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1) \ +{ return emit_seq_2_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1); } + +#define DEFINE_EMIT_SEQ_2_pos_guint(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1) \ +{ return emit_seq_2_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0 + 1, arg1 + 1); } + +#define DEFINE_EMIT_SEQ_2_none_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1) \ +{ return emit_seq_2_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1); } + +#define DEFINE_EMIT_SEQ_2_8fgbg_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1) \ +{ return emit_seq_2_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0 + 30, arg1 + 40); } + +#define DEFINE_EMIT_SEQ_2_aix16fgbg_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1) \ +{ return emit_seq_2_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0 + (arg0 < 8 ? 30 : (90 - 8)), arg1 + (arg1 < 8 ? 40 : (100 - 8))); } + +#define DEFINE_EMIT_SEQ_3_none_guint(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1, guint arg2) \ +{ return emit_seq_3_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2); } + +#define DEFINE_EMIT_SEQ_5_none_guint(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1, guint arg2, guint arg3, guint arg4) \ +{ return emit_seq_5_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2, arg3, arg4); } + +#define DEFINE_EMIT_SEQ_3_none_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1, guint8 arg2) \ +{ return emit_seq_3_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2); } + +#define DEFINE_EMIT_SEQ_6_none_guint8(func_name, seq_name) \ +gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1, guint8 arg2, guint8 arg3, guint8 arg4, guint8 arg5) \ +{ return emit_seq_6_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2, arg3, arg4, arg5); } + +#define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ + DEFINE_EMIT_SEQ_##n_args##_##arg_proc##_##arg_type(name, NAME) +#include "chafa-term-seq-def.h" +#undef CHAFA_TERM_SEQ_DEF diff -Nru chafa-1.2.1/chafa/chafa-term-info.h chafa-1.12.4/chafa/chafa-term-info.h --- chafa-1.2.1/chafa/chafa-term-info.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-term-info.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_TERM_INFO_H__ +#define __CHAFA_TERM_INFO_H__ + +#if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) +# error "Only can be included directly." +#endif + +G_BEGIN_DECLS + +#define CHAFA_TERM_SEQ_LENGTH_MAX 96 + +#ifndef __GTK_DOC_IGNORE__ +/* This declares the enum for CHAFA_TERM_SEQ_*. See chafa-term-seq-def.h + * for more information, or look up the canonical documentation at + * https://hpjansson.org/chafa/ref/ for verbose definitions. */ +typedef enum +{ +#define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) CHAFA_TERM_SEQ_##NAME, +#include +#undef CHAFA_TERM_SEQ_DEF + + CHAFA_TERM_SEQ_MAX +} +ChafaTermSeq; +#endif /* __GTK_DOC_IGNORE__ */ + +typedef struct ChafaTermInfo ChafaTermInfo; + +/** + * CHAFA_TERM_INFO_ERROR: + * + * Error domain for #ChafaTermInfo. Errors in this domain will + * be from the #ChafaTermInfoError enumeration. See #GError for information on + * error domains. + **/ +#define CHAFA_TERM_INFO_ERROR (chafa_term_info_error_quark ()) + +/** + * ChafaTermInfoError: + * @CHAFA_TERM_INFO_ERROR_SEQ_TOO_LONG: A control sequence could exceed + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes if formatted with maximum argument lengths. + * @CHAFA_TERM_INFO_ERROR_BAD_ESCAPE: An illegal escape sequence was used. + * @CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS: A control sequence specified + * more than the maximum number of arguments, or an argument index was out + * of range. + * + * Error codes returned by control sequence parsing. + **/ +typedef enum +{ + CHAFA_TERM_INFO_ERROR_SEQ_TOO_LONG, + CHAFA_TERM_INFO_ERROR_BAD_ESCAPE, + CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS +} +ChafaTermInfoError; + +CHAFA_AVAILABLE_IN_1_6 +GQuark chafa_term_info_error_quark (void); + +CHAFA_AVAILABLE_IN_1_6 +ChafaTermInfo *chafa_term_info_new (void); +CHAFA_AVAILABLE_IN_1_6 +ChafaTermInfo *chafa_term_info_copy (const ChafaTermInfo *term_info); +CHAFA_AVAILABLE_IN_1_6 +void chafa_term_info_ref (ChafaTermInfo *term_info); +CHAFA_AVAILABLE_IN_1_6 +void chafa_term_info_unref (ChafaTermInfo *term_info); + +CHAFA_AVAILABLE_IN_1_6 +const gchar *chafa_term_info_get_seq (ChafaTermInfo *term_info, ChafaTermSeq seq); +CHAFA_AVAILABLE_IN_1_6 +gint chafa_term_info_set_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, const gchar *str, + GError **error); +CHAFA_AVAILABLE_IN_1_6 +gboolean chafa_term_info_have_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq); + +CHAFA_AVAILABLE_IN_1_6 +void chafa_term_info_supplement (ChafaTermInfo *term_info, ChafaTermInfo *source); + +/* This declares the prototypes for chafa_term_info_emit_*(). See + * chafa-term-seq-def.h for more information, or look up the canonical + * documentation at https://hpjansson.org/chafa/ref/ for verbose + * function prototypes. */ +#define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ + CHAFA_TERM_SEQ_AVAILABILITY gchar * chafa_term_info_emit_##name(const ChafaTermInfo *term_info, gchar *dest __VA_ARGS__); +#include +#undef CHAFA_TERM_SEQ_DEF + +G_END_DECLS + +#endif /* __CHAFA_TERM_INFO_H__ */ diff -Nru chafa-1.2.1/chafa/chafa-term-seq-def.h chafa-1.12.4/chafa/chafa-term-seq-def.h --- chafa-1.2.1/chafa/chafa-term-seq-def.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-term-seq-def.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,1098 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* Terminal sequence definitions + * ----------------------------- + * + * This file is #included in various contexts with CHAFA_TERM_SEQ_DEF() + * expanding to different things. It allows us to keep all the terminal + * sequence metadata in one place. + * + * We process this file with 'cpp -CC' to let the docstrings through to + * gtk-doc. + * + * The generator macro is invoked with the following arguments: + * + * CHAFA_TERM_SEQ_DEF (name, NAME, n_args, args_proc, args_type, ...) + * + * Sequences are grouped by the library version they became available in, + * with CHAFA_TERM_SEQ_AVAILABILITY expanding to the appropriate version + * macro in each case. + * + * The actual sequence strings are not defined here; they belong to the + * individual terminal model definitions. + * + * References + * ---------- + * + * VT220 sequences: https://vt100.net/docs/vt220-rm/chapter4.html + * Sixels: https://vt100.net/docs/vt3xx-gp/chapter14.html + */ + +/* For zero-argument functions, we use "char" as the argument type instead + * of the more appropriate "void", since we need to be able to use it with + * sizeof() and -Wpointer-arith. */ + +/* __VA_OPT__ from C++2a would be nice, but it's too recent to rely on in + * public headers just yet. So we have this exciting trick instead. */ +#define CHAFA_TERM_SEQ_ARGS , + +/* --- Available in 1.6+ --- */ + +#define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_6 + +/** + * chafa_term_info_emit_reset_terminal_soft: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(reset_terminal_soft, RESET_TERMINAL_SOFT, 0, none, char) + +/** + * chafa_term_info_emit_reset_terminal_hard: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_TERMINAL_HARD. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(reset_terminal_hard, RESET_TERMINAL_HARD, 0, none, char) + +/** + * chafa_term_info_emit_reset_attributes: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_ATTRIBUTES. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(reset_attributes, RESET_ATTRIBUTES, 0, none, char) + +/** + * chafa_term_info_emit_clear: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CLEAR. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(clear, CLEAR, 0, none, char) + +/** + * chafa_term_info_emit_invert_colors: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_INVERT_COLORS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(invert_colors, INVERT_COLORS, 0, none, char) + +/* Cursor movement. Cursor stops at margins. */ + +/** + * chafa_term_info_emit_cursor_to_top_left: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_TO_TOP_LEFT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_to_top_left, CURSOR_TO_TOP_LEFT, 0, none, char) + +/** + * chafa_term_info_emit_cursor_to_bottom_left: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_TO_BOTTOM_LEFT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_to_bottom_left, CURSOR_TO_BOTTOM_LEFT, 0, none, char) + +/** + * chafa_term_info_emit_cursor_to_pos: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @x: Offset from left edge of display, zero-indexed + * @y: Offset from top edge of display, zero-indexed + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_TO_POS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_to_pos, CURSOR_TO_POS, 2, pos, guint, CHAFA_TERM_SEQ_ARGS guint x, guint y) + +/** + * chafa_term_info_emit_cursor_up_1: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_UP_1. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_up_1, CURSOR_UP_1, 0, none, char) + +/** + * chafa_term_info_emit_cursor_up: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Distance to move the cursor + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_UP. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_up, CURSOR_UP, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/** + * chafa_term_info_emit_cursor_down_1: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_DOWN_1. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_down_1, CURSOR_DOWN_1, 0, none, char) + +/** + * chafa_term_info_emit_cursor_down: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Distance to move the cursor + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_DOWN. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_down, CURSOR_DOWN, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/** + * chafa_term_info_emit_cursor_left_1: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_LEFT_1. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_left_1, CURSOR_LEFT_1, 0, none, char) + +/** + * chafa_term_info_emit_cursor_left: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Distance to move the cursor + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_LEFT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_left, CURSOR_LEFT, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/** + * chafa_term_info_emit_cursor_right_1: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_RIGHT_1. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_right_1, CURSOR_RIGHT_1, 0, none, char) + +/** + * chafa_term_info_emit_cursor_right: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Distance to move the cursor + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_RIGHT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_right, CURSOR_RIGHT, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/* Cursor movement. Cursor crossing margin causes scrolling region to + * scroll. */ + +/** + * chafa_term_info_emit_cursor_up_scroll: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_UP_SCROLL. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_up_scroll, CURSOR_UP_SCROLL, 0, none, char) + +/** + * chafa_term_info_emit_cursor_down_scroll: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(cursor_down_scroll, CURSOR_DOWN_SCROLL, 0, none, char) + +/* Cells will shift on insert. Cells shifted off the edge will be lost. */ + +/** + * chafa_term_info_emit_insert_cells: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Number of cells to insert + * + * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_CELLS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(insert_cells, INSERT_CELLS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/** + * chafa_term_info_emit_delete_cells: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Number of cells to delete + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_CELLS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(delete_cells, DELETE_CELLS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/* Cursor must be inside scrolling region. Rows are shifted inside the + * scrolling region. Rows shifted off the edge will be lost. The cursor + * position is reset to the first column. */ + +/** + * chafa_term_info_emit_insert_rows: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Number of rows to insert + * + * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_ROWS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(insert_rows, INSERT_ROWS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/** + * chafa_term_info_emit_delete_rows: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Number of rows to delete + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_ROWS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(delete_rows, DELETE_ROWS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/* Defines the scrolling region. */ + +/** + * chafa_term_info_emit_set_scrolling_rows: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @top: First row in scrolling area, zero-indexed + * @bottom: Last row in scrolling area, zero-indexed + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_SCROLLING_ROWS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_scrolling_rows, SET_SCROLLING_ROWS, 2, pos, guint, CHAFA_TERM_SEQ_ARGS guint top, guint bottom) + +/* Indicates whether characters printed in the middle of a row should + * cause subsequent cells to shift forwards. Cells shifted off the edge + * will be lost. If disabled, cells at the cursor position will be + * overwritten instead. */ + +/** + * chafa_term_info_emit_enable_insert: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_INSERT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(enable_insert, ENABLE_INSERT, 0, none, char) + +/** + * chafa_term_info_emit_disable_insert: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_INSERT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(disable_insert, DISABLE_INSERT, 0, none, char) + +/** + * chafa_term_info_emit_enable_cursor: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_CURSOR. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(enable_cursor, ENABLE_CURSOR, 0, none, char) + +/** + * chafa_term_info_emit_disable_cursor: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_CURSOR. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(disable_cursor, DISABLE_CURSOR, 0, none, char) + +/** + * chafa_term_info_emit_enable_echo: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_ECHO. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(enable_echo, ENABLE_ECHO, 0, none, char) + +/** + * chafa_term_info_emit_disable_echo: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_ECHO. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(disable_echo, DISABLE_ECHO, 0, none, char) + +/* When printing a character in the last column, indicates whether the + * cursor should move to the next row and potentially cause scrolling. If + * disabled, the cursor may still move to the first column. */ + +/** + * chafa_term_info_emit_enable_wrap: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_WRAP. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(enable_wrap, ENABLE_WRAP, 0, none, char) + +/** + * chafa_term_info_emit_disable_wrap: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_WRAP. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(disable_wrap, DISABLE_WRAP, 0, none, char) + +/** + * chafa_term_info_emit_set_color_fg_direct: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @r: Red component, 0-255 + * @g: Green component, 0-255 + * @b: Blue component, 0-255 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fg_direct, SET_COLOR_FG_DIRECT, 3, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 r, guint8 g, guint8 b) + +/** + * chafa_term_info_emit_set_color_bg_direct: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @r: Red component, 0-255 + * @g: Green component, 0-255 + * @b: Blue component, 0-255 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_bg_direct, SET_COLOR_BG_DIRECT, 3, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 r, guint8 g, guint8 b) + +/** + * chafa_term_info_emit_set_color_fgbg_direct: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @fg_r: Foreground red component, 0-255 + * @fg_g: Foreground green component, 0-255 + * @fg_b: Foreground blue component, 0-255 + * @bg_r: Background red component, 0-255 + * @bg_g: Background green component, 0-255 + * @bg_b: Background blue component, 0-255 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fgbg_direct, SET_COLOR_FGBG_DIRECT, 6, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_r, guint8 fg_g, guint8 fg_b, guint8 bg_r, guint8 bg_g, guint8 bg_b) + +/** + * chafa_term_info_emit_set_color_fg_256: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @pen: Pen number, 0-255 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_256. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fg_256, SET_COLOR_FG_256, 1, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) + +/** + * chafa_term_info_emit_set_color_bg_256: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @pen: Pen number, 0-255 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_256. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_bg_256, SET_COLOR_BG_256, 1, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) + +/** + * chafa_term_info_emit_set_color_fgbg_256: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @fg_pen: Foreground pen number, 0-255 + * @bg_pen: Background pen number, 0-255 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_256. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fgbg_256, SET_COLOR_FGBG_256, 2, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_pen, guint8 bg_pen) + +/** + * chafa_term_info_emit_set_color_fg_16: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @pen: Pen number, 0-15 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_16. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fg_16, SET_COLOR_FG_16, 1, aix16fg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) + +/** + * chafa_term_info_emit_set_color_bg_16: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @pen: Pen number, 0-15 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_16. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_bg_16, SET_COLOR_BG_16, 1, aix16bg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) + +/** + * chafa_term_info_emit_set_color_fgbg_16: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @fg_pen: Foreground pen number, 0-15 + * @bg_pen: Background pen number, 0-15 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_16. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fgbg_16, SET_COLOR_FGBG_16, 2, aix16fgbg, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_pen, guint8 bg_pen) + +/** + * chafa_term_info_emit_begin_sixels: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @p1: Pixel aspect selector + * @p2: Background color selector + * @p3: Horizontal grid selector + * + * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_SIXELS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * All three parameters (@p1, @p2 and @p3) can normally be set to 0. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(begin_sixels, BEGIN_SIXELS, 3, none, guint, CHAFA_TERM_SEQ_ARGS guint p1, guint p2, guint p3) + +/** + * chafa_term_info_emit_end_sixels: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_END_SIXELS. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(end_sixels, END_SIXELS, 0, none, char) + +/** + * chafa_term_info_emit_repeat_char: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @n: Number of repetitions + * + * Prints the control sequence for #CHAFA_TERM_SEQ_REPEAT_CHAR. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.6 + **/ +CHAFA_TERM_SEQ_DEF(repeat_char, REPEAT_CHAR, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) + +/* --- Available in 1.8+ --- */ + +#undef CHAFA_TERM_SEQ_AVAILABILITY +#define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_8 + +/** + * chafa_term_info_emit_begin_kitty_immediate_image_v1: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @bpp: Bits per pixel + * @width_pixels: Image width in pixels + * @height_pixels: Image height in pixels + * @width_cells: Target width in cells + * @height_cells: Target height in cells + * + * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * @bpp must be set to either 24 for RGB data, 32 for RGBA, or 100 to embed a + * PNG file. + * + * This sequence must be followed by zero or more paired sequences of + * type #CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK and #CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK + * with base-64 encoded image data between them. + * + * When the image data has been transferred, #CHAFA_TERM_SEQ_END_KITTY_IMAGE must + * be emitted. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.8 + **/ +CHAFA_TERM_SEQ_DEF(begin_kitty_immediate_image_v1, BEGIN_KITTY_IMMEDIATE_IMAGE_V1, 5, none, guint, CHAFA_TERM_SEQ_ARGS guint bpp, guint width_pixels, guint height_pixels, guint width_cells, guint height_cells) + +/** + * chafa_term_info_emit_end_kitty_image: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_END_KITTY_IMAGE. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.8 + **/ +CHAFA_TERM_SEQ_DEF(end_kitty_image, END_KITTY_IMAGE, 0, none, char) + +/** + * chafa_term_info_emit_begin_kitty_image_chunk: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.8 + **/ +CHAFA_TERM_SEQ_DEF(begin_kitty_image_chunk, BEGIN_KITTY_IMAGE_CHUNK, 0, none, char) + +/** + * chafa_term_info_emit_end_kitty_image_chunk: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.8 + **/ +CHAFA_TERM_SEQ_DEF(end_kitty_image_chunk, END_KITTY_IMAGE_CHUNK, 0, none, char) + +/** + * chafa_term_info_emit_begin_iterm2_image: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @width: Image width in character cells + * @height: Image height in character cells + * + * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * This sequence must be followed by base64-encoded image file data. The image + * can be any format supported by MacOS, e.g. PNG, JPEG, TIFF, GIF. When the + * image data has been transferred, #CHAFA_TERM_SEQ_END_ITERM2_IMAGE must be + * emitted. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.8 + **/ +CHAFA_TERM_SEQ_DEF(begin_iterm2_image, BEGIN_ITERM2_IMAGE, 2, none, guint, CHAFA_TERM_SEQ_ARGS guint width, guint height) + +/** + * chafa_term_info_emit_end_iterm2_image: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_END_ITERM2_IMAGE. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.8 + **/ +CHAFA_TERM_SEQ_DEF(end_iterm2_image, END_ITERM2_IMAGE, 0, none, char) + +/* --- Available in 1.10+ --- */ + +#undef CHAFA_TERM_SEQ_AVAILABILITY +#define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_10 + +/** + * chafa_term_info_emit_enable_sixel_scrolling: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_SIXEL_SCROLLING. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.10 + **/ +CHAFA_TERM_SEQ_DEF(enable_sixel_scrolling, ENABLE_SIXEL_SCROLLING, 0, none, char) + +/** + * chafa_term_info_emit_disable_sixel_scrolling: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_SIXEL_SCROLLING. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.10 + **/ +CHAFA_TERM_SEQ_DEF(disable_sixel_scrolling, DISABLE_SIXEL_SCROLLING, 0, none, char) + +/* --- Available in 1.12+ --- */ + +#undef CHAFA_TERM_SEQ_AVAILABILITY +#define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_12 + +/** + * chafa_term_info_emit_enable_bold: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * + * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_BOLD. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.12 + **/ +CHAFA_TERM_SEQ_DEF(enable_bold, ENABLE_BOLD, 0, none, char) + +/** + * chafa_term_info_emit_set_color_fg_8: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @pen: Pen number, 0-7 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_8. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.12 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fg_8, SET_COLOR_FG_8, 1, 8fg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) + +/** + * chafa_term_info_emit_set_color_bg_8: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @pen: Pen number, 0-7 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_8. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.12 + **/ +CHAFA_TERM_SEQ_DEF(set_color_bg_8, SET_COLOR_BG_8, 1, 8bg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) + +/** + * chafa_term_info_emit_set_color_fgbg_8: + * @term_info: A #ChafaTermInfo + * @dest: String destination + * @fg_pen: Foreground pen number, 0-7 + * @bg_pen: Background pen number, 0-7 + * + * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_8. + * + * @dest must have enough space to hold + * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is + * shorter. The output will not be zero-terminated. + * + * Returns: Pointer to first byte after emitted string + * + * Since: 1.12 + **/ +CHAFA_TERM_SEQ_DEF(set_color_fgbg_8, SET_COLOR_FGBG_8, 2, 8fgbg, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_pen, guint8 bg_pen) + +#undef CHAFA_TERM_SEQ_AVAILABILITY + +#undef CHAFA_TERM_SEQ_ARGS diff -Nru chafa-1.2.1/chafa/chafa-term-seq-doc-in.h chafa-1.12.4/chafa/chafa-term-seq-doc-in.h --- chafa-1.2.1/chafa/chafa-term-seq-doc-in.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-term-seq-doc-in.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ + gchar *chafa_term_info_emit_##name (const ChafaTermInfo *term_info, gchar *dest __VA_ARGS__); +#include "chafa-term-seq-def.h" +#undef CHAFA_TERM_SEQ_DEF + +typedef enum +{ +#define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) CHAFA_TERM_SEQ_##NAME, +#include "chafa-term-seq-def.h" +#undef CHAFA_TERM_SEQ_DEF + + CHAFA_TERM_SEQ_MAX +} +ChafaTermSeq; diff -Nru chafa-1.2.1/chafa/chafa-util.c chafa-1.12.4/chafa/chafa-util.c --- chafa-1.2.1/chafa/chafa-util.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-util.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -19,8 +19,8 @@ #include "config.h" -#include "chafa/chafa.h" -#include "chafa/chafa-private.h" +#include "chafa.h" +#include "internal/chafa-private.h" /** * SECTION:chafa-util @@ -69,7 +69,7 @@ g_return_if_fail (src_width >= 0); g_return_if_fail (src_height >= 0); - g_return_if_fail (font_ratio > 0.0); + g_return_if_fail (font_ratio > 0.0f); if (dest_width_inout) dest_width = *dest_width_inout; diff -Nru chafa-1.2.1/chafa/chafa-util.h chafa-1.12.4/chafa/chafa-util.h --- chafa-1.2.1/chafa/chafa-util.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-util.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * diff -Nru chafa-1.2.1/chafa/chafa-version-macros.h chafa-1.12.4/chafa/chafa-version-macros.h --- chafa-1.2.1/chafa/chafa-version-macros.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/chafa-version-macros.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2019 Hans Petter Jansson +/* Copyright (C) 2019-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -24,6 +24,9 @@ # error "Only can be included directly." #endif +/* Our current version is defined here */ +#include + G_BEGIN_DECLS /* Exported symbol versioning/visibility. Similar to the versioning macros @@ -31,6 +34,11 @@ #define CHAFA_VERSION_1_0 (G_ENCODE_VERSION (1, 0)) #define CHAFA_VERSION_1_2 (G_ENCODE_VERSION (1, 2)) +#define CHAFA_VERSION_1_4 (G_ENCODE_VERSION (1, 4)) +#define CHAFA_VERSION_1_6 (G_ENCODE_VERSION (1, 6)) +#define CHAFA_VERSION_1_8 (G_ENCODE_VERSION (1, 8)) +#define CHAFA_VERSION_1_10 (G_ENCODE_VERSION (1, 10)) +#define CHAFA_VERSION_1_12 (G_ENCODE_VERSION (1, 12)) /* Evaluates to the current stable version; for development cycles, * this means the next stable target. */ @@ -65,6 +73,13 @@ * * Since: 1.2 */ + +/* Make sure all exportable symbols are made visible, even + * deprecated ones. */ +#ifdef CHAFA_COMPILATION +# define CHAFA_VERSION_MIN_REQUIRED (CHAFA_VERSION_1_0) +#endif + /* If the package sets CHAFA_VERSION_MIN_REQUIRED to some future * CHAFA_VERSION_X_Y value that we don't know about, it will compare as * 0 in preprocessor tests. */ @@ -90,9 +105,8 @@ * functions, then using functions added after version * %CHAFA_VERSION_MAX_ALLOWED will cause warnings. * - * Unless you are using CHAFA_CHECK_VERSION() or the like to compile - * different code depending on the Chafa version, then this should be - * set to the same value as %CHAFA_VERSION_MIN_REQUIRED. + * This should normally be set to the same value as + * %CHAFA_VERSION_MIN_REQUIRED. * * Since: 1.2 */ @@ -132,6 +146,76 @@ # define CHAFA_AVAILABLE_IN_1_2 _CHAFA_EXTERN #endif +#if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_4 +# define CHAFA_DEPRECATED_IN_1_4 G_DEPRECATED +# define CHAFA_DEPRECATED_IN_1_4_FOR(f) G_DEPRECATED_FOR(f) +#else +# define CHAFA_DEPRECATED_IN_1_4 _CHAFA_EXTERN +# define CHAFA_DEPRECATED_IN_1_4_FOR(f) _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_4 +# define CHAFA_AVAILABLE_IN_1_4 G_UNAVAILABLE(1, 4) +#else +# define CHAFA_AVAILABLE_IN_1_4 _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_6 +# define CHAFA_DEPRECATED_IN_1_6 G_DEPRECATED +# define CHAFA_DEPRECATED_IN_1_6_FOR(f) G_DEPRECATED_FOR(f) +#else +# define CHAFA_DEPRECATED_IN_1_6 _CHAFA_EXTERN +# define CHAFA_DEPRECATED_IN_1_6_FOR(f) _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_6 +# define CHAFA_AVAILABLE_IN_1_6 G_UNAVAILABLE(1, 6) +#else +# define CHAFA_AVAILABLE_IN_1_6 _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_8 +# define CHAFA_DEPRECATED_IN_1_8 G_DEPRECATED +# define CHAFA_DEPRECATED_IN_1_8_FOR(f) G_DEPRECATED_FOR(f) +#else +# define CHAFA_DEPRECATED_IN_1_8 _CHAFA_EXTERN +# define CHAFA_DEPRECATED_IN_1_8_FOR(f) _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_8 +# define CHAFA_AVAILABLE_IN_1_8 G_UNAVAILABLE(1, 8) +#else +# define CHAFA_AVAILABLE_IN_1_8 _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_10 +# define CHAFA_DEPRECATED_IN_1_10 G_DEPRECATED +# define CHAFA_DEPRECATED_IN_1_10_FOR(f) G_DEPRECATED_FOR(f) +#else +# define CHAFA_DEPRECATED_IN_1_10 _CHAFA_EXTERN +# define CHAFA_DEPRECATED_IN_1_10_FOR(f) _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_10 +# define CHAFA_AVAILABLE_IN_1_10 G_UNAVAILABLE(1, 10) +#else +# define CHAFA_AVAILABLE_IN_1_10 _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_12 +# define CHAFA_DEPRECATED_IN_1_12 G_DEPRECATED +# define CHAFA_DEPRECATED_IN_1_12_FOR(f) G_DEPRECATED_FOR(f) +#else +# define CHAFA_DEPRECATED_IN_1_12 _CHAFA_EXTERN +# define CHAFA_DEPRECATED_IN_1_12_FOR(f) _CHAFA_EXTERN +#endif + +#if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_12 +# define CHAFA_AVAILABLE_IN_1_12 G_UNAVAILABLE(1, 12) +#else +# define CHAFA_AVAILABLE_IN_1_12 _CHAFA_EXTERN +#endif + G_END_DECLS #endif /* __CHAFA_VERSION_MACROS_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-base64.c chafa-1.12.4/chafa/internal/chafa-base64.c --- chafa-1.2.1/chafa/internal/chafa-base64.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-base64.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,113 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2021-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include + +#include "chafa.h" +#include "internal/chafa-base64.h" + +static const gchar base64_dict [] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +void +chafa_base64_init (ChafaBase64 *base64) +{ + memset (base64, 0, sizeof (*base64)); +} + +void +chafa_base64_deinit (ChafaBase64 *base64) +{ + memset (base64, 0, sizeof (*base64)); + base64->buf_len = -1; +} + +static void +encode_3_bytes (GString *gs_out, guint32 bytes) +{ + g_string_append_c (gs_out, base64_dict [(bytes >> (3 * 6)) & 0x3f]); + g_string_append_c (gs_out, base64_dict [(bytes >> (2 * 6)) & 0x3f]); + g_string_append_c (gs_out, base64_dict [(bytes >> (1 * 6)) & 0x3f]); + g_string_append_c (gs_out, base64_dict [bytes & 0x3f]); +} + +void +chafa_base64_encode (ChafaBase64 *base64, GString *gs_out, gconstpointer in, gint in_len) +{ + const guint8 *in_u8 = in; + const guint8 *end_u8 = in_u8 + in_len; + guint32 r; + + if (base64->buf_len + in_len < 3) + { + memcpy (base64->buf + base64->buf_len, in_u8, in_len); + base64->buf_len += in_len; + return; + } + + if (base64->buf_len == 1) + { + r = (base64->buf [0] << 16) | (in_u8 [0] << 8) | in_u8 [1]; + in_u8 += 2; + encode_3_bytes (gs_out, r); + } + else if (base64->buf_len == 2) + { + r = (base64->buf [0] << 16) | (base64->buf [1] << 8) | in_u8 [0]; + in_u8++; + encode_3_bytes (gs_out, r); + } + + base64->buf_len = 0; + + while (end_u8 - in_u8 >= 3) + { + r = (in_u8 [0] << 16) | (in_u8 [1] << 8) | in_u8 [2]; + encode_3_bytes (gs_out, r); + in_u8 += 3; + } + + while (end_u8 - in_u8 > 0) + { + base64->buf [base64->buf_len++] = *(in_u8++); + } +} + +void +chafa_base64_encode_end (ChafaBase64 *base64, GString *gs_out) +{ + if (base64->buf_len == 1) + { + g_string_append_c (gs_out, base64_dict [base64->buf [0] >> 2]); + g_string_append_c (gs_out, base64_dict [(base64->buf [0] << 4) & 0x30]); + g_string_append (gs_out, "=="); + } + else if (base64->buf_len == 2) + { + g_string_append_c (gs_out, base64_dict [base64->buf [0] >> 2]); + g_string_append_c (gs_out, base64_dict [((base64->buf [0] << 4) | (base64->buf [1] >> 4)) & 0x3f]); + g_string_append_c (gs_out, base64_dict [base64->buf [1] & 0x0f]); + g_string_append_c (gs_out, '='); + } +} diff -Nru chafa-1.2.1/chafa/internal/chafa-base64.h chafa-1.12.4/chafa/internal/chafa-base64.h --- chafa-1.2.1/chafa/internal/chafa-base64.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-base64.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2021-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_BASE64_H__ +#define __CHAFA_BASE64_H__ + +#include +#include "chafa.h" + +G_BEGIN_DECLS + +typedef struct +{ + /* We turn 3-byte groups into 4-character base64 groups, so we + * may need to buffer up to 2 bytes between batches */ + guint8 buf [2]; + gint buf_len; +} +ChafaBase64; + +void chafa_base64_init (ChafaBase64 *base64); +void chafa_base64_deinit (ChafaBase64 *base64); + +void chafa_base64_encode (ChafaBase64 *base64, GString *gs_out, gconstpointer in, gint in_len); +void chafa_base64_encode_end (ChafaBase64 *base64, GString *gs_out); + +G_END_DECLS + +#endif /* __CHAFA_BASE64_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-batch.c chafa-1.12.4/chafa/internal/chafa-batch.c --- chafa-1.2.1/chafa/internal/chafa-batch.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-batch.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,127 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include + +#include "chafa.h" +#include "internal/chafa-batch.h" + +void +chafa_process_batches (gpointer ctx, GFunc batch_func, GFunc post_func, gint n_rows, gint n_batches, gint batch_unit) +{ + GThreadPool *thread_pool = NULL; + ChafaBatchInfo *batches; + gint n_threads; + gint n_units; + gfloat units_per_batch; + gfloat ofs [2] = { .0f, .0f }; + gint i; + + g_assert (n_batches >= 1); + g_assert (batch_unit >= 1); + + if (n_rows < 1) + return; + + n_threads = MIN (chafa_get_n_actual_threads (), n_batches); + n_units = (n_rows + batch_unit - 1) / batch_unit; + units_per_batch = (gfloat) n_units / (gfloat) n_batches; + + batches = g_new0 (ChafaBatchInfo, n_batches); + + if (n_threads >= 2) + thread_pool = g_thread_pool_new (batch_func, + (gpointer) ctx, + n_threads, + FALSE, + NULL); + + /* Divide work up into batches that are multiples of batch_unit, except + * for the last one (if n_rows is not itself a multiple) */ + + for (i = 0; i < n_batches; ) + { + ChafaBatchInfo *batch; + gint row_ofs [2]; + + row_ofs [0] = ofs [0]; + + do + { + ofs [1] += units_per_batch; + row_ofs [1] = ofs [1]; + } + while (row_ofs [0] == row_ofs [1]); + + row_ofs [0] *= batch_unit; + row_ofs [1] *= batch_unit; + + if (row_ofs [1] > n_rows || i == n_batches - 1) + { + ofs [1] = n_rows + 0.5; + row_ofs [1] = n_rows; + } + + if (row_ofs [0] >= row_ofs [1]) + { + /* Save the number of batches actually produced to use in + * post_func loop later. */ + n_batches = i; + break; + } + + batch = &batches [i++]; + batch->first_row = row_ofs [0]; + batch->n_rows = row_ofs [1] - row_ofs [0]; + +#if 0 + g_printerr ("Batch %d: %04d rows\n", i, batch->n_rows); +#endif + + if (n_threads >= 2) + { + g_thread_pool_push (thread_pool, batch, NULL); + } + else + { + batch_func (batch, ctx); + } + + ofs [0] = ofs [1]; + } + + if (n_threads >= 2) + { + /* Wait for threads to finish */ + g_thread_pool_free (thread_pool, FALSE, TRUE); + } + + if (post_func) + { + for (i = 0; i < n_batches; i++) + { + ((void (*)(ChafaBatchInfo *, gpointer)) post_func) (&batches [i], ctx); + } + } + + g_free (batches); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-batch.h chafa-1.12.4/chafa/internal/chafa-batch.h --- chafa-1.2.1/chafa/internal/chafa-batch.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-batch.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_BATCH_H__ +#define __CHAFA_BATCH_H__ + +#include + +G_BEGIN_DECLS + +typedef struct +{ + gint first_row; + gint n_rows; + + gpointer ret_p; + gint ret_n; +} +ChafaBatchInfo; + +void chafa_process_batches (gpointer ctx, GFunc batch_func, GFunc post_func, + gint n_rows, gint n_batches, gint batch_unit); + +G_END_DECLS + +#endif /* __CHAFA_BATCH_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-bitfield.h chafa-1.12.4/chafa/internal/chafa-bitfield.h --- chafa-1.2.1/chafa/internal/chafa-bitfield.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-bitfield.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_BITFIELD_H__ +#define __CHAFA_BITFIELD_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct +{ + guint32 *bits; + guint n_bits; +} +ChafaBitfield; + +static inline void +chafa_bitfield_init (ChafaBitfield *bitfield, guint n_bits) +{ + bitfield->n_bits = n_bits; + bitfield->bits = g_malloc0 ((n_bits + 31) / 8); +} + +static inline void +chafa_bitfield_deinit (ChafaBitfield *bitfield) +{ + g_free (bitfield->bits); + bitfield->n_bits = 0; + bitfield->bits = NULL; +} + +static inline void +chafa_bitfield_clear (ChafaBitfield *bitfield) +{ + memset (bitfield->bits, 0, (bitfield->n_bits + 31) / 8); +} + +static inline gboolean +chafa_bitfield_get_bit (const ChafaBitfield *bitfield, guint nth) +{ + gint index; + gint shift; + + g_return_val_if_fail (nth < bitfield->n_bits, FALSE); + + index = (nth / 32); + shift = (nth % 32); + + return (bitfield->bits [index] >> shift) & 1U; +} + +static inline void +chafa_bitfield_set_bit (ChafaBitfield *bitfield, guint nth, gboolean value) +{ + gint index; + gint shift; + guint32 v32; + + g_return_if_fail (nth < bitfield->n_bits); + + index = (nth / 32); + shift = (nth % 32); + v32 = (guint32) !!value; + + bitfield->bits [index] = (bitfield->bits [index] & ~(1UL << shift)) | (v32 << shift); +} + +G_END_DECLS + +#endif /* __CHAFA_BITFIELD_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-canvas-internal.h chafa-1.12.4/chafa/internal/chafa-canvas-internal.h --- chafa-1.2.1/chafa/internal/chafa-canvas-internal.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-canvas-internal.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_CANVAS_INTERNAL_H__ +#define __CHAFA_CANVAS_INTERNAL_H__ + +#include +#include "chafa.h" +#include "internal/chafa-private.h" +#include "internal/chafa-pixops.h" + +G_BEGIN_DECLS + +struct ChafaCanvasCell +{ + gunichar c; + + /* Colors can be either packed RGBA or index */ + guint32 fg_color; + guint32 bg_color; +}; + +struct ChafaCanvas +{ + gint refs; + + gint width_pixels, height_pixels; + ChafaPixel *pixels; + ChafaCanvasCell *cells; + guint have_alpha : 1; + guint needs_clear : 1; + + /* Whether to consider inverted symbols; FALSE if using FG only */ + guint consider_inverted : 1; + + /* Whether to extract symbol colors; FALSE if using default colors */ + guint extract_colors : 1; + + /* Whether to quantize colors before calculating error (slower, but + * yields better results in palettized modes, especially 16/8) */ + guint use_quantized_error : 1; + + ChafaColorPair default_colors; + guint work_factor_int; + + /* Character to use in cells where fg color == bg color. Typically + * space, but could be something else depending on the symbol map. */ + gunichar blank_char; + + /* Character to use in cells where fg color == bg color and the color + * is only legal in FG. Typically 0x2588 (solid block), but could be + * something else depending on the symbol map. Can be zero if there is + * no good candidate! */ + gunichar solid_char; + + ChafaCanvasConfig config; + + /* Used when setting pixel data */ + ChafaDither dither; + + /* This is NULL in CHAFA_PIXEL_MODE_SYMBOLS, otherwise one of: + * (ChafaSixelCanvas *), (ChafaKittyCanvas *), (ChafaIterm2Canvas *) */ + gpointer pixel_canvas; + + /* Our palettes. Kind of a big structure, so they go last. */ + ChafaPalette fg_palette; + ChafaPalette bg_palette; +}; + +G_END_DECLS + +#endif /* __CHAFA_CANVAS_INTERNAL_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-canvas-printer.c chafa-1.12.4/chafa/internal/chafa-canvas-printer.c --- chafa-1.2.1/chafa/internal/chafa-canvas-printer.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-canvas-printer.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,749 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "chafa.h" +#include "internal/chafa-canvas-printer.h" + +typedef struct +{ + ChafaCanvas *canvas; + ChafaTermInfo *term_info; + + gunichar cur_char; + gint n_reps; + guint cur_inverted : 1; + guint cur_bold : 1; + guint32 cur_fg; + guint32 cur_bg; + + /* For direct-color mode */ + ChafaColor cur_fg_direct; + ChafaColor cur_bg_direct; +} +PrintCtx; + +static gint +cmp_colors (ChafaColor a, ChafaColor b) +{ + return memcmp (&a, &b, sizeof (ChafaColor)); +} + +static ChafaColor +threshold_alpha (ChafaColor col, gint alpha_threshold) +{ + col.ch [3] = col.ch [3] < alpha_threshold ? 0 : 255; + return col; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +flush_chars (PrintCtx *ctx, gchar *out) +{ + gchar buf [8]; + gint len; + + if (!ctx->cur_char) + return out; + + len = g_unichar_to_utf8 (ctx->cur_char, buf); + + if ((ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REPEAT_CELLS) + && chafa_term_info_have_seq (ctx->term_info, CHAFA_TERM_SEQ_REPEAT_CHAR) + && ctx->n_reps > 1 + && ctx->n_reps * len > len + 4 /* ESC [#b */) + { + memcpy (out, buf, len); + out += len; + + out = chafa_term_info_emit_repeat_char (ctx->term_info, out, ctx->n_reps - 1); + + ctx->n_reps = 0; + } + else + { + do + { + memcpy (out, buf, len); + out += len; + ctx->n_reps--; + } + while (ctx->n_reps != 0); + } + + ctx->cur_char = 0; + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +queue_char (PrintCtx *ctx, gchar *out, gunichar c) +{ + if (ctx->cur_char == c) + { + ctx->n_reps++; + } + else + { + if (ctx->cur_char) + out = flush_chars (ctx, out); + + ctx->cur_char = c; + ctx->n_reps = 1; + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +reset_attributes (PrintCtx *ctx, gchar *out) +{ + out = chafa_term_info_emit_reset_attributes (ctx->term_info, out); + + ctx->cur_inverted = FALSE; + ctx->cur_bold = FALSE; + ctx->cur_fg = CHAFA_PALETTE_INDEX_TRANSPARENT; + ctx->cur_bg = CHAFA_PALETTE_INDEX_TRANSPARENT; + ctx->cur_fg_direct.ch [3] = 0; + ctx->cur_bg_direct.ch [3] = 0; + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_attributes_truecolor (PrintCtx *ctx, gchar *out, + ChafaColor fg, ChafaColor bg, gboolean inverted) +{ + if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) + { + if (!ctx->canvas->config.fg_only_enabled + && ((ctx->cur_inverted && !inverted) + || (ctx->cur_fg_direct.ch [3] != 0 && fg.ch [3] == 0) + || (ctx->cur_bg_direct.ch [3] != 0 && bg.ch [3] == 0))) + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + } + + if (!ctx->cur_inverted && inverted) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + } + + if (cmp_colors (fg, ctx->cur_fg_direct)) + { + if (cmp_colors (bg, ctx->cur_bg_direct) && bg.ch [3] != 0) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fgbg_direct (ctx->term_info, out, + fg.ch [0], fg.ch [1], fg.ch [2], + bg.ch [0], bg.ch [1], bg.ch [2]); + } + else if (fg.ch [3] != 0) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fg_direct (ctx->term_info, out, + fg.ch [0], fg.ch [1], fg.ch [2]); + } + } + else if (cmp_colors (bg, ctx->cur_bg_direct) && bg.ch [3] != 0) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_bg_direct (ctx->term_info, out, + bg.ch [0], bg.ch [1], bg.ch [2]); + } + } + else + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + if (inverted) + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + + if (fg.ch [3] != 0) + { + if (bg.ch [3] != 0) + { + out = chafa_term_info_emit_set_color_fgbg_direct (ctx->term_info, out, + fg.ch [0], fg.ch [1], fg.ch [2], + bg.ch [0], bg.ch [1], bg.ch [2]); + } + else + { + out = chafa_term_info_emit_set_color_fg_direct (ctx->term_info, out, + fg.ch [0], fg.ch [1], fg.ch [2]); + } + } + else if (bg.ch [3] != 0) + { + out = chafa_term_info_emit_set_color_bg_direct (ctx->term_info, out, + bg.ch [0], bg.ch [1], bg.ch [2]); + } + } + + ctx->cur_fg_direct = fg; + ctx->cur_bg_direct = bg; + ctx->cur_inverted = inverted; + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_ansi_truecolor (PrintCtx *ctx, gchar *out, gint i, gint i_max) +{ + for ( ; i < i_max; i++) + { + ChafaCanvasCell *cell = &ctx->canvas->cells [i]; + ChafaColor fg, bg; + + /* Wide symbols have a zero code point in the rightmost cell */ + if (cell->c == 0) + continue; + + chafa_unpack_color (cell->fg_color, &fg); + fg = threshold_alpha (fg, ctx->canvas->config.alpha_threshold); + chafa_unpack_color (cell->bg_color, &bg); + bg = threshold_alpha (bg, ctx->canvas->config.alpha_threshold); + + if (fg.ch [3] == 0 && bg.ch [3] != 0) + out = emit_attributes_truecolor (ctx, out, bg, fg, TRUE); + else + out = emit_attributes_truecolor (ctx, out, fg, bg, FALSE); + + if (fg.ch [3] == 0 && bg.ch [3] == 0) + { + out = queue_char (ctx, out, ' '); + if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) + out = queue_char (ctx, out, ' '); + } + else + { + out = queue_char (ctx, out, cell->c); + } + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +handle_attrs_with_reuse (PrintCtx *ctx, gchar *out, + guint32 fg, guint32 bg, + gboolean inverted, gboolean bold) +{ + /* In FG-only mode, we can't use inverse color or bold because the + * attribute reset would mess with the background color. */ + if (ctx->canvas->config.fg_only_enabled) + return out; + + if ((ctx->cur_inverted && !inverted) + || (ctx->cur_bold && !bold) + || (ctx->cur_fg != CHAFA_PALETTE_INDEX_TRANSPARENT && fg == CHAFA_PALETTE_INDEX_TRANSPARENT) + || (ctx->cur_bg != CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT)) + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + } + + if (!ctx->cur_inverted && inverted) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + } + + if (!ctx->cur_bold && bold) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_enable_bold (ctx->term_info, out); + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_attributes_256 (PrintCtx *ctx, gchar *out, + guint32 fg, guint32 bg, gboolean inverted) +{ + if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) + { + out = handle_attrs_with_reuse (ctx, out, fg, bg, inverted, FALSE); + + if (fg != ctx->cur_fg) + { + if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fgbg_256 (ctx->term_info, out, fg, bg); + } + else if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fg_256 (ctx->term_info, out, fg); + } + } + else if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_bg_256 (ctx->term_info, out, bg); + } + } + else + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + if (inverted) + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + + if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = chafa_term_info_emit_set_color_fgbg_256 (ctx->term_info, out, fg, bg); + } + else + { + out = chafa_term_info_emit_set_color_fg_256 (ctx->term_info, out, fg); + } + } + else if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = chafa_term_info_emit_set_color_bg_256 (ctx->term_info, out, bg); + } + } + + ctx->cur_fg = fg; + ctx->cur_bg = bg; + ctx->cur_inverted = inverted; + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_ansi_256 (PrintCtx *ctx, gchar *out, gint i, gint i_max) +{ + for ( ; i < i_max; i++) + { + ChafaCanvasCell *cell = &ctx->canvas->cells [i]; + guint32 fg, bg; + + /* Wide symbols have a zero code point in the rightmost cell */ + if (cell->c == 0) + continue; + + fg = cell->fg_color; + bg = cell->bg_color; + + if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + out = emit_attributes_256 (ctx, out, bg, fg, TRUE); + else + out = emit_attributes_256 (ctx, out, fg, bg, FALSE); + + if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = queue_char (ctx, out, ' '); + if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) + out = queue_char (ctx, out, ' '); + } + else + { + out = queue_char (ctx, out, cell->c); + } + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_attributes_16 (PrintCtx *ctx, gchar *out, + guint32 fg, guint32 bg, gboolean inverted) +{ + if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) + { + out = handle_attrs_with_reuse (ctx, out, fg, bg, inverted, FALSE); + + if (fg != ctx->cur_fg) + { + if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fgbg_16 (ctx->term_info, out, fg, bg); + } + else if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fg_16 (ctx->term_info, out, fg); + } + } + else if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_bg_16 (ctx->term_info, out, bg); + } + } + else + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + if (inverted) + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + + if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = chafa_term_info_emit_set_color_fgbg_16 (ctx->term_info, out, fg, bg); + } + else + { + out = chafa_term_info_emit_set_color_fg_16 (ctx->term_info, out, fg); + } + } + else if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = chafa_term_info_emit_set_color_bg_16 (ctx->term_info, out, bg); + } + } + + ctx->cur_fg = fg; + ctx->cur_bg = bg; + ctx->cur_inverted = inverted; + return out; +} + +/* Uses aixterm control codes for bright colors */ +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_ansi_16 (PrintCtx *ctx, gchar *out, gint i, gint i_max) +{ + for ( ; i < i_max; i++) + { + ChafaCanvasCell *cell = &ctx->canvas->cells [i]; + guint32 fg, bg; + + /* Wide symbols have a zero code point in the rightmost cell */ + if (cell->c == 0) + continue; + + fg = cell->fg_color; + bg = cell->bg_color; + + if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + out = emit_attributes_16 (ctx, out, bg, fg, TRUE); + else + out = emit_attributes_16 (ctx, out, fg, bg, FALSE); + + if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = queue_char (ctx, out, ' '); + if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) + out = queue_char (ctx, out, ' '); + } + else + { + out = queue_char (ctx, out, cell->c); + } + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_attributes_16_8 (PrintCtx *ctx, gchar *out, + guint32 fg, guint32 bg, gboolean inverted) +{ + if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) + { + out = handle_attrs_with_reuse (ctx, out, fg, bg, inverted, fg > 7 && fg < 256 ? TRUE : FALSE); + + if (fg != ctx->cur_fg) + { + if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fgbg_8 (ctx->term_info, out, fg & 7, bg); + } + else if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_fg_8 (ctx->term_info, out, fg & 7); + } + } + else if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_set_color_bg_8 (ctx->term_info, out, bg); + } + } + else + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + if (inverted) + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + if (fg > 7) + out = chafa_term_info_emit_enable_bold (ctx->term_info, out); + + if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = chafa_term_info_emit_set_color_fgbg_8 (ctx->term_info, out, fg & 7, bg); + } + else + { + out = chafa_term_info_emit_set_color_fg_8 (ctx->term_info, out, fg & 7); + } + } + else if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = chafa_term_info_emit_set_color_bg_8 (ctx->term_info, out, bg); + } + } + + ctx->cur_fg = fg; + ctx->cur_bg = bg; + ctx->cur_inverted = inverted; + ctx->cur_bold = fg > 7 && fg < 256 ? TRUE : FALSE; + return out; +} + +/* Uses bold for bright FG colors. */ +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_ansi_16_8 (PrintCtx *ctx, gchar *out, gint i, gint i_max) +{ + for ( ; i < i_max; i++) + { + ChafaCanvasCell *cell = &ctx->canvas->cells [i]; + guint32 fg, bg; + + /* Wide symbols have a zero code point in the rightmost cell */ + if (cell->c == 0) + continue; + + fg = cell->fg_color; + bg = cell->bg_color; + + if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) + out = emit_attributes_16_8 (ctx, out, bg, fg, TRUE); + else + out = emit_attributes_16_8 (ctx, out, fg, bg, FALSE); + + if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT) + { + out = queue_char (ctx, out, ' '); + if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) + out = queue_char (ctx, out, ' '); + } + else + { + out = queue_char (ctx, out, cell->c); + } + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_ansi_fgbg_bgfg (PrintCtx *ctx, gchar *out, gint i, gint i_max) +{ + gunichar blank_symbol = 0; + + if (chafa_symbol_map_has_symbol (&ctx->canvas->config.symbol_map, ' ')) + { + blank_symbol = ' '; + } + else if (chafa_symbol_map_has_symbol (&ctx->canvas->config.symbol_map, 0x2588 /* Solid block */ )) + { + blank_symbol = 0x2588; + } + + for ( ; i < i_max; i++) + { + ChafaCanvasCell *cell = &ctx->canvas->cells [i]; + gboolean invert = FALSE; + gunichar c = cell->c; + + /* Wide symbols have a zero code point in the rightmost cell */ + if (c == 0) + continue; + + /* Replace with blank symbol only if this is a single-width cell */ + if (cell->fg_color == cell->bg_color && blank_symbol != 0 + && (i == i_max - 1 || (ctx->canvas->cells [i + 1].c != 0))) + { + c = blank_symbol; + if (blank_symbol == 0x2588) + invert = TRUE; + } + + if (cell->bg_color == CHAFA_PALETTE_INDEX_FG) + { + invert ^= TRUE; + } + + if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) + { + if (!ctx->cur_inverted && invert) + { + out = flush_chars (ctx, out); + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + } + else if (ctx->cur_inverted && !invert) + { + out = flush_chars (ctx, out); + out = reset_attributes (ctx, out); + } + + ctx->cur_inverted = invert; + } + else + { + out = flush_chars (ctx, out); + if (invert) + out = chafa_term_info_emit_invert_colors (ctx->term_info, out); + else + out = reset_attributes (ctx, out); + } + + out = queue_char (ctx, out, c); + } + + return out; +} + +G_GNUC_WARN_UNUSED_RESULT static gchar * +emit_ansi_fgbg (PrintCtx *ctx, gchar *out, gint i, gint i_max) +{ + for ( ; i < i_max; i++) + { + ChafaCanvasCell *cell = &ctx->canvas->cells [i]; + + /* Wide symbols have a zero code point in the rightmost cell */ + if (cell->c == 0) + continue; + + out = queue_char (ctx, out, cell->c); + } + + return out; +} + +static void +prealloc_string (GString *gs, gint n_cells) +{ + guint needed_len; + + /* Each cell produces at most three control sequences and six bytes + * for the UTF-8 character. Each row may add one seq and one newline. */ + needed_len = (n_cells + 1) * (CHAFA_TERM_SEQ_LENGTH_MAX * 3 + 6) + 1; + + if (gs->allocated_len - gs->len < needed_len) + { + guint current_len = gs->len; + g_string_set_size (gs, gs->len + needed_len * 2); + gs->len = current_len; + } +} + +static GString * +build_ansi_gstring (ChafaCanvas *canvas, ChafaTermInfo *ti) +{ + GString *gs = g_string_new (""); + PrintCtx ctx = { 0 }; + gint i, i_max, i_step, i_next; + + ctx.canvas = canvas; + ctx.term_info = ti; + + i = 0; + i_max = canvas->config.width * canvas->config.height; + i_step = canvas->config.width; + + for ( ; i < i_max; i = i_next) + { + gchar *out; + + i_next = i + i_step; + + prealloc_string (gs, i_step); + out = gs->str + gs->len; + + /* Avoid control codes in FGBG mode. Don't reset attributes when BG + * is held, to preserve any BG color set previously. */ + if (i == 0 + && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG + && !canvas->config.fg_only_enabled) + { + out = reset_attributes (&ctx, out); + } + + switch (canvas->config.canvas_mode) + { + case CHAFA_CANVAS_MODE_TRUECOLOR: + out = emit_ansi_truecolor (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_INDEXED_256: + case CHAFA_CANVAS_MODE_INDEXED_240: + out = emit_ansi_256 (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_INDEXED_16: + out = emit_ansi_16 (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_INDEXED_16_8: + out = emit_ansi_16_8 (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_INDEXED_8: + out = emit_ansi_16 (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_FGBG_BGFG: + out = emit_ansi_fgbg_bgfg (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_FGBG: + out = emit_ansi_fgbg (&ctx, out, i, i_next); + break; + case CHAFA_CANVAS_MODE_MAX: + g_assert_not_reached (); + break; + } + + out = flush_chars (&ctx, out); + + /* Avoid control codes in FGBG mode. Don't reset attributes when BG + * is held, to preserve any BG color set previously. */ + if (canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG + && !canvas->config.fg_only_enabled) + { + out = reset_attributes (&ctx, out); + } + + /* Last line should not end in newline */ + if (i_next < i_max) + *(out++) = '\n'; + + *out = '\0'; + gs->len = out - gs->str; + } + + return gs; +} + +GString * +chafa_canvas_print_symbols (ChafaCanvas *canvas, ChafaTermInfo *ti) +{ + g_assert (canvas != NULL); + g_assert (ti != NULL); + + return build_ansi_gstring (canvas, ti); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-canvas-printer.h chafa-1.12.4/chafa/internal/chafa-canvas-printer.h --- chafa-1.2.1/chafa/internal/chafa-canvas-printer.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-canvas-printer.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_CANVAS_PRINTER_H__ +#define __CHAFA_CANVAS_PRINTER_H__ + +#include +#include "chafa.h" +#include "internal/chafa-canvas-internal.h" +#include "internal/chafa-private.h" +#include "internal/chafa-pixops.h" + +G_BEGIN_DECLS + +GString *chafa_canvas_print_symbols (ChafaCanvas *canvas, ChafaTermInfo *ti); + +G_END_DECLS + +#endif /* __CHAFA_CANVAS_PRINTER_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-color.c chafa-1.12.4/chafa/internal/chafa-color.c --- chafa-1.2.1/chafa/internal/chafa-color.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-color.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,158 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include /* abs */ +#include /* pow, cbrt, log, sqrt, atan2, cos, sin */ +#include "chafa.h" +#include "internal/chafa-color.h" + +guint32 +chafa_pack_color (const ChafaColor *color) +{ + /* Assumes each channel 0 <= value <= 255 */ + return ((guint32) color->ch [0] << 16) + | ((guint32) color->ch [1] << 8) + | ((guint32) color->ch [2]) + | ((guint32) color->ch [3] << 24); /* Alpha */ +} + +void +chafa_unpack_color (guint32 packed, ChafaColor *color_out) +{ + color_out->ch [0] = (packed >> 16) & 0xff; + color_out->ch [1] = (packed >> 8) & 0xff; + color_out->ch [2] = packed & 0xff; + color_out->ch [3] = (packed >> 24) & 0xff; /* Alpha */ +} + +void +chafa_color_accum_div_scalar (ChafaColorAccum *accum, gint scalar) +{ + accum->ch [0] /= scalar; + accum->ch [1] /= scalar; + accum->ch [2] /= scalar; + accum->ch [3] /= scalar; +} + +typedef struct +{ + gdouble c [3]; +} +ChafaColorRGBf; + +typedef struct +{ + gdouble c [3]; +} +ChafaColorXYZ; + +typedef struct +{ + gdouble c [3]; +} +ChafaColorLab; + +static gdouble +invert_rgb_channel_compand (gdouble v) +{ + return v <= 0.04045 ? (v / 12.92) : pow ((v + 0.055) / 1.044, 2.4); +} + +static void +convert_rgb_to_xyz (const ChafaColor *rgbi, ChafaColorXYZ *xyz) +{ + ChafaColorRGBf rgbf; + gint i; + + for (i = 0; i < 3; i++) + { + rgbf.c [i] = (gdouble) rgbi->ch [i] / 255.0; + rgbf.c [i] = invert_rgb_channel_compand (rgbf.c [i]); + } + + xyz->c [0] = 0.4124564 * rgbf.c [0] + 0.3575761 * rgbf.c [1] + 0.1804375 * rgbf.c [2]; + xyz->c [1] = 0.2126729 * rgbf.c [0] + 0.7151522 * rgbf.c [1] + 0.0721750 * rgbf.c [2]; + xyz->c [2] = 0.0193339 * rgbf.c [0] + 0.1191920 * rgbf.c [1] + 0.9503041 * rgbf.c [2]; +} + +#define XYZ_EPSILON (216.0 / 24389.0) +#define XYZ_KAPPA (24389.0 / 27.0) + +static gdouble +lab_f (gdouble v) +{ + return v > XYZ_EPSILON ? cbrt (v) : (XYZ_KAPPA * v + 16.0) / 116.0; +} + +static void +convert_xyz_to_lab (const ChafaColorXYZ *xyz, ChafaColorLab *lab) +{ + ChafaColorXYZ wp = { { 0.95047, 1.0, 1.08883 } }; /* D65 white point */ + ChafaColorXYZ xyz2; + gint i; + + for (i = 0; i < 3; i++) + xyz2.c [i] = lab_f (xyz->c [i] / wp.c [i]); + + lab->c [0] = 116.0 * xyz2.c [1] - 16.0; + lab->c [1] = 500.0 * (xyz2.c [0] - xyz2.c [1]); + lab->c [2] = 200.0 * (xyz2.c [1] - xyz2.c [2]); +} + +void +chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99) +{ + ChafaColorXYZ xyz; + ChafaColorLab lab; + gdouble adj_L, ee, f, G, C, h; + + convert_rgb_to_xyz (rgb, &xyz); + + /* Apply tristimulus-space correction term */ + + xyz.c [0] = 1.12 * xyz.c [0] - 0.12 * xyz.c [2]; + + /* Convert to L*a*b* */ + + convert_xyz_to_lab (&xyz, &lab); + adj_L = 325.22 * log (1.0 + 0.0036 * lab.c [0]); + + /* Intermediate parameters */ + + ee = 0.6427876096865393 * lab.c [1] + 0.766044443118978 * lab.c [2]; + f = 1.14 * (0.6427876096865393 * lab.c [2] - 0.766044443118978 * lab.c [1]); + G = sqrt (ee * ee + f * f); + + /* Hue/chroma */ + + C = 22.5 * log (1.0 + 0.06 * G); + + h = atan2 (f, ee) + 0.8726646 /* 50 degrees */; + while (h < 0.0) h += 6.283185; /* 360 degrees */ + while (h > 6.283185) h -= 6.283185; /* 360 degrees */ + + /* The final values should be in the range [0..255] */ + + din99->ch [0] = adj_L * 2.5; + din99->ch [1] = C * cos (h) * 2.5 + 128.0; + din99->ch [2] = C * sin (h) * 2.5 + 128.0; + din99->ch [3] = rgb->ch [3]; +} diff -Nru chafa-1.2.1/chafa/internal/chafa-color.h chafa-1.12.4/chafa/internal/chafa-color.h --- chafa-1.2.1/chafa/internal/chafa-color.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-color.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,142 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_COLOR_H__ +#define __CHAFA_COLOR_H__ + +#include +#include "chafa.h" + +G_BEGIN_DECLS + +#define CHAFA_PALETTE_INDEX_TRANSPARENT 256 +#define CHAFA_PALETTE_INDEX_FG 257 +#define CHAFA_PALETTE_INDEX_BG 258 + +#define CHAFA_PALETTE_INDEX_MAX 259 + +#define CHAFA_COLOR8_U32(col) (*((guint32 *) (col).ch)) + +/* Color space agnostic */ +typedef struct +{ + guint8 ch [4]; +} +ChafaColor; + +/* BG/FG indexes must be 0 and 1 respectively, corresponding to + * coverage bitmap values */ +#define CHAFA_COLOR_PAIR_BG 0 +#define CHAFA_COLOR_PAIR_FG 1 + +typedef struct +{ + ChafaColor colors [2]; +} +ChafaColorPair; + +typedef struct +{ + union + { + guint32 u32; + ChafaColor col; + } u; +} +ChafaColorConv; + +static inline ChafaColor +chafa_color8_fetch_from_rgba8 (gconstpointer p) +{ + const guint32 *p32 = (const guint32 *) p; + ChafaColorConv cc; + cc.u.u32 = *p32; + return cc.u.col; +} + +static inline void +chafa_color8_store_to_rgba8 (ChafaColor col, gpointer p) +{ + guint32 *p32 = (guint32 *) p; + ChafaColorConv cc; + cc.u.col = col; + *p32 = cc.u.u32; +} + +static inline ChafaColor +chafa_color_average_2 (ChafaColor color_a, ChafaColor color_b) +{ + ChafaColor avg = { 0 }; + + CHAFA_COLOR8_U32 (avg) = + ((CHAFA_COLOR8_U32 (color_a) >> 1) & 0x7f7f7f7f) + + ((CHAFA_COLOR8_U32 (color_b) >> 1) & 0x7f7f7f7f); + + return avg; +} + +typedef struct +{ + ChafaColor col [CHAFA_COLOR_SPACE_MAX]; +} +ChafaPaletteColor; + +typedef struct +{ + gint16 ch [4]; +} +ChafaColorAccum; + +typedef struct +{ + ChafaColor col; +} +ChafaPixel; + +/* Color selection candidate pair */ + +typedef struct +{ + gint16 index [2]; + gint error [2]; +} +ChafaColorCandidates; + +/* Internal API */ + +guint32 chafa_pack_color (const ChafaColor *color) G_GNUC_PURE; +void chafa_unpack_color (guint32 packed, ChafaColor *color_out); + +#define chafa_color_accum_add(d, s) \ +G_STMT_START { \ + (d)->ch [0] += (s)->ch [0]; (d)->ch [1] += (s)->ch [1]; (d)->ch [2] += (s)->ch [2]; (d)->ch [3] += (s)->ch [3]; \ +} G_STMT_END + +#define chafa_color_diff_fast(col_a, col_b) \ + (((gint) (col_b)->ch [0] - (gint) (col_a)->ch [0]) * ((gint) (col_b)->ch [0] - (gint) (col_a)->ch [0]) \ + + ((gint) (col_b)->ch [1] - (gint) (col_a)->ch [1]) * ((gint) (col_b)->ch [1] - (gint) (col_a)->ch [1]) \ + + ((gint) (col_b)->ch [2] - (gint) (col_a)->ch [2]) * ((gint) (col_b)->ch [2] - (gint) (col_a)->ch [2])) + +void chafa_color_accum_div_scalar (ChafaColorAccum *color, gint scalar); + +void chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99); + +G_END_DECLS + +#endif /* __CHAFA_COLOR_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-color-hash.c chafa-1.12.4/chafa/internal/chafa-color-hash.c --- chafa-1.2.1/chafa/internal/chafa-color-hash.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-color-hash.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "chafa.h" +#include "internal/chafa-color-hash.h" + +void +chafa_color_hash_init (ChafaColorHash *color_hash) +{ + guint i; + guint32 j; + + /* Initialize with invalid entries */ + + for (i = 0, j = 0; i < CHAFA_COLOR_HASH_N_ENTRIES; i++) + { + while (_chafa_color_hash_calc_hash (j) == i) + { + j++; + j %= 0x01000000; + } + + color_hash->map [i] = j << 8; + } +} + +void +chafa_color_hash_deinit (G_GNUC_UNUSED ChafaColorHash *color_hash) +{ +} diff -Nru chafa-1.2.1/chafa/internal/chafa-color-hash.h chafa-1.12.4/chafa/internal/chafa-color-hash.h --- chafa-1.2.1/chafa/internal/chafa-color-hash.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-color-hash.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_COLOR_HASH_H__ +#define __CHAFA_COLOR_HASH_H__ + +G_BEGIN_DECLS + +#define CHAFA_COLOR_HASH_N_ENTRIES 16384 + +typedef struct +{ + guint32 map [CHAFA_COLOR_HASH_N_ENTRIES]; +} +ChafaColorHash; + +void chafa_color_hash_init (ChafaColorHash *color_hash); +void chafa_color_hash_deinit (ChafaColorHash *color_hash); + +static inline guint +_chafa_color_hash_calc_hash (guint32 color) +{ + color &= 0x00ffffff; + + return (color ^ (color >> 7) ^ (color >> 14)) % CHAFA_COLOR_HASH_N_ENTRIES; +} + +static inline void +chafa_color_hash_replace (ChafaColorHash *color_hash, guint32 color, guint8 pen) +{ + guint index = _chafa_color_hash_calc_hash (color); + guint32 entry = (color << 8) | pen; + + color_hash->map [index] = entry; +} + +static inline gint +chafa_color_hash_lookup (const ChafaColorHash *color_hash, guint32 color) +{ + guint index = _chafa_color_hash_calc_hash (color); + guint32 entry = color_hash->map [index]; + + if ((entry & 0xffffff00) == (color << 8)) + return entry & 0xff; + + return -1; +} + +G_END_DECLS + +#endif /* __CHAFA_COLOR_HASH_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-color-table.c chafa-1.12.4/chafa/internal/chafa-color-table.c --- chafa-1.2.1/chafa/internal/chafa-color-table.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-color-table.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,390 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include +#include + +#include "chafa.h" + +#include "internal/chafa-color-table.h" +#include "internal/chafa-pca.h" + +#define CHAFA_COLOR_TABLE_ENABLE_PROFILING 0 +#define DEBUG_PEN_CHOICE(x) + +#define POW2(x) ((x) * (x)) + +#define FIXED_MUL_BIG_SHIFT 14 +#define FIXED_MUL_BIG (1 << (FIXED_MUL_BIG_SHIFT)) + +#define FIXED_MUL 32 +#define FIXED_MUL_F ((gfloat) (FIXED_MUL)) + +#if CHAFA_COLOR_TABLE_ENABLE_PROFILING + +# define profile_counter_inc(x) g_atomic_int_inc ((gint *) &(x)) + +static gint n_lookups; +static gint n_misses; +static gint n_a; +static gint n_b; +static gint n_c; +static gint n_d; + +static void +dump_entry (const ChafaColorTable *color_table, gint entry, gint want_color) +{ + const ChafaColorTableEntry *e = &color_table->entries [entry]; + guint32 c = color_table->pens [e->pen]; + gint err; + + err = POW2 (((gint) (c & 0xff) - (want_color & 0xff))) + + POW2 (((gint) (c >> 8) & 0xff) - ((want_color >> 8) & 0xff)) + + POW2 (((gint) (c >> 16) & 0xff) - ((want_color >> 16) & 0xff)); + + g_printerr ("{ %3d, %3d, %3d } { %7d, %7d } n = %3d err = %6d\n", + c & 0xff, + (c >> 8) & 0xff, + (c >> 16) & 0xff, + e->v [0], e->v [1], + entry, + err); +} + +#else +# define profile_counter_inc(x) +#endif + +static gint +compare_entries (gconstpointer a, gconstpointer b) +{ + const ChafaColorTableEntry *ab = a; + const ChafaColorTableEntry *bb = b; + + return ab->v [0] - bb->v [0]; +} + +static gint +scalar_project_vec3i32 (const ChafaVec3i32 *a, const ChafaVec3i32 *b, guint32 b_mul) +{ + guint64 d = chafa_vec3i32_dot_64 (a, b); + + /* I replaced the following (a division, three multiplications and + * two additions) with a multiplication and a right shift: + * + * d / (POW2 (b->v [0]) + POW2 (b->v [1]) + POW2 (b->v [2])) + * + * The result is multiplied by FIXED_MUL for increased precision. */ + + return (d * b_mul) / (FIXED_MUL_BIG / FIXED_MUL); +} + +static gint +color_diff (guint32 a, guint32 b) +{ + gint diff; + gint n; + + n = (gint) ((b & 0xff) - (gint) (a & 0xff)) * FIXED_MUL; + diff = n * n; + n = (gint) (((b >> 8) & 0xff) - (gint) ((a >> 8) & 0xff)) * FIXED_MUL; + diff += n * n; + n = (gint) (((b >> 16) & 0xff) - (gint) ((a >> 16) & 0xff)) * FIXED_MUL; + diff += n * n; + + return diff; +} + +static void +project_color (const ChafaColorTable *color_table, guint32 color, gint *v_out) +{ + ChafaVec3i32 v; + + v.v [0] = (color & 0xff) * FIXED_MUL; + v.v [1] = ((color >> 8) & 0xff) * FIXED_MUL; + v.v [2] = ((color >> 16) & 0xff) * FIXED_MUL; + + chafa_vec3i32_sub (&v, &v, &color_table->average); + + v_out [0] = scalar_project_vec3i32 (&v, &color_table->eigenvectors [0], color_table->eigen_mul [0]); + v_out [1] = scalar_project_vec3i32 (&v, &color_table->eigenvectors [1], color_table->eigen_mul [1]); +} + +static void +vec3i32_fixed_point_from_vec3f32 (ChafaVec3i32 *out, const ChafaVec3f32 *in) +{ + ChafaVec3f32 t; + + chafa_vec3f32_mul_scalar (&t, in, FIXED_MUL_F); + chafa_vec3i32_from_vec3f32 (out, &t); +} + +static void +do_pca (ChafaColorTable *color_table) +{ + ChafaVec3f32 v [256]; + ChafaVec3f32 eigenvectors [2]; + ChafaVec3f32 average; + gint i, j; + + for (i = 0, j = 0; i < 256; i++) + { + guint32 col = color_table->pens [i]; + + if ((col & 0xff000000) == 0xff000000) + continue; + + v [j].v [0] = (col & 0xff) * FIXED_MUL_F; + v [j].v [1] = ((col >> 8) & 0xff) * FIXED_MUL_F; + v [j].v [2] = ((col >> 16) & 0xff) * FIXED_MUL_F; + j++; + } + + chafa_vec3f32_array_compute_pca (v, j, + 2, + eigenvectors, + NULL, + &average); + + vec3i32_fixed_point_from_vec3f32 (&color_table->eigenvectors [0], &eigenvectors [0]); + vec3i32_fixed_point_from_vec3f32 (&color_table->eigenvectors [1], &eigenvectors [1]); + vec3i32_fixed_point_from_vec3f32 (&color_table->average, &average); + + color_table->eigen_mul [0] = + POW2 (color_table->eigenvectors [0].v [0]) + + POW2 (color_table->eigenvectors [0].v [1]) + + POW2 (color_table->eigenvectors [0].v [2]); + color_table->eigen_mul [0] = MAX (color_table->eigen_mul [0], 1); + color_table->eigen_mul [0] = FIXED_MUL_BIG / color_table->eigen_mul [0]; + + color_table->eigen_mul [1] = + POW2 (color_table->eigenvectors [1].v [0]) + + POW2 (color_table->eigenvectors [1].v [1]) + + POW2 (color_table->eigenvectors [1].v [2]); + color_table->eigen_mul [1] = MAX (color_table->eigen_mul [1], 1); + color_table->eigen_mul [1] = FIXED_MUL_BIG / color_table->eigen_mul [1]; + + for (i = 0; i < color_table->n_entries; i++) + { + ChafaColorTableEntry *entry = &color_table->entries [i]; + project_color (color_table, color_table->pens [entry->pen], entry->v); + } +} + +static inline gboolean +refine_pen_choice (const ChafaColorTable *color_table, guint want_color, const gint *v, gint j, + gint *best_pen, gint64 *best_diff) +{ + const ChafaColorTableEntry *pj = &color_table->entries [j]; + gint64 a, b, d; + + a = POW2 ((gint64) pj->v [0] - v [0]); + + profile_counter_inc (n_a); + DEBUG_PEN_CHOICE (g_printerr ("a=%d\n", a)); + + if (a <= *best_diff) + { + /* TODO: When using gint32, the POW2 multiplication can result in overflow. + * Our workaround is to use gint64 for best_diff, a, b and d. This probably + * slows us down, though (we should measure). Is there a better fix? */ + b = POW2 ((gint64) pj->v [1] - v [1]); + + profile_counter_inc (n_b); + DEBUG_PEN_CHOICE (g_printerr ("b=%d\n", b)); + + if (b <= *best_diff) + { + d = color_diff (color_table->pens [pj->pen], want_color); + + profile_counter_inc (n_c); + DEBUG_PEN_CHOICE (g_printerr ("d=%d\n", d)); + + if (d <= *best_diff) + { + *best_pen = j; + *best_diff = d; + + profile_counter_inc (n_d); + DEBUG_PEN_CHOICE (g_printerr ("a=%d, b=%d, d=%d\n", a, b, d)); + } + } + } + else + { + DEBUG_PEN_CHOICE (g_printerr ("\n")); + return FALSE; + } + + return TRUE; +} + +void +chafa_color_table_init (ChafaColorTable *color_table) +{ + color_table->n_entries = 0; + color_table->is_sorted = TRUE; + + memset (color_table->pens, 0xff, sizeof (color_table->pens)); +} + +void +chafa_color_table_deinit (G_GNUC_UNUSED ChafaColorTable *color_table) +{ +#if CHAFA_COLOR_TABLE_ENABLE_PROFILING + g_printerr ("l=%7d m=%7d a=%7d b=%7d c=%7d d=%7d\n" + "per probe: a=%6.1lf b=%6.1lf c=%6.1lf d=%6.1lf\n", + n_lookups, + n_misses, + n_a, + n_b, + n_c, + n_d, + n_a / (gdouble) n_lookups, + n_b / (gdouble) n_lookups, + n_c / (gdouble) n_lookups, + n_d / (gdouble) n_lookups); +#endif +} + +guint32 +chafa_color_table_get_pen_color (const ChafaColorTable *color_table, gint pen) +{ + g_assert (pen >= 0); + g_assert (pen < CHAFA_COLOR_TABLE_MAX_ENTRIES); + + return color_table->pens [pen]; +} + +void +chafa_color_table_set_pen_color (ChafaColorTable *color_table, gint pen, guint32 color) +{ + g_assert (pen >= 0); + g_assert (pen < CHAFA_COLOR_TABLE_MAX_ENTRIES); + + color_table->pens [pen] = color & 0x00ffffff; + color_table->is_sorted = FALSE; +} + +void +chafa_color_table_sort (ChafaColorTable *color_table) +{ + gint i, j; + + if (color_table->is_sorted) + return; + + for (i = 0, j = 0; i < CHAFA_COLOR_TABLE_MAX_ENTRIES; i++) + { + ChafaColorTableEntry *entry; + + if (color_table->pens [i] == 0xffffffff) + continue; + + entry = &color_table->entries [j++]; + entry->pen = i; + } + + color_table->n_entries = j; + + do_pca (color_table); + + qsort (color_table->entries, color_table->n_entries, sizeof (ChafaColorTableEntry), compare_entries); + color_table->is_sorted = TRUE; +} + +gint +chafa_color_table_find_nearest_pen (const ChafaColorTable *color_table, guint32 want_color) +{ + gint64 best_diff = G_MAXINT64; + gint best_pen = 0; + gint v [2]; + gint i, j, m; + + g_assert (color_table->n_entries > 0); + g_assert (color_table->is_sorted); + + profile_counter_inc (n_lookups); + + project_color (color_table, want_color, v); + + /* Binary search for first vector component */ + + i = 0; + j = color_table->n_entries; + + while (i != j) + { + gint n = i + (j - i) / 2; + + if (v [0] > color_table->entries [n].v [0]) + i = n + 1; + else + j = n; + } + + m = j; + + /* Left scan for closer match */ + + for (j = m; j >= 0; j--) + { + if (!refine_pen_choice (color_table, want_color, v, j, &best_pen, &best_diff)) + break; + } + + /* Right scan for closer match */ + + for (j = m + 1; j < color_table->n_entries; j++) + { + if (!refine_pen_choice (color_table, want_color, v, j, &best_pen, &best_diff)) + break; + } + +#if CHAFA_COLOR_TABLE_ENABLE_PROFILING + gint best_pen_2 = -1; + gint64 best_diff_2 = G_MAXINT64; + + for (i = 0; i < color_table->n_entries; i++) + { + const ChafaColorTableEntry *pi = &color_table->entries [i]; + gint64 d = color_diff (color_table->pens [pi->pen], want_color); + + if (d < best_diff_2) + { + best_pen_2 = i; + best_diff_2 = d; + } + } + + if (best_diff_2 < best_diff) + { + profile_counter_inc (n_misses); + + g_printerr ("Bad lookup: "); dump_entry (color_table, best_pen, want_color); + g_printerr ("\nShould be: "); dump_entry (color_table, best_pen_2, want_color); + g_printerr ("\n"); + } +#endif + + return color_table->entries [best_pen].pen; +} diff -Nru chafa-1.2.1/chafa/internal/chafa-color-table.h chafa-1.12.4/chafa/internal/chafa-color-table.h --- chafa-1.2.1/chafa/internal/chafa-color-table.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-color-table.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_COLOR_TABLE_H__ +#define __CHAFA_COLOR_TABLE_H__ + +#include "internal/chafa-pca.h" + +G_BEGIN_DECLS + +#define CHAFA_COLOR_TABLE_MAX_ENTRIES 256 + +typedef struct +{ + gint v [2]; + gint pen; +} +ChafaColorTableEntry; + +typedef struct +{ + ChafaColorTableEntry entries [CHAFA_COLOR_TABLE_MAX_ENTRIES]; + + /* Each pen is 24 bits (B8G8R8) of color information */ + guint32 pens [CHAFA_COLOR_TABLE_MAX_ENTRIES]; + + gint n_entries; + guint is_sorted : 1; + + ChafaVec3i32 eigenvectors [2]; + ChafaVec3i32 average; + + guint eigen_mul [2]; +} +ChafaColorTable; + +void chafa_color_table_init (ChafaColorTable *color_table); +void chafa_color_table_deinit (ChafaColorTable *color_table); + +guint32 chafa_color_table_get_pen_color (const ChafaColorTable *color_table, gint pen); +void chafa_color_table_set_pen_color (ChafaColorTable *color_table, gint pen, guint32 color); + +void chafa_color_table_sort (ChafaColorTable *color_table); +gint chafa_color_table_find_nearest_pen (const ChafaColorTable *color_table, guint32 color); + +G_END_DECLS + +#endif /* __CHAFA_COLOR_TABLE_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-dither.c chafa-1.12.4/chafa/internal/chafa-dither.c --- chafa-1.2.1/chafa/internal/chafa-dither.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-dither.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,111 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include + +#include "chafa.h" +#include "internal/chafa-dither.h" +#include "internal/chafa-private.h" + +#define BAYER_MATRIX_DIM_SHIFT 4 +#define BAYER_MATRIX_DIM (1 << (BAYER_MATRIX_DIM_SHIFT)) +#define BAYER_MATRIX_SIZE ((BAYER_MATRIX_DIM) * (BAYER_MATRIX_DIM)) + +static gint +calc_grain_shift (gint size) +{ + switch (size) + { + case 1: + return 0; + case 2: + return 1; + case 4: + return 2; + case 8: + return 3; + default: + g_assert_not_reached (); + } + + return 0; +} + +void +chafa_dither_init (ChafaDither *dither, ChafaDitherMode mode, + gdouble intensity, + gint grain_width, gint grain_height) +{ + memset (dither, 0, sizeof (*dither)); + + dither->mode = mode; + dither->intensity = intensity; + dither->grain_width_shift = calc_grain_shift (grain_width); + dither->grain_height_shift = calc_grain_shift (grain_height); + dither->bayer_size_shift = BAYER_MATRIX_DIM_SHIFT; + dither->bayer_size_mask = BAYER_MATRIX_DIM - 1; + + if (mode == CHAFA_DITHER_MODE_ORDERED) + { + dither->bayer_matrix = chafa_gen_bayer_matrix (BAYER_MATRIX_DIM, intensity); + } + else if (mode == CHAFA_DITHER_MODE_DIFFUSION) + { + dither->intensity = MIN (dither->intensity, 1.0); + } +} + +void +chafa_dither_deinit (ChafaDither *dither) +{ + g_free (dither->bayer_matrix); + dither->bayer_matrix = NULL; +} + +void +chafa_dither_copy (const ChafaDither *src, ChafaDither *dest) +{ + memcpy (dest, src, sizeof (*dest)); + if (dest->bayer_matrix) + dest->bayer_matrix = g_memdup (src->bayer_matrix, BAYER_MATRIX_SIZE * sizeof (gint)); +} + +ChafaColor +chafa_dither_color_ordered (const ChafaDither *dither, ChafaColor color, gint x, gint y) +{ + gint bayer_index = (((y >> dither->grain_height_shift) & dither->bayer_size_mask) + << dither->bayer_size_shift) + + ((x >> dither->grain_width_shift) & dither->bayer_size_mask); + gint16 bayer_mod = dither->bayer_matrix [bayer_index]; + gint i; + + for (i = 0; i < 3; i++) + { + gint16 c; + + c = (gint16) color.ch [i] + bayer_mod; + c = CLAMP (c, 0, 255); + color.ch [i] = c; + } + + return color; +} diff -Nru chafa-1.2.1/chafa/internal/chafa-dither.h chafa-1.12.4/chafa/internal/chafa-dither.h --- chafa-1.2.1/chafa/internal/chafa-dither.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-dither.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_DITHER_H__ +#define __CHAFA_DITHER_H__ + +#include "internal/chafa-palette.h" + +G_BEGIN_DECLS + +typedef struct +{ + ChafaDitherMode mode; + gdouble intensity; + gint grain_width_shift; + gint grain_height_shift; + + gint bayer_size_shift; + guint bayer_size_mask; + gint *bayer_matrix; +} +ChafaDither; + +void chafa_dither_init (ChafaDither *dither, ChafaDitherMode mode, + gdouble intensity, + gint grain_width, gint grain_height); +void chafa_dither_deinit (ChafaDither *dither); +void chafa_dither_copy (const ChafaDither *src, ChafaDither *dest); + +ChafaColor chafa_dither_color_ordered (const ChafaDither *dither, ChafaColor color, gint x, gint y); + +G_END_DECLS + +#endif /* __CHAFA_DITHER_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-indexed-image.c chafa-1.12.4/chafa/internal/chafa-indexed-image.c --- chafa-1.2.1/chafa/internal/chafa-indexed-image.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-indexed-image.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,474 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "smolscale/smolscale.h" +#include "chafa.h" +#include "internal/chafa-batch.h" +#include "internal/chafa-private.h" + +typedef struct +{ + ChafaIndexedImage *indexed_image; + ChafaColorSpace color_space; + ChafaPixelType src_pixel_type; + gconstpointer src_pixels; + gint src_width, src_height, src_rowstride; + gint dest_width, dest_height; + + SmolScaleCtx *scale_ctx; + guint32 *scaled_data; + + /* BG color with alpha multiplier 255-0 */ + guint32 bg_color_lut [256]; +} +DrawPixelsCtx; + +static void +gen_color_lut_rgba8 (guint32 *color_lut, ChafaColor col) +{ + gint i; + + for (i = 0; i < 256; i++) + { + ChafaColor ncol; + + ncol.ch [0] = (col.ch [0] * (255 - i)) / 255; + ncol.ch [1] = (col.ch [1] * (255 - i)) / 255; + ncol.ch [2] = (col.ch [2] * (255 - i)) / 255; + ncol.ch [3] = 0; + + chafa_color8_store_to_rgba8 (ncol, &color_lut [i]); + } +} + +static void +post_scale_row (guint32 *row_inout, int width, void *user_data) +{ + const DrawPixelsCtx *ctx = user_data; + guint32 *row_inout_end = row_inout + width; + + /* Composite on solid background color */ + + for ( ; row_inout < row_inout_end; row_inout++) + { + ChafaColor c = chafa_color8_fetch_from_rgba8 (row_inout); + *row_inout += ctx->bg_color_lut [c.ch [3]]; + } +} + +static void +draw_pixels_pass_1_worker (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx) +{ + smol_scale_batch_full (ctx->scale_ctx, + ctx->scaled_data + (ctx->dest_width * batch->first_row), + batch->first_row, + batch->n_rows); +} + +static gint +quantize_pixel (const ChafaPalette *palette, ChafaColorSpace color_space, + ChafaColorHash *color_hash, ChafaColor color) +{ + ChafaColor cached_color; + gint index; + + if ((gint) (color.ch [3]) < chafa_palette_get_alpha_threshold (palette)) + return chafa_palette_get_transparent_index (palette); + + /* Sixel color resolution is only slightly less than 7 bits per channel, + * so eliminate the low-order bits to get better hash performance. Also + * mask out the alpha channel. */ + CHAFA_COLOR8_U32 (cached_color) = CHAFA_COLOR8_U32 (color) & GUINT32_FROM_BE (0xfefefe00); + + index = chafa_color_hash_lookup (color_hash, CHAFA_COLOR8_U32 (cached_color)); + + if (index < 0) + { + if (color_space == CHAFA_COLOR_SPACE_DIN99D) + chafa_color_rgb_to_din99d (&color, &color); + + index = chafa_palette_lookup_nearest (palette, + color_space, + &color, + NULL) + - chafa_palette_get_first_color (palette); + + /* Don't insert transparent pixels, since color hash does not store transparency */ + if (index != chafa_palette_get_transparent_index (palette)) + chafa_color_hash_replace (color_hash, CHAFA_COLOR8_U32 (cached_color), index); + } + + return index; +} + +static gint +quantize_pixel_with_error (const ChafaPalette *palette, ChafaColorSpace color_space, + ChafaColor color, ChafaColorAccum *error_inout) +{ + gint index; + + if ((gint) (color.ch [3]) < chafa_palette_get_alpha_threshold (palette)) + { + gint i; + + /* Don't propagate error across transparency */ + for (i = 0; i < 4; i++) + error_inout->ch [i] = 0; + + return chafa_palette_get_transparent_index (palette); + } + + if (color_space == CHAFA_COLOR_SPACE_DIN99D) + chafa_color_rgb_to_din99d (&color, &color); + + index = chafa_palette_lookup_with_error (palette, + color_space, + color, + error_inout) + - chafa_palette_get_first_color (palette); + + return index; +} + +static void +draw_pixels_pass_2_nodither (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx, + ChafaColorHash *chash) +{ + const guint32 *src_p; + guint8 *dest_p, *dest_end_p; + + src_p = ctx->scaled_data + (ctx->dest_width * batch->first_row); + dest_p = ctx->indexed_image->pixels + (ctx->dest_width * batch->first_row); + dest_end_p = dest_p + (ctx->dest_width * batch->n_rows); + + for ( ; dest_p < dest_end_p; src_p++, dest_p++) + { + ChafaColor col; + gint index; + + col = chafa_color8_fetch_from_rgba8 (src_p); + index = quantize_pixel (&ctx->indexed_image->palette, ctx->color_space, chash, col); + *dest_p = index; + } +} + +static void +draw_pixels_pass_2_bayer (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx, + ChafaColorHash *chash) +{ + const guint32 *src_p; + guint8 *dest_p, *dest_end_p; + gint x, y; + + src_p = ctx->scaled_data + (ctx->dest_width * batch->first_row); + dest_p = ctx->indexed_image->pixels + (ctx->dest_width * batch->first_row); + dest_end_p = dest_p + (ctx->dest_width * batch->n_rows); + + x = 0; + y = batch->first_row; + + for ( ; dest_p < dest_end_p; src_p++, dest_p++) + { + ChafaColor col; + gint index; + + col = chafa_color8_fetch_from_rgba8 (src_p); + col = chafa_dither_color_ordered (&ctx->indexed_image->dither, col, x, y); + index = quantize_pixel (&ctx->indexed_image->palette, ctx->color_space, chash, col); + *dest_p = index; + + if (++x >= ctx->dest_width) + { + x = 0; + y++; + } + } +} + +static void +distribute_error (ChafaColorAccum error_in, ChafaColorAccum *error_out_0, + ChafaColorAccum *error_out_1, ChafaColorAccum *error_out_2, + ChafaColorAccum *error_out_3, gdouble intensity) +{ + gint i; + + for (i = 0; i < 3; i++) + { + gint16 ch = error_in.ch [i]; + + error_out_0->ch [i] += (ch * 7) * intensity; + error_out_1->ch [i] += (ch * 1) * intensity; + error_out_2->ch [i] += (ch * 5) * intensity; + error_out_3->ch [i] += (ch * 3) * intensity; + } +} + +static guint8 +fs_dither_pixel (const DrawPixelsCtx *ctx, G_GNUC_UNUSED ChafaColorHash *chash, + const guint32 *inpixel_p, + ChafaColorAccum error_in, + ChafaColorAccum *error_out_0, ChafaColorAccum *error_out_1, + ChafaColorAccum *error_out_2, ChafaColorAccum *error_out_3) +{ + ChafaColor col = chafa_color8_fetch_from_rgba8 (inpixel_p); + guint8 index; + + index = quantize_pixel_with_error (&ctx->indexed_image->palette, ctx->color_space, col, &error_in); + distribute_error (error_in, + error_out_0, error_out_1, error_out_2, error_out_3, + ctx->indexed_image->dither.intensity); + return index; +} + +static void +fs_dither_row (const DrawPixelsCtx *ctx, ChafaColorHash *chash, const guint32 *inrow_p, + guint8 *outrow_p, ChafaColorAccum *error_row, ChafaColorAccum *next_error_row, + gint width, gint y) +{ + gint x; + + if (y & 1) + { + /* Forwards pass */ + + outrow_p [0] = fs_dither_pixel (ctx, chash, &inrow_p [0], error_row [0], + &error_row [1], + &next_error_row [1], + &next_error_row [0], + &next_error_row [1]); + + for (x = 1; x < width - 1; x++) + { + outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], + &error_row [x + 1], + &next_error_row [x + 1], + &next_error_row [x], + &next_error_row [x - 1]); + } + + outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], + &next_error_row [x], + &next_error_row [x], + &next_error_row [x - 1], + &next_error_row [x - 1]); + } + else + { + /* Backwards pass */ + + x = width - 1; + + outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], + &error_row [x - 1], + &next_error_row [x - 1], + &next_error_row [x], + &next_error_row [x - 1]); + + for (x--; x >= 1; x--) + { + outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], + &error_row [x - 1], + &next_error_row [x - 1], + &next_error_row [x], + &next_error_row [x + 1]); + } + + outrow_p [0] = fs_dither_pixel (ctx, chash, &inrow_p [0], error_row [0], + &next_error_row [0], + &next_error_row [0], + &next_error_row [1], + &next_error_row [1]); + } +} + +static void +draw_pixels_pass_2_fs (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx, + ChafaColorHash *chash) +{ + ChafaColorAccum *error_row [2]; + const guint32 *src_p; + guint8 *dest_end_p, *dest_p; + gint y; + + error_row [0] = g_malloc (ctx->dest_width * sizeof (ChafaColorAccum)); + error_row [1] = g_malloc (ctx->dest_width * sizeof (ChafaColorAccum)); + + src_p = ctx->scaled_data + (ctx->dest_width * batch->first_row); + dest_p = ctx->indexed_image->pixels + (ctx->dest_width * batch->first_row); + dest_end_p = dest_p + (ctx->dest_width * batch->n_rows); + + y = batch->first_row; + + memset (error_row [0], 0, ctx->dest_width * sizeof (ChafaColorAccum)); + + for ( ; dest_p < dest_end_p; src_p += ctx->dest_width, dest_p += ctx->dest_width, y++) + { + ChafaColorAccum *error_row_temp; + + memset (error_row [1], 0, ctx->dest_width * sizeof (ChafaColorAccum)); + + fs_dither_row (ctx, chash, src_p, dest_p, error_row [0], error_row [1], + ctx->dest_width, y); + + error_row_temp = error_row [0]; + error_row [0] = error_row [1]; + error_row [1] = error_row_temp; + } + + g_free (error_row [1]); + g_free (error_row [0]); +} + +static void +draw_pixels_pass_2_worker (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx) +{ + ChafaColorHash chash; + + chafa_color_hash_init (&chash); + + switch (ctx->indexed_image->dither.mode) + { + case CHAFA_DITHER_MODE_NONE: + draw_pixels_pass_2_nodither (batch, ctx, &chash); + break; + + case CHAFA_DITHER_MODE_ORDERED: + draw_pixels_pass_2_bayer (batch, ctx, &chash); + break; + + case CHAFA_DITHER_MODE_DIFFUSION: + draw_pixels_pass_2_fs (batch, ctx, &chash); + break; + + case CHAFA_DITHER_MODE_MAX: + g_assert_not_reached (); + break; + } + + chafa_color_hash_deinit (&chash); +} + +static void +draw_pixels (DrawPixelsCtx *ctx) +{ + chafa_process_batches (ctx, + (GFunc) draw_pixels_pass_1_worker, + NULL, + ctx->dest_height, + chafa_get_n_actual_threads (), + 1); + + chafa_palette_generate (&ctx->indexed_image->palette, + ctx->scaled_data, ctx->dest_width * ctx->dest_height, + ctx->color_space); + + /* Single thread only for diffusion; it's a fully serial operation */ + chafa_process_batches (ctx, + (GFunc) draw_pixels_pass_2_worker, + NULL, + ctx->dest_height, + ctx->indexed_image->dither.mode == CHAFA_DITHER_MODE_DIFFUSION + ? 1 : chafa_get_n_actual_threads (), + 1); +} + +ChafaIndexedImage * +chafa_indexed_image_new (gint width, gint height, + const ChafaPalette *palette, + const ChafaDither *dither) +{ + ChafaIndexedImage *indexed_image; + + indexed_image = g_new0 (ChafaIndexedImage, 1); + indexed_image->width = width; + indexed_image->height = height; + indexed_image->pixels = g_malloc (width * height); + + chafa_palette_copy (palette, &indexed_image->palette); + chafa_palette_set_transparent_index (&indexed_image->palette, 255); + + chafa_dither_copy (dither, &indexed_image->dither); + + return indexed_image; +} + +void +chafa_indexed_image_destroy (ChafaIndexedImage *indexed_image) +{ + chafa_dither_deinit (&indexed_image->dither); + g_free (indexed_image->pixels); + g_free (indexed_image); +} + +void +chafa_indexed_image_draw_pixels (ChafaIndexedImage *indexed_image, + ChafaColorSpace color_space, + ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride, + gint dest_width, gint dest_height) +{ + DrawPixelsCtx ctx; + + g_return_if_fail (dest_width == indexed_image->width); + g_return_if_fail (dest_height <= indexed_image->height); + + dest_width = MIN (dest_width, indexed_image->width); + dest_height = MIN (dest_height, indexed_image->height); + + ctx.indexed_image = indexed_image; + ctx.color_space = color_space; + ctx.src_pixel_type = src_pixel_type; + ctx.src_pixels = src_pixels; + ctx.src_width = src_width; + ctx.src_height = src_height; + ctx.src_rowstride = src_rowstride; + ctx.dest_width = dest_width; + ctx.dest_height = dest_height; + + gen_color_lut_rgba8 (ctx.bg_color_lut, + *chafa_palette_get_color (&indexed_image->palette, + CHAFA_COLOR_SPACE_RGB, + CHAFA_PALETTE_INDEX_BG)); + + ctx.scaled_data = g_new (guint32, dest_width * dest_height); + ctx.scale_ctx = smol_scale_new_full ((SmolPixelType) src_pixel_type, + (const guint32 *) src_pixels, + src_width, + src_height, + src_rowstride, + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + NULL, + dest_width, + dest_height, + dest_width * sizeof (guint32), + post_scale_row, + &ctx); + + draw_pixels (&ctx); + + memset (indexed_image->pixels + indexed_image->width * dest_height, + 0, + indexed_image->width * (indexed_image->height - dest_height)); + + smol_scale_destroy (ctx.scale_ctx); + g_free (ctx.scaled_data); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-indexed-image.h chafa-1.12.4/chafa/internal/chafa-indexed-image.h --- chafa-1.2.1/chafa/internal/chafa-indexed-image.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-indexed-image.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_INDEXED_IMAGE_H__ +#define __CHAFA_INDEXED_IMAGE_H__ + +#include "internal/chafa-palette.h" +#include "internal/chafa-dither.h" + +G_BEGIN_DECLS + +typedef struct +{ + gint width, height; + ChafaPalette palette; + ChafaDither dither; + guint8 *pixels; +} +ChafaIndexedImage; + +ChafaIndexedImage *chafa_indexed_image_new (gint width, gint height, + const ChafaPalette *palette, + const ChafaDither *dither); +void chafa_indexed_image_destroy (ChafaIndexedImage *indexed_image); +void chafa_indexed_image_draw_pixels (ChafaIndexedImage *indexed_image, + ChafaColorSpace color_space, + ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride, + gint dest_width, gint dest_height); + +G_END_DECLS + +#endif /* __CHAFA_INDEXED_IMAGE_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-iterm2-canvas.c chafa-1.12.4/chafa/internal/chafa-iterm2-canvas.c --- chafa-1.2.1/chafa/internal/chafa-iterm2-canvas.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-iterm2-canvas.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,269 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2021-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "chafa.h" +#include "smolscale/smolscale.h" +#include "internal/chafa-base64.h" +#include "internal/chafa-batch.h" +#include "internal/chafa-bitfield.h" +#include "internal/chafa-indexed-image.h" +#include "internal/chafa-iterm2-canvas.h" +#include "internal/chafa-string-util.h" + +/* We support iTerm2 images by embedding them as uncompressed TIFF files. + * + * See: https://www.adobe.io/open/standards/TIFF.html */ + +typedef enum +{ + TIFF_TYPE_NONE = 0, + TIFF_TYPE_BYTE, + TIFF_TYPE_ASCII, + TIFF_TYPE_SHORT, + TIFF_TYPE_LONG, + TIFF_TYPE_RATIONAL, + TIFF_TYPE_SBYTE, + TIFF_TYPE_UNDEF, + TIFF_TYPE_SSHORT, + TIFF_TYPE_SLONG, + TIFF_TYPE_SRATIONAL, + TIFF_TYPE_FLOAT, + TIFF_TYPE_DOUBLE +} +TiffType; + +typedef enum +{ + TIFF_TAG_NONE = 0, + TIFF_TAG_NEW_SUB_FILE_TYPE = 254, + TIFF_TAG_SUB_FILE_TYPE, + TIFF_TAG_IMAGE_WIDTH, + TIFF_TAG_IMAGE_LENGTH, + TIFF_TAG_BITS_PER_SAMPLE, + TIFF_TAG_COMPRESSION, + TIFF_TAG_PHOTOMETRIC_INTERPRETATION = 262, + TIFF_TAG_MAKE = 271, + TIFF_TAG_MODEL, + TIFF_TAG_STRIP_OFFSETS, + TIFF_TAG_ORIENTATION, + TIFF_TAG_SAMPLES_PER_PIXEL = 277, + TIFF_TAG_ROWS_PER_STRIP, + TIFF_TAG_STRIP_BYTE_COUNTS, + TIFF_TAG_MIN_SAMPLE_VALUE, + TIFF_TAG_MAX_SAMPLE_VALUE, + TIFF_TAG_X_RESOLUTION, + TIFF_TAG_Y_RESOLUTION, + TIFF_TAG_PLANAR_CONFIGURATION, + TIFF_TAG_EXTRA_SAMPLES = 338 +} +TiffTagId; + +#define TIFF_PHOTOMETRIC_INTERPRETATION_RGB 2 +#define TIFF_ORIENTATION_TOPLEFT 1 +#define TIFF_PLANAR_CONFIGURATION_CONTIGUOUS 1 +#define TIFF_EXTRA_SAMPLE_ASSOC_ALPHA 1 + +typedef struct +{ + guint16 tag_id; + guint16 type; + guint32 count; + guint32 data; +} +TiffTag; + +typedef struct +{ + ChafaIterm2Canvas *iterm2_canvas; + GString *out_str; +} +BuildCtx; + +typedef struct +{ + ChafaIterm2Canvas *iterm2_canvas; + SmolScaleCtx *scale_ctx; +} +DrawCtx; + +ChafaIterm2Canvas * +chafa_iterm2_canvas_new (gint width, gint height) +{ + ChafaIterm2Canvas *iterm2_canvas; + + iterm2_canvas = g_new (ChafaIterm2Canvas, 1); + iterm2_canvas->width = width; + iterm2_canvas->height = height; + iterm2_canvas->rgba_image = g_malloc (width * height * sizeof (guint32)); + + return iterm2_canvas; +} + +void +chafa_iterm2_canvas_destroy (ChafaIterm2Canvas *iterm2_canvas) +{ + g_free (iterm2_canvas->rgba_image); + g_free (iterm2_canvas); +} + +static void +draw_pixels_worker (ChafaBatchInfo *batch, const DrawCtx *ctx) +{ + smol_scale_batch_full (ctx->scale_ctx, + ((guint32 *) ctx->iterm2_canvas->rgba_image) + (ctx->iterm2_canvas->width * batch->first_row), + batch->first_row, + batch->n_rows); +} + +void +chafa_iterm2_canvas_draw_all_pixels (ChafaIterm2Canvas *iterm2_canvas, ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride) +{ + DrawCtx ctx; + + g_return_if_fail (iterm2_canvas != NULL); + g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); + g_return_if_fail (src_pixels != NULL); + g_return_if_fail (src_width >= 0); + g_return_if_fail (src_height >= 0); + + if (src_width == 0 || src_height == 0) + return; + + ctx.iterm2_canvas = iterm2_canvas; + ctx.scale_ctx = smol_scale_new_full ((SmolPixelType) src_pixel_type, + (const guint32 *) src_pixels, + src_width, + src_height, + src_rowstride, + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + NULL, + iterm2_canvas->width, + iterm2_canvas->height, + iterm2_canvas->width * sizeof (guint32), + NULL, + &ctx); + + chafa_process_batches (&ctx, + (GFunc) draw_pixels_worker, + NULL, + iterm2_canvas->height, + chafa_get_n_actual_threads (), + 1); + + smol_scale_destroy (ctx.scale_ctx); +} + +static void +encode_tag (ChafaBase64 *base64, GString *gs, const TiffTag *tag) +{ + chafa_base64_encode (base64, gs, tag, sizeof (*tag)); +} + +static void +generate_tag (ChafaBase64 *base64, GString *gs, + guint16 tag_id, guint16 type, guint32 count, guint32 data) +{ + TiffTag tag; + + tag.tag_id = GUINT16_TO_LE (tag_id); + tag.type = GUINT16_TO_LE (type); + tag.count = GUINT32_TO_LE (count); + tag.data = GUINT32_TO_LE (data); + + encode_tag (base64, gs, &tag); +} + +void +chafa_iterm2_canvas_build_ansi (ChafaIterm2Canvas *iterm2_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells) +{ + gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; + ChafaBase64 base64; + guint32 u32; + guint16 u16; + + *chafa_term_info_emit_begin_iterm2_image (term_info, seq, width_cells, height_cells) = '\0'; + g_string_append (out_str, seq); + + chafa_base64_init (&base64); + + /* Header and directory offset */ + + u32 = GUINT32_TO_LE (0x002a4949); + chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); + u32 = GUINT32_TO_LE (iterm2_canvas->width * iterm2_canvas->height * sizeof (guint32) + + sizeof (guint32) * 2); + chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); + + /* Image data */ + + chafa_base64_encode (&base64, out_str, + iterm2_canvas->rgba_image, + iterm2_canvas->width * iterm2_canvas->height * sizeof (guint32)); + + /* IFD */ + + u16 = GUINT16_TO_LE (11); /* Tag count */ + chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); + + /* Tags */ + + generate_tag (&base64, out_str, TIFF_TAG_IMAGE_WIDTH, TIFF_TYPE_LONG, 1, iterm2_canvas->width); + generate_tag (&base64, out_str, TIFF_TAG_IMAGE_LENGTH, TIFF_TYPE_LONG, 1, iterm2_canvas->height); + + /* For BitsPerSample, the data field points to external data towards the end of file */ + generate_tag (&base64, out_str, TIFF_TAG_BITS_PER_SAMPLE, TIFF_TYPE_SHORT, 4, + iterm2_canvas->width * iterm2_canvas->height * sizeof (guint32) + + sizeof (guint32) * 2 + + sizeof (guint16) + + sizeof (TiffTag) * 11 /* Tag count */ + + sizeof (guint32)); + + generate_tag (&base64, out_str, TIFF_TAG_PHOTOMETRIC_INTERPRETATION, TIFF_TYPE_SHORT, 1, TIFF_PHOTOMETRIC_INTERPRETATION_RGB); + generate_tag (&base64, out_str, TIFF_TAG_STRIP_OFFSETS, TIFF_TYPE_LONG, 1, sizeof (guint32) * 2); + generate_tag (&base64, out_str, TIFF_TAG_ORIENTATION, TIFF_TYPE_SHORT, 1, TIFF_ORIENTATION_TOPLEFT); + generate_tag (&base64, out_str, TIFF_TAG_SAMPLES_PER_PIXEL, TIFF_TYPE_SHORT, 1, 4); + generate_tag (&base64, out_str, TIFF_TAG_ROWS_PER_STRIP, TIFF_TYPE_LONG, 1, iterm2_canvas->height); + generate_tag (&base64, out_str, TIFF_TAG_STRIP_BYTE_COUNTS, TIFF_TYPE_LONG, 1, + iterm2_canvas->width * iterm2_canvas->height * 4); + generate_tag (&base64, out_str, TIFF_TAG_PLANAR_CONFIGURATION, TIFF_TYPE_SHORT, 1, TIFF_PLANAR_CONFIGURATION_CONTIGUOUS); + generate_tag (&base64, out_str, TIFF_TAG_EXTRA_SAMPLES, TIFF_TYPE_SHORT, 1, TIFF_EXTRA_SAMPLE_ASSOC_ALPHA); + + /* Next IFD offset (terminator) */ + + u32 = GUINT32_TO_LE (0); + chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); + + /* Bits per sample external data */ + + u16 = GUINT16_TO_LE (8); + chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); + chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); + chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); + chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); + + chafa_base64_encode_end (&base64, out_str); + chafa_base64_deinit (&base64); + + *chafa_term_info_emit_end_iterm2_image (term_info, seq) = '\0'; + g_string_append (out_str, seq); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-iterm2-canvas.h chafa-1.12.4/chafa/internal/chafa-iterm2-canvas.h --- chafa-1.2.1/chafa/internal/chafa-iterm2-canvas.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-iterm2-canvas.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2021-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_ITERM2_CANVAS_H__ +#define __CHAFA_ITERM2_CANVAS_H__ + +#include "chafa.h" + +G_BEGIN_DECLS + +typedef struct +{ + gint width, height; + gpointer rgba_image; +} +ChafaIterm2Canvas; + +ChafaIterm2Canvas *chafa_iterm2_canvas_new (gint width, gint height); +void chafa_iterm2_canvas_destroy (ChafaIterm2Canvas *iterm2_canvas); + +void chafa_iterm2_canvas_draw_all_pixels (ChafaIterm2Canvas *iterm2_canvas, ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride); +void chafa_iterm2_canvas_build_ansi (ChafaIterm2Canvas *iterm2_canvas, ChafaTermInfo *term_info, GString *out_str, + gint width_cells, gint height_cells); + +G_END_DECLS + +#endif /* __CHAFA_ITERM2_CANVAS_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-kitty-canvas.c chafa-1.12.4/chafa/internal/chafa-kitty-canvas.c --- chafa-1.2.1/chafa/internal/chafa-kitty-canvas.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-kitty-canvas.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,178 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "chafa.h" +#include "smolscale/smolscale.h" +#include "internal/chafa-base64.h" +#include "internal/chafa-batch.h" +#include "internal/chafa-bitfield.h" +#include "internal/chafa-indexed-image.h" +#include "internal/chafa-kitty-canvas.h" +#include "internal/chafa-pixops.h" +#include "internal/chafa-string-util.h" + +typedef struct +{ + ChafaKittyCanvas *kitty_canvas; + GString *out_str; +} +BuildCtx; + +typedef struct +{ + ChafaKittyCanvas *kitty_canvas; + SmolScaleCtx *scale_ctx; + ChafaColor bg_color; + gboolean flatten_alpha; +} +DrawCtx; + +ChafaKittyCanvas * +chafa_kitty_canvas_new (gint width, gint height) +{ + ChafaKittyCanvas *kitty_canvas; + + kitty_canvas = g_new0 (ChafaKittyCanvas, 1); + kitty_canvas->width = width; + kitty_canvas->height = height; + kitty_canvas->rgba_image = g_malloc (width * height * sizeof (guint32)); + + return kitty_canvas; +} + +void +chafa_kitty_canvas_destroy (ChafaKittyCanvas *kitty_canvas) +{ + g_free (kitty_canvas->rgba_image); + g_free (kitty_canvas); +} + +static void +draw_pixels_worker (ChafaBatchInfo *batch, const DrawCtx *ctx) +{ + smol_scale_batch_full (ctx->scale_ctx, + ((guint32 *) ctx->kitty_canvas->rgba_image) + (ctx->kitty_canvas->width * batch->first_row), + batch->first_row, + batch->n_rows); + + /* FIXME: Smolscale should be able to do this */ + if (ctx->flatten_alpha) + chafa_composite_rgba_on_solid_color (ctx->bg_color, + ctx->kitty_canvas->rgba_image, + ctx->kitty_canvas->width, + batch->first_row, + batch->n_rows); +} + +void +chafa_kitty_canvas_draw_all_pixels (ChafaKittyCanvas *kitty_canvas, ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride, + ChafaColor bg_color) +{ + DrawCtx ctx; + + g_return_if_fail (kitty_canvas != NULL); + g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); + g_return_if_fail (src_pixels != NULL); + g_return_if_fail (src_width >= 0); + g_return_if_fail (src_height >= 0); + + if (src_width == 0 || src_height == 0) + return; + + ctx.kitty_canvas = kitty_canvas; + ctx.scale_ctx = smol_scale_new_full ((SmolPixelType) src_pixel_type, + (const guint32 *) src_pixels, + src_width, + src_height, + src_rowstride, + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + NULL, + kitty_canvas->width, + kitty_canvas->height, + kitty_canvas->width * sizeof (guint32), + NULL, + &ctx); + ctx.bg_color = bg_color; + ctx.flatten_alpha = bg_color.ch [3] == 0; + + chafa_process_batches (&ctx, + (GFunc) draw_pixels_worker, + NULL, + kitty_canvas->height, + chafa_get_n_actual_threads (), + 1); + + smol_scale_destroy (ctx.scale_ctx); +} + +static void +encode_chunk (GString *gs, const guint8 *start, const guint8 *end) +{ + ChafaBase64 base64; + + chafa_base64_init (&base64); + chafa_base64_encode (&base64, gs, start, end - start); + chafa_base64_encode_end (&base64, gs); + chafa_base64_deinit (&base64); +} + +void +chafa_kitty_canvas_build_ansi (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, + gint width_cells, gint height_cells) +{ + const guint8 *p, *last; + gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; + + *chafa_term_info_emit_begin_kitty_immediate_image_v1 (term_info, seq, + 32, + kitty_canvas->width, + kitty_canvas->height, + width_cells, + height_cells) = '\0'; + g_string_append (out_str, seq); + + last = ((guint8 *) kitty_canvas->rgba_image) + + kitty_canvas->width * kitty_canvas->height * sizeof (guint32); + + for (p = kitty_canvas->rgba_image; p < last; ) + { + const guint8 *end; + + end = p + 512; + if (end > last) + end = last; + + *chafa_term_info_emit_begin_kitty_image_chunk (term_info, seq) = '\0'; + g_string_append (out_str, seq); + + encode_chunk (out_str, p, end); + + *chafa_term_info_emit_end_kitty_image_chunk (term_info, seq) = '\0'; + g_string_append (out_str, seq); + + p = end; + } + + *chafa_term_info_emit_end_kitty_image (term_info, seq) = '\0'; + g_string_append (out_str, seq); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-kitty-canvas.h chafa-1.12.4/chafa/internal/chafa-kitty-canvas.h --- chafa-1.2.1/chafa/internal/chafa-kitty-canvas.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-kitty-canvas.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_KITTY_CANVAS_H__ +#define __CHAFA_KITTY_CANVAS_H__ + +#include "chafa.h" + +G_BEGIN_DECLS + +typedef struct +{ + gint width, height; + gpointer rgba_image; +} +ChafaKittyCanvas; + +ChafaKittyCanvas *chafa_kitty_canvas_new (gint width, gint height); +void chafa_kitty_canvas_destroy (ChafaKittyCanvas *kitty_canvas); + +void chafa_kitty_canvas_draw_all_pixels (ChafaKittyCanvas *kitty_canvas, + ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride, + ChafaColor bg_color); +void chafa_kitty_canvas_build_ansi (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, + gint width_cells, gint height_cells); + +G_END_DECLS + +#endif /* __CHAFA_KITTY_CANVAS_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-mmx.c chafa-1.12.4/chafa/internal/chafa-mmx.c --- chafa-1.2.1/chafa/internal/chafa-mmx.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-mmx.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include "chafa.h" +#include "internal/chafa-private.h" + +void +calc_colors_mmx (const ChafaPixel *pixels, ChafaColorAccum *accums_out, const guint8 *cov) +{ + __m64 accum [2] = { 0 }; + const guint32 *u32p0 = (const guint32 *) pixels; + __m64 m64b; + gint i; + + m64b = _mm_setzero_si64 (); + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + __m64 *m64p1; + __m64 m64a; + + m64p1 = &accum [cov [i]]; + m64a = _mm_cvtsi32_si64 (u32p0 [i]); + m64a = _mm_unpacklo_pi8 (m64a, m64b); + *m64p1 = _mm_adds_pi16 (*m64p1, m64a); + } + + ((__m64 *) accums_out) [0] = accum [0]; + ((__m64 *) accums_out) [1] = accum [1]; + +#if 0 + /* Called after outer loop is done */ + _mm_empty (); +#endif +} + +void +chafa_leave_mmx (void) +{ +#ifdef HAVE_MMX_INTRINSICS + if (chafa_have_mmx ()) + _mm_empty (); +#endif +} diff -Nru chafa-1.2.1/chafa/internal/chafa-palette.c chafa-1.12.4/chafa/internal/chafa-palette.c --- chafa-1.2.1/chafa/internal/chafa-palette.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-palette.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,1078 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include /* abs */ +#include /* memcpy, memset */ +#include /* pow, cbrt, log, sqrt, atan2, cos, sin */ +#include "chafa.h" +#include "internal/chafa-private.h" + +#define DEBUG(x) + +/* ---------------- * + * Color candidates * + * ---------------- */ + +/* Some situations (like fill symbols) call for both a best and a second-best + * match. ChafaColorCandidates is used to track and return these. */ + +static void +init_candidates (ChafaColorCandidates *candidates) +{ + candidates->index [0] = candidates->index [1] = -1; + candidates->error [0] = candidates->error [1] = G_MAXINT; +} + +static gboolean +update_candidates (ChafaColorCandidates *candidates, gint index, gint error) +{ + if (error < candidates->error [0]) + { + candidates->index [1] = candidates->index [0]; + candidates->index [0] = index; + candidates->error [1] = candidates->error [0]; + candidates->error [0] = error; + return TRUE; + } + else if (error < candidates->error [1]) + { + candidates->index [1] = index; + candidates->error [1] = error; + return TRUE; + } + + return FALSE; +} + +/* -------------------- * + * Fixed system palette * + * -------------------- */ + +static const guint32 term_colors_256 [CHAFA_PALETTE_INDEX_MAX] = +{ + /* First 16 colors; these are usually set by the terminal and can vary quite a + * bit. We try to strike a balance. */ + + 0x000000, 0x800000, 0x007000, 0x707000, 0x000070, 0x700070, 0x007070, 0xc0c0c0, + 0x404040, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, + + /* 240 universal colors; a 216-entry color cube followed by 24 grays */ + + 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, + 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, + + 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, + 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, + + 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, + 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, + + 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, + 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, + + 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, + 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, + + 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, + 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, + + 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, + 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, + + 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, + 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, + + 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, + 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, + + 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, + 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, + + 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, + 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, + + 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, + 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, + + 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, + 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, + + 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, + 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, + + 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, + 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, + + /* Special colors */ + + 0x808080, /* Transparent */ + 0xffffff, /* Terminal's default foreground */ + 0x000000, /* Terminal's default background */ +}; + +static ChafaPaletteColor fixed_palette_256 [CHAFA_PALETTE_INDEX_MAX]; +static guchar color_cube_216_channel_index [256]; +static gboolean palette_initialized; + +static const ChafaColor * +get_fixed_palette_color (guint index, ChafaColorSpace color_space) +{ + return &fixed_palette_256 [index].col [color_space]; +} + +void +chafa_init_palette (void) +{ + gint i; + + if (palette_initialized) + return; + + for (i = 0; i < CHAFA_PALETTE_INDEX_MAX; i++) + { + chafa_unpack_color (term_colors_256 [i], &fixed_palette_256 [i].col [0]); + chafa_color_rgb_to_din99d (&fixed_palette_256 [i].col [0], &fixed_palette_256 [i].col [1]); + + fixed_palette_256 [i].col [0].ch [3] = 0xff; /* Fully opaque */ + fixed_palette_256 [i].col [1].ch [3] = 0xff; /* Fully opaque */ + } + + /* Transparent color */ + fixed_palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [0].ch [3] = 0x00; + fixed_palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [1].ch [3] = 0x00; + + for (i = 0; i < 0x5f / 2; i++) + color_cube_216_channel_index [i] = 0; + for ( ; i < (0x5f + 0x87) / 2; i++) + color_cube_216_channel_index [i] = 1; + for ( ; i < (0x87 + 0xaf) / 2; i++) + color_cube_216_channel_index [i] = 2; + for ( ; i < (0xaf + 0xd7) / 2; i++) + color_cube_216_channel_index [i] = 3; + for ( ; i < (0xd7 + 0xff) / 2; i++) + color_cube_216_channel_index [i] = 4; + for ( ; i <= 0xff; i++) + color_cube_216_channel_index [i] = 5; + + palette_initialized = TRUE; +} + +static gint +update_candidates_with_color_index_diff (ChafaColorCandidates *candidates, ChafaColorSpace color_space, const ChafaColor *color, gint index) +{ + const ChafaColor *palette_color; + gint error; + + palette_color = get_fixed_palette_color (index, color_space); + error = chafa_color_diff_fast (color, palette_color); + update_candidates (candidates, index, error); + + return error; +} + +static void +pick_color_fixed_216_cube (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) +{ + gint i; + + g_assert (color_space == CHAFA_COLOR_SPACE_RGB); + + i = 16 + (color_cube_216_channel_index [color->ch [0]] * 6 * 6 + + color_cube_216_channel_index [color->ch [1]] * 6 + + color_cube_216_channel_index [color->ch [2]]); + + update_candidates_with_color_index_diff (candidates, color_space, color, i); +} + +static void +pick_color_fixed_24_grays (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) +{ + const ChafaColor *palette_color; + gint error, last_error = G_MAXINT; + gint step, i; + + g_assert (color_space == CHAFA_COLOR_SPACE_RGB); + + i = 232 + 12; + last_error = update_candidates_with_color_index_diff (candidates, color_space, color, i); + + palette_color = get_fixed_palette_color (i + 1, color_space); + error = chafa_color_diff_fast (color, palette_color); + if (error < last_error) + { + update_candidates (candidates, i, error); + last_error = error; + step = 1; + i++; + } + else + { + step = -1; + } + + do + { + i += step; + palette_color = get_fixed_palette_color (i, color_space); + + error = chafa_color_diff_fast (color, palette_color); + if (error > last_error) + break; + + update_candidates (candidates, i, error); + last_error = error; + } + while (i >= 232 && i <= 255); +} + +static void +pick_color_fixed_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) +{ + gint i; + + for (i = 0; i < 16; i++) + { + update_candidates_with_color_index_diff (candidates, color_space, color, i); + } +} + +static void +pick_color_fixed_8 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) +{ + gint i; + + for (i = 0; i < 8; i++) + { + update_candidates_with_color_index_diff (candidates, color_space, color, i); + } +} + +static void +pick_color_fixed_256 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) +{ + gint i; + + if (color_space == CHAFA_COLOR_SPACE_RGB) + { + pick_color_fixed_216_cube (color, color_space, candidates); + pick_color_fixed_24_grays (color, color_space, candidates); + + /* Do this last so ties are broken in favor of high-index colors. */ + pick_color_fixed_16 (color, color_space, candidates); + } + else + { + for (i = 0; i < 256; i++) + { + update_candidates_with_color_index_diff (candidates, color_space, color, i); + } + } +} + +static void +pick_color_fixed_240 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) +{ + gint i; + + if (color_space == CHAFA_COLOR_SPACE_RGB) + { + pick_color_fixed_216_cube (color, color_space, candidates); + pick_color_fixed_24_grays (color, color_space, candidates); + } + else + { + /* Check color cube, but not lower 16, bg or fg. Slow! */ + for (i = 16; i < 256; i++) + { + update_candidates_with_color_index_diff (candidates, color_space, color, i); + } + } +} + +/* Pick the best approximation of color from a palette consisting of + * fg_color and bg_color */ +static void +pick_color_fixed_fgbg (const ChafaColor *color, + const ChafaColor *fg_color, const ChafaColor *bg_color, + ChafaColorCandidates *candidates) +{ + gint error; + + error = chafa_color_diff_fast (color, fg_color); + update_candidates (candidates, CHAFA_PALETTE_INDEX_FG, error); + + error = chafa_color_diff_fast (color, bg_color); + update_candidates (candidates, CHAFA_PALETTE_INDEX_BG, error); +} + +/* --------------------------------- * + * Quantization for dynamic palettes * + * --------------------------------- */ + +static gint +find_dominant_channel (gconstpointer pixels, gint n_pixels) +{ + const guint8 *p = pixels; + guint8 min [4] = { G_MAXUINT8, G_MAXUINT8, G_MAXUINT8, G_MAXUINT8 }; + guint8 max [4] = { 0, 0, 0, 0 }; + guint16 diff [4]; + gint best; + gint i; + + for (i = 0; i < n_pixels; i++) + { + /* This should yield branch-free code where possible */ + min [0] = MIN (min [0], *p); + max [0] = MAX (max [0], *p); + p++; + min [1] = MIN (min [1], *p); + max [1] = MAX (max [1], *p); + p++; + min [2] = MIN (min [2], *p); + max [2] = MAX (max [2], *p); + + /* Skip alpha */ + p += 2; + } + +#if 1 + /* Multipliers for luminance */ + diff [0] = (max [0] - min [0]) * 30; + diff [1] = (max [1] - min [1]) * 59; + diff [2] = (max [2] - min [2]) * 11; +#else + diff [0] = (max [0] - min [0]); + diff [1] = (max [1] - min [1]); + diff [2] = (max [2] - min [2]); +#endif + + /* If there are ties, prioritize thusly: G, R, B */ + + best = 1; + if (diff [0] > diff [best]) + best = 0; + if (diff [2] > diff [best]) + best = 2; + + return best; +} + +static int +compare_rgba_0 (gconstpointer a, gconstpointer b) +{ + const guint8 *ab = a; + const guint8 *bb = b; + gint ai = ab [0]; + gint bi = bb [0]; + + return ai - bi; +} + +static int +compare_rgba_1 (gconstpointer a, gconstpointer b) +{ + const guint8 *ab = a; + const guint8 *bb = b; + gint ai = ab [1]; + gint bi = bb [1]; + + return ai - bi; +} + +static int +compare_rgba_2 (gconstpointer a, gconstpointer b) +{ + const guint8 *ab = a; + const guint8 *bb = b; + gint ai = ab [2]; + gint bi = bb [2]; + + return ai - bi; +} + +static int +compare_rgba_3 (gconstpointer a, gconstpointer b) +{ + const guint8 *ab = a; + const guint8 *bb = b; + gint ai = ab [3]; + gint bi = bb [3]; + + return ai - bi; +} + +static void +sort_by_channel (gpointer pixels, gint n_pixels, gint ch) +{ + switch (ch) + { + case 0: + qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_0); + break; + case 1: + qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_1); + break; + case 2: + qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_2); + break; + case 3: + qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_3); + break; + default: + g_assert_not_reached (); + } +} + +#if 0 + +static void +average_pixels (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out) +{ + guint8 *p = pixels + first_ofs * sizeof (guint32); + guint8 *pixels_end; + gint ch [3] = { 0 }; + + pixels_end = p + n_pixels * sizeof (guint32); + + for ( ; p < pixels_end; p += 4) + { + ch [0] += p [0]; + ch [1] += p [1]; + ch [2] += p [2]; + } + + col_out->ch [0] = (ch [0] + n_pixels / 2) / n_pixels; + col_out->ch [1] = (ch [1] + n_pixels / 2) / n_pixels; + col_out->ch [2] = (ch [2] + n_pixels / 2) / n_pixels; +} + +#endif + +static void +median_pixels (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out) +{ + guint8 *p = pixels + (first_ofs + n_pixels / 2) * sizeof (guint32); + col_out->ch [0] = p [0]; + col_out->ch [1] = p [1]; + col_out->ch [2] = p [2]; +} + +static void +average_pixels_weighted_by_deviation (guint8 *pixels, gint first_ofs, gint n_pixels, + ChafaColor *col_out) +{ + guint8 *p = pixels + first_ofs * sizeof (guint32); + guint8 *pixels_end; + gint ch [3] = { 0 }; + ChafaColor median; + guint sum = 0; + + median_pixels (pixels, first_ofs, n_pixels, &median); + + pixels_end = p + n_pixels * sizeof (guint32); + + for ( ; p < pixels_end; p += 4) + { + ChafaColor t; + guint diff; + + t.ch [0] = p [0]; + t.ch [1] = p [1]; + t.ch [2] = p [2]; + + diff = chafa_color_diff_fast (&median, &t); + diff /= 256; + diff += 1; + + ch [0] += p [0] * diff; + ch [1] += p [1] * diff; + ch [2] += p [2] * diff; + + sum += diff; + } + + col_out->ch [0] = (ch [0] + sum / 2) / sum; + col_out->ch [1] = (ch [1] + sum / 2) / sum; + col_out->ch [2] = (ch [2] + sum / 2) / sum; +} + +static void +pick_box_color (gpointer pixels, gint first_ofs, gint n_pixels, ChafaColor *color_out) +{ + average_pixels_weighted_by_deviation (pixels, first_ofs, n_pixels, color_out); +} + +static void +median_cut_once (gpointer pixels, + gint first_ofs, gint n_pixels, + ChafaColor *color_out) +{ + guint8 *p = pixels; + gint dominant_ch; + + g_assert (n_pixels > 0); + + dominant_ch = find_dominant_channel (p + first_ofs * sizeof (guint32), n_pixels); + sort_by_channel (p + first_ofs * sizeof (guint32), n_pixels, dominant_ch); + + pick_box_color (pixels, first_ofs, n_pixels, color_out); +} + +static void +median_cut (ChafaPalette *pal, gpointer pixels, + gint first_ofs, gint n_pixels, + gint first_col, gint n_cols) +{ + guint8 *p = pixels; + gint dominant_ch; + + g_assert (n_pixels > 0); + g_assert (n_cols > 0); + + dominant_ch = find_dominant_channel (p + first_ofs * sizeof (guint32), n_pixels); + sort_by_channel (p + first_ofs * sizeof (guint32), n_pixels, dominant_ch); + + if (n_cols == 1 || n_pixels < 2) + { + pick_box_color (pixels, first_ofs, n_pixels, &pal->colors [first_col].col [CHAFA_COLOR_SPACE_RGB]); + return; + } + + median_cut (pal, pixels, + first_ofs, + n_pixels / 2, + first_col, + n_cols / 2); + + median_cut (pal, pixels, + first_ofs + (n_pixels / 2), + n_pixels - (n_pixels / 2), + first_col + (n_cols / 2), + n_cols - (n_cols / 2)); +} + +static gint +dominant_diff (guint8 *p1, guint8 *p2) +{ + gint diff [3]; + + diff [0] = abs (p2 [0] - (gint) p1 [0]); + diff [1] = abs (p2 [1] - (gint) p1 [1]); + diff [2] = abs (p2 [2] - (gint) p1 [2]); + + return MAX (diff [0], MAX (diff [1], diff [2])); +} + +static void +diversity_pass (ChafaPalette *pal, gpointer pixels, + gint n_pixels, + gint first_col, gint n_cols) +{ + guint8 *p = pixels; + gint step = n_pixels / 128; + gint i, n, c; + guint8 done [128] = { 0 }; + + step = MAX (step, 1); + + for (c = 0; c < n_cols; c++) + { + gint best_box = 0; + gint best_diff = 0; + + for (i = 0, n = 0; i < 128 && i < n_pixels; i++) + { + gint diff = dominant_diff (p + 4 * n, p + 4 * (n + step - 1)); + + if (diff > best_diff && !done [i]) + { + best_diff = diff; + best_box = i; + } + + n += step; + } + + median_cut_once (pixels, best_box * step, MAX (step / 2, 1), + &pal->colors [first_col + c].col [CHAFA_COLOR_SPACE_RGB]); + c++; + if (c >= n_cols) + break; + + median_cut_once (pixels, best_box * step + step / 2, MAX (step / 2, 1), + &pal->colors [first_col + c].col [CHAFA_COLOR_SPACE_RGB]); + + done [best_box] = 1; + } +} + +static void +gen_din99d_color_space (ChafaPalette *palette) +{ + gint i; + + for (i = 0; i < palette->n_colors; i++) + { + chafa_color_rgb_to_din99d (&palette->colors [i].col [CHAFA_COLOR_SPACE_RGB], + &palette->colors [i].col [CHAFA_COLOR_SPACE_DIN99D]); + } +} + +static void +gen_table (ChafaPalette *palette, ChafaColorSpace color_space) +{ + gint i; + + for (i = 0; i < palette->n_colors; i++) + { + const ChafaColor *col; + + if (i == palette->transparent_index) + continue; + + col = &palette->colors [i].col [color_space]; + + chafa_color_table_set_pen_color (&palette->table [color_space], i, + col->ch [0] | (col->ch [1] << 8) | (col->ch [2] << 16)); + } + + chafa_color_table_sort (&palette->table [color_space]); +} + +#define N_SAMPLES 32768 + +static gint +extract_samples (gconstpointer pixels, gpointer pixels_out, gint n_pixels, gint step, + gint alpha_threshold) +{ + const guint32 *p = pixels; + guint32 *p_out = pixels_out; + gint i; + + step = MAX (step, 1); + + for (i = 0; i < n_pixels; i += step) + { + gint alpha = p [i] >> 24; + if (alpha < alpha_threshold) + continue; + *(p_out++) = p [i]; + } + + return ((ptrdiff_t) p_out - (ptrdiff_t) pixels_out) / 4; +} + +static gint +extract_samples_dense (gconstpointer pixels, gpointer pixels_out, gint n_pixels, + gint n_samples_max, gint alpha_threshold) +{ + const guint32 *p = pixels; + guint32 *p_out = pixels_out; + gint n_samples = 0; + gint i; + + g_assert (n_samples_max > 0); + + for (i = 0; i < n_pixels; i++) + { + gint alpha = p [i] >> 24; + if (alpha < alpha_threshold) + continue; + + *(p_out++) = p [i]; + + n_samples++; + if (n_samples == n_samples_max) + break; + } + + return n_samples; +} + +static void +clean_up (ChafaPalette *palette_out) +{ + gint i, j; + gint best_diff = G_MAXINT; + gint best_pair = 1; + + /* Reserve 0th pen for transparency and move colors up. + * Eliminate duplicates and colors that would be the same in + * sixel representation (0..100). */ + + DEBUG (g_printerr ("Colors before: %d\n", palette_out->n_colors)); + + for (i = 1, j = 1; i < palette_out->n_colors; i++) + { + ChafaColor *a, *b; + gint diff, t; + + a = &palette_out->colors [j - 1].col [CHAFA_COLOR_SPACE_RGB]; + b = &palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB]; + + /* Dividing by 256 is strictly not correct, but it's close enough for + * comparison purposes, and a lot faster too. */ + t = (gint) (a->ch [0] * 100) / 256 - (gint) (b->ch [0] * 100) / 256; + diff = t * t; + t = (gint) (a->ch [1] * 100) / 256 - (gint) (b->ch [1] * 100) / 256; + diff += t * t; + t = (gint) (a->ch [2] * 100) / 256 - (gint) (b->ch [2] * 100) / 256; + diff += t * t; + + if (diff == 0) + { + DEBUG (g_printerr ("%d and %d are the same\n", j - 1, i)); + continue; + } + else if (diff < best_diff) + { + best_pair = j - 1; + best_diff = diff; + } + + palette_out->colors [j++] = palette_out->colors [i]; + } + + palette_out->n_colors = j; + + DEBUG (g_printerr ("Colors after: %d\n", palette_out->n_colors)); + + g_assert (palette_out->n_colors >= 0 && palette_out->n_colors <= 256); + + if (palette_out->transparent_index < 256) + { + if (palette_out->n_colors < 256) + { + DEBUG (g_printerr ("Color 0 moved to end (%d)\n", palette_out->n_colors)); + palette_out->colors [palette_out->n_colors] = palette_out->colors [palette_out->transparent_index]; + palette_out->n_colors++; + } + else + { + /* Delete one color to make room for transparency */ + palette_out->colors [best_pair] = palette_out->colors [palette_out->transparent_index]; + DEBUG (g_printerr ("Color 0 replaced %d\n", best_pair)); + } + } +} + +/* --- * + * API * + * --- */ + +void +chafa_palette_init (ChafaPalette *palette_out, ChafaPaletteType type) +{ + gint i; + + chafa_init_palette (); + palette_out->type = type; + palette_out->transparent_index = CHAFA_PALETTE_INDEX_TRANSPARENT; + + for (i = 0; i < CHAFA_PALETTE_INDEX_MAX; i++) + { + palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB] = *get_fixed_palette_color (i, CHAFA_COLOR_SPACE_RGB); + palette_out->colors [i].col [CHAFA_COLOR_SPACE_DIN99D] = *get_fixed_palette_color (i, CHAFA_COLOR_SPACE_DIN99D); + } + + switch (type) + { + case CHAFA_PALETTE_TYPE_FIXED_FGBG: + palette_out->first_color = CHAFA_PALETTE_INDEX_FG; + palette_out->n_colors = 2; + break; + + case CHAFA_PALETTE_TYPE_FIXED_8: + palette_out->n_colors = 8; + break; + + case CHAFA_PALETTE_TYPE_FIXED_16: + palette_out->n_colors = 16; + break; + + case CHAFA_PALETTE_TYPE_FIXED_240: + palette_out->first_color = 16; + palette_out->n_colors = 240; + break; + + case CHAFA_PALETTE_TYPE_FIXED_256: + palette_out->first_color = 0; + palette_out->n_colors = 256; + break; + + case CHAFA_PALETTE_TYPE_DYNAMIC_256: + for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++) + chafa_color_table_init (&palette_out->table [i]); + break; + } +} + +void +chafa_palette_deinit (ChafaPalette *palette) +{ + gint i; + + if (palette->type == CHAFA_PALETTE_TYPE_DYNAMIC_256) + { + for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++) + chafa_color_table_deinit (&palette->table [i]); + } +} + +gint +chafa_palette_get_first_color (const ChafaPalette *palette) +{ + return palette->first_color; +} + +gint +chafa_palette_get_n_colors (const ChafaPalette *palette) +{ + return palette->n_colors; +} + +void +chafa_palette_copy (const ChafaPalette *src, ChafaPalette *dest) +{ + memcpy (dest, src, sizeof (*dest)); +} + +/* pixels must point to RGBA8888 data to sample */ +/* FIXME: Rowstride etc? */ +void +chafa_palette_generate (ChafaPalette *palette_out, gconstpointer pixels, gint n_pixels, + ChafaColorSpace color_space) +{ + guint32 *pixels_copy; + gint step; + gint copy_n_pixels; + + if (palette_out->type != CHAFA_PALETTE_TYPE_DYNAMIC_256) + return; + + pixels_copy = g_malloc (N_SAMPLES * sizeof (guint32)); + + step = (n_pixels / N_SAMPLES) + 1; + copy_n_pixels = extract_samples (pixels, pixels_copy, n_pixels, step, + palette_out->alpha_threshold); + + /* If we recovered very few (potentially zero) samples, it could be due to + * the image being mostly transparent. Resample at full density if so. */ + if (copy_n_pixels < 256 && step != 1) + copy_n_pixels = extract_samples_dense (pixels, pixels_copy, n_pixels, N_SAMPLES, + palette_out->alpha_threshold); + + DEBUG (g_printerr ("Extracted %d samples.\n", copy_n_pixels)); + + if (copy_n_pixels < 1) + { + palette_out->n_colors = 0; + goto out; + } + + median_cut (palette_out, pixels_copy, 0, copy_n_pixels, 0, 128); + palette_out->n_colors = 128; + clean_up (palette_out); + + diversity_pass (palette_out, pixels_copy, copy_n_pixels, palette_out->n_colors, 256 - palette_out->n_colors); + palette_out->n_colors = 256; + clean_up (palette_out); + + gen_table (palette_out, CHAFA_COLOR_SPACE_RGB); + + if (color_space == CHAFA_COLOR_SPACE_DIN99D) + { + gen_din99d_color_space (palette_out); + gen_table (palette_out, CHAFA_COLOR_SPACE_DIN99D); + } + +out: + g_free (pixels_copy); +} + +gint +chafa_palette_lookup_nearest (const ChafaPalette *palette, ChafaColorSpace color_space, + const ChafaColor *color, ChafaColorCandidates *candidates) +{ + if (palette->type == CHAFA_PALETTE_TYPE_DYNAMIC_256) + { + gint result; + + /* Transparency */ + if (color->ch [3] < palette->alpha_threshold) + return palette->transparent_index; + + result = chafa_color_table_find_nearest_pen (&palette->table [color_space], + color->ch [0] + | (color->ch [1] << 8) + | (color->ch [2] << 16)); + + if (candidates) + { + /* The only consumer of multiple candidates is the cell canvas, and that + * supports fixed palettes only. Therefore, in practice we'll never end up here. + * Let's not leave a loose end, though... */ + + candidates->index [0] = result; + candidates->index [1] = result; + candidates->error [0] = 0; + candidates->error [1] = 0; + } + + return result; + } + else + { + ChafaColorCandidates candidates_temp; + + if (!candidates) + candidates = &candidates_temp; + + init_candidates (candidates); + + if (color->ch [3] < palette->alpha_threshold) + { + /* Transparency */ + candidates->index [0] = palette->transparent_index; + candidates->index [1] = palette->transparent_index; + candidates->error [0] = 0; + candidates->error [1] = 0; + } + else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_256) + pick_color_fixed_256 (color, color_space, candidates); + else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_240) + pick_color_fixed_240 (color, color_space, candidates); + else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_16) + pick_color_fixed_16 (color, color_space, candidates); + else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_8) + pick_color_fixed_8 (color, color_space, candidates); + else /* CHAFA_PALETTE_TYPE_FIXED_FGBG */ + pick_color_fixed_fgbg (color, + &palette->colors [CHAFA_PALETTE_INDEX_FG].col [color_space], + &palette->colors [CHAFA_PALETTE_INDEX_BG].col [color_space], + candidates); + + if (palette->transparent_index < 256) + { + if (candidates->index [0] == palette->transparent_index) + { + candidates->index [0] = candidates->index [1]; + candidates->error [0] = candidates->error [1]; + } + else + { + if (candidates->index [0] == CHAFA_PALETTE_INDEX_TRANSPARENT) + candidates->index [0] = palette->transparent_index; + if (candidates->index [1] == CHAFA_PALETTE_INDEX_TRANSPARENT) + candidates->index [1] = palette->transparent_index; + } + } + + return candidates->index [0]; + } + + g_assert_not_reached (); +} + +gint +chafa_palette_lookup_with_error (const ChafaPalette *palette, ChafaColorSpace color_space, + ChafaColor color, ChafaColorAccum *error_inout) +{ + ChafaColorAccum compensated_color; + gint index; + + if (error_inout) + { + compensated_color.ch [0] = ((gint16) color.ch [0]) + ((error_inout->ch [0] * 0.9f) / 16); + compensated_color.ch [1] = ((gint16) color.ch [1]) + ((error_inout->ch [1] * 0.9f) / 16); + compensated_color.ch [2] = ((gint16) color.ch [2]) + ((error_inout->ch [2] * 0.9f) / 16); + + color.ch [0] = CLAMP (compensated_color.ch [0], 0, 255); + color.ch [1] = CLAMP (compensated_color.ch [1], 0, 255); + color.ch [2] = CLAMP (compensated_color.ch [2], 0, 255); + } + + index = chafa_palette_lookup_nearest (palette, color_space, &color, NULL); + + if (error_inout) + { + if (index == palette->transparent_index) + { + memset (error_inout, 0, sizeof (*error_inout)); + } + else + { + ChafaColor found_color = palette->colors [index].col [color_space]; + + error_inout->ch [0] = ((gint16) compensated_color.ch [0]) - ((gint16) found_color.ch [0]); + error_inout->ch [1] = ((gint16) compensated_color.ch [1]) - ((gint16) found_color.ch [1]); + error_inout->ch [2] = ((gint16) compensated_color.ch [2]) - ((gint16) found_color.ch [2]); + } + } + + return index; +} + +ChafaPaletteType +chafa_palette_get_type (const ChafaPalette *palette) +{ + return palette->type; +} + +const ChafaColor * +chafa_palette_get_color (const ChafaPalette *palette, ChafaColorSpace color_space, + gint index) +{ + return &palette->colors [index].col [color_space]; +} + +void +chafa_palette_set_color (ChafaPalette *palette, gint index, const ChafaColor *color) +{ + palette->colors [index].col [CHAFA_COLOR_SPACE_RGB] = *color; + chafa_color_rgb_to_din99d (&palette->colors [index].col [CHAFA_COLOR_SPACE_RGB], + &palette->colors [index].col [CHAFA_COLOR_SPACE_DIN99D]); +} + +gint +chafa_palette_get_alpha_threshold (const ChafaPalette *palette) +{ + return palette->alpha_threshold; +} + +void +chafa_palette_set_alpha_threshold (ChafaPalette *palette, gint alpha_threshold) +{ + palette->alpha_threshold = alpha_threshold; +} + +gint +chafa_palette_get_transparent_index (const ChafaPalette *palette) +{ + return palette->transparent_index; +} + +void +chafa_palette_set_transparent_index (ChafaPalette *palette, gint index) +{ + palette->transparent_index = index; +} + diff -Nru chafa-1.2.1/chafa/internal/chafa-palette.h chafa-1.12.4/chafa/internal/chafa-palette.h --- chafa-1.2.1/chafa/internal/chafa-palette.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-palette.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_PALETTE_H__ +#define __CHAFA_PALETTE_H__ + +#include +#include "internal/chafa-color.h" +#include "internal/chafa-color-table.h" + +G_BEGIN_DECLS + +typedef enum +{ + CHAFA_PALETTE_TYPE_DYNAMIC_256, + CHAFA_PALETTE_TYPE_FIXED_256, + CHAFA_PALETTE_TYPE_FIXED_240, + CHAFA_PALETTE_TYPE_FIXED_16, + CHAFA_PALETTE_TYPE_FIXED_8, + CHAFA_PALETTE_TYPE_FIXED_FGBG +} +ChafaPaletteType; + +typedef struct +{ + ChafaPaletteType type; + ChafaPaletteColor colors [CHAFA_PALETTE_INDEX_MAX]; + ChafaColorTable table [CHAFA_COLOR_SPACE_MAX]; + gint first_color; + gint n_colors; + gint alpha_threshold; + gint transparent_index; +} +ChafaPalette; + +/* Global init */ +void chafa_init_palette (void); + +void chafa_palette_init (ChafaPalette *palette_out, ChafaPaletteType type); +void chafa_palette_deinit (ChafaPalette *palette); + +void chafa_palette_copy (const ChafaPalette *src, ChafaPalette *dest); +void chafa_palette_generate (ChafaPalette *palette_out, gconstpointer pixels, gint n_pixels, + ChafaColorSpace color_space); + +ChafaPaletteType chafa_palette_get_type (const ChafaPalette *palette); + +gint chafa_palette_get_first_color (const ChafaPalette *palette); +gint chafa_palette_get_n_colors (const ChafaPalette *palette); + +gint chafa_palette_lookup_nearest (const ChafaPalette *palette, ChafaColorSpace color_space, + const ChafaColor *color, ChafaColorCandidates *candidates); + +gint chafa_palette_lookup_with_error (const ChafaPalette *palette, ChafaColorSpace color_space, + ChafaColor color, ChafaColorAccum *error_inout); + +const ChafaColor *chafa_palette_get_color (const ChafaPalette *palette, ChafaColorSpace color_space, + gint index); +void chafa_palette_set_color (ChafaPalette *palette, gint index, const ChafaColor *color); + +gint chafa_palette_get_alpha_threshold (const ChafaPalette *palette); +void chafa_palette_set_alpha_threshold (ChafaPalette *palette, gint alpha_threshold); + +gint chafa_palette_get_transparent_index (const ChafaPalette *palette); +void chafa_palette_set_transparent_index (ChafaPalette *palette, gint index); + +G_END_DECLS + +#endif /* __CHAFA_PALETTE_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-pca.c chafa-1.12.4/chafa/internal/chafa-pca.c --- chafa-1.2.1/chafa/internal/chafa-pca.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-pca.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,162 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include /* memcpy */ +#include "internal/chafa-pca.h" + +#define PCA_POWER_MAX_ITERATIONS 1000 +#define PCA_POWER_MIN_ERROR 0.0001 + +static gfloat +pca_converge (const ChafaVec3f32 *vecs_in, gint n_vecs, + ChafaVec3f32 *eigenvector_out) +{ + ChafaVec3f32 r = CHAFA_VEC3F32_INIT (.11, .23, .51); + gfloat eigenvalue; + gint i, j; + + /* Power iteration */ + + /* FIXME: r should probably be random, and we should try again + * if we pick a bad one */ + + chafa_vec3f32_normalize (&r, &r); + + for (j = 0; j < PCA_POWER_MAX_ITERATIONS; j++) + { + ChafaVec3f32 s = CHAFA_VEC3F32_INIT_ZERO; + ChafaVec3f32 t; + gfloat err; + + for (i = 0; i < n_vecs; i++) + { + gfloat u; + + u = chafa_vec3f32_dot (&vecs_in [i], &r); + chafa_vec3f32_mul_scalar (&t, &vecs_in [i], u); + chafa_vec3f32_add (&s, &s, &t); + } + + eigenvalue = chafa_vec3f32_dot (&r, &s); + + chafa_vec3f32_mul_scalar (&t, &r, eigenvalue); + chafa_vec3f32_sub (&t, &t, &s); + err = chafa_vec3f32_get_magnitude (&t); + + chafa_vec3f32_copy (&r, &s); + chafa_vec3f32_normalize (&r, &r); + + if (err < PCA_POWER_MIN_ERROR) + break; + } + + chafa_vec3f32_copy (eigenvector_out, &r); + return eigenvalue; +} + +static void +pca_deflate (ChafaVec3f32 *vecs, gint n_vecs, const ChafaVec3f32 *eigenvector) +{ + gint i; + + /* Calculate scores, reconstruct with scores and eigenvector, + * then subtract from original vectors to generate residuals. + * We should be able to get the next component from those. */ + + for (i = 0; i < n_vecs; i++) + { + ChafaVec3f32 t; + gfloat score; + + score = chafa_vec3f32_dot (&vecs [i], eigenvector); + chafa_vec3f32_mul_scalar (&t, eigenvector, score); + chafa_vec3f32_sub (&vecs [i], &vecs [i], &t); + } +} + +/** + * chafa_vec3f32_array_compute_pca: + * @vecs_in: Input vector array + * @n_vecs: Number of vectors in array + * @n_components: Number of components to compute (1 or 2) + * @eigenvectors_out: Pointer to array to store n_components eigenvectors in, or NULL + * @eigenvalues_out: Pointer to array to store n_components eigenvalues in, or NULL + * @average_out: Pointer to a vector to store array average (for centering), or NULL + * + * Compute principal components from an array of 3D vectors. This + * implementation is naive and probably not that fast, but it should + * be good enough for our purposes. + **/ +void +chafa_vec3f32_array_compute_pca (const ChafaVec3f32 *vecs_in, gint n_vecs, + gint n_components, + ChafaVec3f32 *eigenvectors_out, + gfloat *eigenvalues_out, + ChafaVec3f32 *average_out) +{ + ChafaVec3f32 *v; + ChafaVec3f32 average; + ChafaVec3f32 t; + gfloat eigenvalue; + gint i; + + v = g_malloc (n_vecs * sizeof (ChafaVec3f32)); + memcpy (v, vecs_in, n_vecs * sizeof (ChafaVec3f32)); + + /* Calculate average */ + + chafa_vec3f32_average_array (&average, v, n_vecs); + + /* Recenter around average */ + + chafa_vec3f32_set_zero (&t); + chafa_vec3f32_sub (&t, &t, &average); + chafa_vec3f32_add_to_array (v, &t, n_vecs); + + /* Compute principal components */ + + for (i = 0; ; ) + { + eigenvalue = pca_converge (v, n_vecs, &t); + + if (eigenvectors_out) + { + chafa_vec3f32_copy (eigenvectors_out, &t); + eigenvectors_out++; + } + + if (eigenvalues_out) + { + *eigenvalues_out = eigenvalue; + eigenvalues_out++; + } + + if (++i >= n_components) + break; + + pca_deflate (v, n_vecs, &t); + } + + if (average_out) + chafa_vec3f32_copy (average_out, &average); + + g_free (v); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-pca.h chafa-1.12.4/chafa/internal/chafa-pca.h --- chafa-1.2.1/chafa/internal/chafa-pca.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-pca.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,192 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_PCA_H__ +#define __CHAFA_PCA_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CHAFA_VEC3F32_INIT(x, y, z) { { (x), (y), (z) } } +#define CHAFA_VEC3F32_INIT_ZERO CHAFA_VEC3F32_INIT (0.0, 0.0, 0.0) + +typedef struct +{ + gfloat v [3]; +} +ChafaVec3f32; + +typedef struct +{ + gint32 v [3]; +} +ChafaVec3i32; + +static inline void +chafa_vec3i32_set (ChafaVec3i32 *out, gint32 x, gint32 y, gint32 z) +{ + out->v [0] = x; + out->v [1] = y; + out->v [2] = z; +} + +static inline void +chafa_vec3i32_sub (ChafaVec3i32 *out, const ChafaVec3i32 *a, const ChafaVec3i32 *b) +{ + out->v [0] = a->v [0] - b->v [0]; + out->v [1] = a->v [1] - b->v [1]; + out->v [2] = a->v [2] - b->v [2]; +} + +static inline void +chafa_vec3i32_from_vec3f32 (ChafaVec3i32 *out, const ChafaVec3f32 *in) +{ + /* lrintf() rounding can be extremely slow, so use this function + * sparingly. We use tricks to make GCC emit cvtss2si instructions + * ("-fno-math-errno -fno-trapping-math", or simply "-ffast-math"), + * but Clang apparently cannot be likewise persuaded. + * + * Clang _does_ like rounding with SSE 4.1, but that's not something + * we can enable by default. */ + + out->v [0] = lrintf (in->v [0]); + out->v [1] = lrintf (in->v [1]); + out->v [2] = lrintf (in->v [2]); +} + +static inline gint32 +chafa_vec3i32_dot_32 (const ChafaVec3i32 *v, const ChafaVec3i32 *u) +{ + return v->v [0] * u->v [0] + v->v [1] * u->v [1] + v->v [2] * u->v [2]; +} + +static inline gint64 +chafa_vec3i32_dot_64 (const ChafaVec3i32 *v, const ChafaVec3i32 *u) +{ + return v->v [0] * u->v [0] + v->v [1] * u->v [1] + v->v [2] * u->v [2]; +} + +static inline void +chafa_vec3f32_copy (ChafaVec3f32 *dest, const ChafaVec3f32 *src) +{ + *dest = *src; +} + +static inline void +chafa_vec3f32_add (ChafaVec3f32 *out, const ChafaVec3f32 *a, const ChafaVec3f32 *b) +{ + out->v [0] = a->v [0] + b->v [0]; + out->v [1] = a->v [1] + b->v [1]; + out->v [2] = a->v [2] + b->v [2]; +} + +static inline void +chafa_vec3f32_add_from_array (ChafaVec3f32 *accum, const ChafaVec3f32 *v, gint n) +{ + gint i; + + for (i = 0; i < n; i++) + { + chafa_vec3f32_add (accum, accum, &v [i]); + } +} + +static inline void +chafa_vec3f32_add_to_array (ChafaVec3f32 *v, const ChafaVec3f32 *in, gint n) +{ + gint i; + + for (i = 0; i < n; i++) + { + chafa_vec3f32_add (&v [i], &v [i], in); + } +} + +static inline void +chafa_vec3f32_sub (ChafaVec3f32 *out, const ChafaVec3f32 *a, const ChafaVec3f32 *b) +{ + out->v [0] = a->v [0] - b->v [0]; + out->v [1] = a->v [1] - b->v [1]; + out->v [2] = a->v [2] - b->v [2]; +} + +static inline void +chafa_vec3f32_mul_scalar (ChafaVec3f32 *out, const ChafaVec3f32 *in, gfloat s) +{ + out->v [0] = in->v [0] * s; + out->v [1] = in->v [1] * s; + out->v [2] = in->v [2] * s; +} + +static inline gfloat +chafa_vec3f32_dot (const ChafaVec3f32 *v, const ChafaVec3f32 *u) +{ + return v->v [0] * u->v [0] + v->v [1] * u->v [1] + v->v [2] * u->v [2]; +} + +static inline void +chafa_vec3f32_set (ChafaVec3f32 *v, gfloat x, gfloat y, gfloat z) +{ + v->v [0] = x; + v->v [1] = y; + v->v [2] = z; +} + +static inline void +chafa_vec3f32_set_zero (ChafaVec3f32 *v) +{ + chafa_vec3f32_set (v, 0.0f, 0.0f, 0.0f); +} + +static inline gfloat +chafa_vec3f32_get_magnitude (const ChafaVec3f32 *v) +{ + return sqrtf (v->v [0] * v->v [0] + v->v [1] * v->v [1] + v->v [2] * v->v [2]); +} + +static inline void +chafa_vec3f32_normalize (ChafaVec3f32 *out, const ChafaVec3f32 *in) +{ + gfloat m; + + m = 1.0f / chafa_vec3f32_get_magnitude (in); + out->v [0] = in->v [0] * m; + out->v [1] = in->v [1] * m; + out->v [2] = in->v [2] * m; +} + +static inline void +chafa_vec3f32_average_array (ChafaVec3f32 *out, const ChafaVec3f32 *v, gint n) +{ + chafa_vec3f32_set_zero (out); + chafa_vec3f32_add_from_array (out, v, n); + chafa_vec3f32_mul_scalar (out, out, 1.0f / (gfloat) n); +} + +void chafa_vec3f32_array_compute_pca (const ChafaVec3f32 *vecs_in, gint n_vecs, + gint n_components, + ChafaVec3f32 *eigenvectors_out, + gfloat *eigenvalues_out, + ChafaVec3f32 *average_out); + +G_END_DECLS + +#endif /* __CHAFA_PCA_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-pixops.c chafa-1.12.4/chafa/internal/chafa-pixops.c --- chafa-1.2.1/chafa/internal/chafa-pixops.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-pixops.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,855 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "internal/chafa-batch.h" +#include "internal/chafa-pixops.h" +#include "internal/smolscale/smolscale.h" + +/* Fixed point multiplier */ +#define FIXED_MULT 4096 + +/* See rgb_to_intensity_fast () */ +#define INTENSITY_MAX (256 * 8) + +/* Normalization: Percentage of pixels to discard at extremes of histogram */ +#define INDEXED_16_CROP_PCT 5 +#define INDEXED_8_CROP_PCT 10 +#define INDEXED_2_CROP_PCT 20 + +/* Ensure there's no overflow in normalize_ch() */ +G_STATIC_ASSERT (FIXED_MULT * INTENSITY_MAX * (gint64) 255 <= G_MAXINT); +G_STATIC_ASSERT (FIXED_MULT * INTENSITY_MAX * (gint64) -255 >= G_MININT); + +typedef struct +{ + gint32 c [INTENSITY_MAX]; + + /* Transparent pixels are not sampled, so we must keep count */ + gint n_samples; + + /* Lower and upper bounds */ + gint32 min, max; +} +Histogram; + +typedef struct +{ + ChafaPixelType src_pixel_type; + gconstpointer src_pixels; + gint src_width, src_height; + gint src_rowstride; + + ChafaPixel *dest_pixels; + gint dest_width, dest_height; + + const ChafaPalette *palette; + const ChafaDither *dither; + ChafaColorSpace color_space; + gboolean preprocessing_enabled; + gint work_factor_int; + + /* Cached to avoid repeatedly calling palette functions */ + ChafaPaletteType palette_type; + ChafaColor bg_color_rgb; + + /* Result of alpha detection is stored here */ + gint have_alpha_int; + + Histogram hist; + SmolScaleCtx *scale_ctx; +} +PrepareContext; + +typedef struct +{ + Histogram hist; +} +PreparePixelsBatch1Ret; + +static gint +rgb_to_intensity_fast (const ChafaColor *color) +{ + /* Sum to 8x so we can divide by shifting later */ + return color->ch [0] * 3 + color->ch [1] * 4 + color->ch [2]; +} + +static void +sum_histograms (const Histogram *hist_in, Histogram *hist_accum) +{ + gint i; + + hist_accum->n_samples += hist_in->n_samples; + + for (i = 0; i < INTENSITY_MAX; i++) + { + hist_accum->c [i] += hist_in->c [i]; + } +} + +static void +histogram_calc_bounds (Histogram *hist, gint crop_pct) +{ + gint64 pixels_crop; + gint i; + gint t; + + pixels_crop = (hist->n_samples * (((gint64) crop_pct * 1024) / 100)) / 1024; + + /* Find lower bound */ + + for (i = 0, t = pixels_crop; i < INTENSITY_MAX; i++) + { + t -= hist->c [i]; + if (t <= 0) + break; + } + + hist->min = i; + + /* Find upper bound */ + + for (i = INTENSITY_MAX - 1, t = pixels_crop; i >= 0; i--) + { + t -= hist->c [i]; + if (t <= 0) + break; + } + + hist->max = i; +} + +static gint16 +normalize_ch (guint8 v, gint min, gint factor) +{ + gint vt = v; + + vt -= min; + vt *= factor; + vt /= FIXED_MULT; + + vt = CLAMP (vt, 0, 255); + return vt; +} + +static void +normalize_rgb (ChafaPixel *pixels, const Histogram *hist, gint width, gint dest_y, gint n_rows) +{ + ChafaPixel *p0, *p1; + gint factor; + + /* Make sure range is more or less sane */ + + if (hist->min == hist->max) + return; + +#if 0 + if (min > 512) + min = 512; + if (max < 1536) + max = 1536; +#endif + + /* Adjust intensities */ + + factor = ((INTENSITY_MAX - 1) * FIXED_MULT) / (hist->max - hist->min); + +#if 0 + g_printerr ("[%d-%d] * %d, crop=%d \n", min, max, factor, pixels_crop); +#endif + + p0 = pixels + dest_y * width; + p1 = p0 + n_rows * width; + + for ( ; p0 < p1; p0++) + { + p0->col.ch [0] = normalize_ch (p0->col.ch [0], hist->min / 8, factor); + p0->col.ch [1] = normalize_ch (p0->col.ch [1], hist->min / 8, factor); + p0->col.ch [2] = normalize_ch (p0->col.ch [2], hist->min / 8, factor); + } +} + +static void +boost_saturation_rgb (ChafaColor *col) +{ + gint ch [3]; + gfloat P = sqrtf (col->ch [0] * (gfloat) col->ch [0] * .299f + + col->ch [1] * (gfloat) col->ch [1] * .587f + + col->ch [2] * (gfloat) col->ch [2] * .144f); + + ch [0] = P + ((gfloat) col->ch [0] - P) * 2; + ch [1] = P + ((gfloat) col->ch [1] - P) * 2; + ch [2] = P + ((gfloat) col->ch [2] - P) * 2; + + col->ch [0] = CLAMP (ch [0], 0, 255); + col->ch [1] = CLAMP (ch [1], 0, 255); + col->ch [2] = CLAMP (ch [2], 0, 255); +} + +/* pixel must point to top-left pixel of the grain to be dithered */ +static void +fs_dither_grain (const ChafaDither *dither, + const ChafaPalette *palette, ChafaColorSpace color_space, + ChafaPixel *pixel, gint image_width, + const ChafaColorAccum *error_in, + ChafaColorAccum *error_out_0, ChafaColorAccum *error_out_1, + ChafaColorAccum *error_out_2, ChafaColorAccum *error_out_3) +{ + gint grain_width = 1 << dither->grain_width_shift; + gint grain_height = 1 << dither->grain_height_shift; + gint grain_shift = dither->grain_width_shift + dither->grain_height_shift; + ChafaColorAccum next_error = { 0 }; + ChafaColorAccum accum = { 0 }; + ChafaPixel *p; + ChafaColor acol; + const ChafaColor *col; + gint index; + gint x, y, i; + + p = pixel; + + for (y = 0; y < grain_height; y++) + { + for (x = 0; x < grain_width; x++, p++) + { + for (i = 0; i < 3; i++) + { + gint16 ch = p->col.ch [i]; + ch += error_in->ch [i]; + + if (ch < 0) + { + next_error.ch [i] += ch; + ch = 0; + } + else if (ch > 255) + { + next_error.ch [i] += ch - 255; + ch = 255; + } + + p->col.ch [i] = ch; + accum.ch [i] += ch; + } + } + + p += image_width - grain_width; + } + + for (i = 0; i < 3; i++) + { + accum.ch [i] >>= grain_shift; + acol.ch [i] = accum.ch [i]; + } + + /* Don't try to dither alpha */ + acol.ch [3] = 0xff; + + index = chafa_palette_lookup_nearest (palette, color_space, &acol, NULL); + col = chafa_palette_get_color (palette, color_space, index); + + for (i = 0; i < 3; i++) + { + /* FIXME: Floating point op is slow. Factor this out and make + * dither_intensity == 1.0 the fast path. */ + next_error.ch [i] = ((next_error.ch [i] >> grain_shift) + (accum.ch [i] - (gint16) col->ch [i]) * dither->intensity); + + error_out_0->ch [i] += next_error.ch [i] * 7 / 16; + error_out_1->ch [i] += next_error.ch [i] * 1 / 16; + error_out_2->ch [i] += next_error.ch [i] * 5 / 16; + error_out_3->ch [i] += next_error.ch [i] * 3 / 16; + } +} + +static void +convert_rgb_to_din99d (ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) +{ + ChafaPixel *pixel = pixels + dest_y * width; + ChafaPixel *pixel_max = pixel + n_rows * width; + + /* RGB -> DIN99d */ + + for ( ; pixel < pixel_max; pixel++) + { + chafa_color_rgb_to_din99d (&pixel->col, &pixel->col); + } +} + +static void +bayer_dither (const ChafaDither *dither, ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) +{ + ChafaPixel *pixel = pixels + dest_y * width; + ChafaPixel *pixel_max = pixel + n_rows * width; + gint x, y; + + for (y = dest_y; pixel < pixel_max; y++) + { + for (x = 0; x < width; x++) + { + pixel->col = chafa_dither_color_ordered (dither, pixel->col, x, y); + pixel++; + } + } +} + +static void +fs_dither (const ChafaDither *dither, const ChafaPalette *palette, + ChafaColorSpace color_space, + ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) +{ + ChafaPixel *pixel; + ChafaColorAccum *error_rows; + ChafaColorAccum *error_row [2]; + ChafaColorAccum *pp; + gint grain_width = 1 << dither->grain_width_shift; + gint grain_height = 1 << dither->grain_height_shift; + gint width_grains = width >> dither->grain_width_shift; + gint x, y; + + g_assert (width % grain_width == 0); + g_assert (dest_y % grain_height == 0); + g_assert (n_rows % grain_height == 0); + + dest_y >>= dither->grain_height_shift; + n_rows >>= dither->grain_height_shift; + + error_rows = g_malloc (width_grains * 2 * sizeof (ChafaColorAccum)); + error_row [0] = error_rows; + error_row [1] = error_rows + width_grains; + + memset (error_row [0], 0, width_grains * sizeof (ChafaColorAccum)); + + for (y = dest_y; y < dest_y + n_rows; y++) + { + memset (error_row [1], 0, width_grains * sizeof (ChafaColorAccum)); + + if (!(y & 1)) + { + /* Forwards pass */ + pixel = pixels + (y << dither->grain_height_shift) * width; + + fs_dither_grain (dither, palette, color_space, pixel, width, + error_row [0], + error_row [0] + 1, + error_row [1] + 1, + error_row [1], + error_row [1] + 1); + pixel += grain_width; + + for (x = 1; ((x + 1) << dither->grain_width_shift) < width; x++) + { + fs_dither_grain (dither, palette, color_space, pixel, width, + error_row [0] + x, + error_row [0] + x + 1, + error_row [1] + x + 1, + error_row [1] + x, + error_row [1] + x - 1); + pixel += grain_width; + } + + fs_dither_grain (dither, palette, color_space, pixel, width, + error_row [0] + x, + error_row [1] + x, + error_row [1] + x, + error_row [1] + x - 1, + error_row [1] + x - 1); + } + else + { + /* Backwards pass */ + pixel = pixels + (y << dither->grain_height_shift) * width + width - grain_width; + + fs_dither_grain (dither, palette, color_space, pixel, width, + error_row [0] + width_grains - 1, + error_row [0] + width_grains - 2, + error_row [1] + width_grains - 2, + error_row [1] + width_grains - 1, + error_row [1] + width_grains - 2); + + pixel -= grain_width; + + for (x = width_grains - 2; x > 0; x--) + { + fs_dither_grain (dither, palette, color_space, pixel, width, + error_row [0] + x, + error_row [0] + x - 1, + error_row [1] + x - 1, + error_row [1] + x, + error_row [1] + x + 1); + pixel -= grain_width; + } + + fs_dither_grain (dither, palette, color_space, pixel, width, + error_row [0], + error_row [1], + error_row [1], + error_row [1] + 1, + error_row [1] + 1); + } + + pp = error_row [0]; + error_row [0] = error_row [1]; + error_row [1] = pp; + } + + g_free (error_rows); +} + +static void +bayer_and_convert_rgb_to_din99d (const ChafaDither *dither, + ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) +{ + ChafaPixel *pixel = pixels + dest_y * width; + ChafaPixel *pixel_max = pixel + n_rows * width; + gint x, y; + + for (y = dest_y; pixel < pixel_max; y++) + { + for (x = 0; x < width; x++) + { + pixel->col = chafa_dither_color_ordered (dither, pixel->col, x, y); + chafa_color_rgb_to_din99d (&pixel->col, &pixel->col); + pixel++; + } + } +} + +static void +fs_and_convert_rgb_to_din99d (const ChafaDither *dither, const ChafaPalette *palette, + ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) +{ + convert_rgb_to_din99d (pixels, width, dest_y, n_rows); + fs_dither (dither, palette, CHAFA_COLOR_SPACE_DIN99D, pixels, width, dest_y, n_rows); +} + +static void +prepare_pixels_1_inner (PreparePixelsBatch1Ret *ret, + PrepareContext *prep_ctx, + const guint8 *data_p, + ChafaPixel *pixel_out, + gint *alpha_sum) +{ + ChafaColor *col = &pixel_out->col; + + col->ch [0] = data_p [0]; + col->ch [1] = data_p [1]; + col->ch [2] = data_p [2]; + col->ch [3] = data_p [3]; + + *alpha_sum += (0xff - col->ch [3]); + + if (prep_ctx->preprocessing_enabled + && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16 + || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8) + ) + { + boost_saturation_rgb (col); + } + + /* Build histogram */ + if (col->ch [3] > 127) + { + gint v = rgb_to_intensity_fast (col); + ret->hist.c [v]++; + ret->hist.n_samples++; + } +} + +static void +prepare_pixels_1_worker_nearest (ChafaBatchInfo *batch, PrepareContext *prep_ctx) +{ + ChafaPixel *pixel; + gint dest_y; + gint px, py; + gint x_inc, y_inc; + gint alpha_sum = 0; + const guint8 *data; + gint n_rows; + gint rowstride; + PreparePixelsBatch1Ret *ret; + + ret = g_new (PreparePixelsBatch1Ret, 1); + batch->ret_p = ret; + + dest_y = batch->first_row; + data = prep_ctx->src_pixels; + n_rows = batch->n_rows; + rowstride = prep_ctx->src_rowstride; + + x_inc = (prep_ctx->src_width * FIXED_MULT) / (prep_ctx->dest_width); + y_inc = (prep_ctx->src_height * FIXED_MULT) / (prep_ctx->dest_height); + + pixel = prep_ctx->dest_pixels + dest_y * prep_ctx->dest_width; + + for (py = dest_y; py < dest_y + n_rows; py++) + { + const guint8 *data_row_p; + + data_row_p = data + ((py * y_inc) / FIXED_MULT) * rowstride; + + for (px = 0; px < prep_ctx->dest_width; px++) + { + const guint8 *data_p = data_row_p + ((px * x_inc) / FIXED_MULT) * 4; + prepare_pixels_1_inner (ret, prep_ctx, data_p, pixel++, &alpha_sum); + } + } + + if (alpha_sum > 0) + g_atomic_int_set (&prep_ctx->have_alpha_int, 1); +} + +static void +prepare_pixels_1_worker_smooth (ChafaBatchInfo *batch, PrepareContext *prep_ctx) +{ + ChafaPixel *pixel, *pixel_max; + gint alpha_sum = 0; + guint8 *scaled_data; + const guint8 *data_p; + PreparePixelsBatch1Ret *ret; + + ret = g_new0 (PreparePixelsBatch1Ret, 1); + batch->ret_p = ret; + + scaled_data = g_malloc (prep_ctx->dest_width * batch->n_rows * sizeof (guint32)); + smol_scale_batch_full (prep_ctx->scale_ctx, scaled_data, batch->first_row, batch->n_rows); + + data_p = scaled_data; + pixel = prep_ctx->dest_pixels + batch->first_row * prep_ctx->dest_width; + pixel_max = pixel + batch->n_rows * prep_ctx->dest_width; + + while (pixel < pixel_max) + { + prepare_pixels_1_inner (ret, prep_ctx, data_p, pixel++, &alpha_sum); + data_p += 4; + } + + g_free (scaled_data); + + if (alpha_sum > 0) + g_atomic_int_set (&prep_ctx->have_alpha_int, 1); +} + +static void +pass_1_post (ChafaBatchInfo *batch, PrepareContext *prep_ctx) +{ + PreparePixelsBatch1Ret *ret = batch->ret_p; + + if (prep_ctx->preprocessing_enabled) + { + sum_histograms (&ret->hist, &prep_ctx->hist); + } + + g_free (ret); +} + +static void +prepare_pixels_pass_1 (PrepareContext *prep_ctx) +{ + GFunc batch_func; + + /* First pass + * ---------- + * + * - Scale and convert pixel format + * - Apply local preprocessing like saturation boost (optional) + * - Generate histogram for later passes (e.g. for normalization) + * - Figure out if we have alpha transparency + */ + + batch_func = (GFunc) ((prep_ctx->work_factor_int < 3 + && prep_ctx->src_pixel_type == CHAFA_PIXEL_RGBA8_UNASSOCIATED) + ? prepare_pixels_1_worker_nearest + : prepare_pixels_1_worker_smooth); + + chafa_process_batches (prep_ctx, + (GFunc) batch_func, + (GFunc) pass_1_post, + prep_ctx->dest_height, + chafa_get_n_actual_threads (), + 1); + + /* Generate final histogram */ + if (prep_ctx->preprocessing_enabled) + { + switch (prep_ctx->palette_type) + { + case CHAFA_PALETTE_TYPE_FIXED_16: + histogram_calc_bounds (&prep_ctx->hist, INDEXED_16_CROP_PCT); + break; + case CHAFA_PALETTE_TYPE_FIXED_8: + histogram_calc_bounds (&prep_ctx->hist, INDEXED_8_CROP_PCT); + break; + default: + histogram_calc_bounds (&prep_ctx->hist, INDEXED_2_CROP_PCT); + break; + } + } +} + +static void +composite_alpha_on_bg (ChafaColor bg_color, + ChafaPixel *pixels, gint width, gint first_row, gint n_rows) +{ + ChafaPixel *p0, *p1; + + p0 = pixels + first_row * width; + p1 = p0 + n_rows * width; + + for ( ; p0 < p1; p0++) + { + p0->col.ch [0] += (bg_color.ch [0] * (255 - (guint32) p0->col.ch [3])) / 255; + p0->col.ch [1] += (bg_color.ch [1] * (255 - (guint32) p0->col.ch [3])) / 255; + p0->col.ch [2] += (bg_color.ch [2] * (255 - (guint32) p0->col.ch [3])) / 255; + } +} + +/* FIXME: Could we always destroy the alpha channel and eliminate the other + * variant? */ +static void +composite_alpha_on_solid (ChafaColor bg_color, + ChafaPixel *pixels, gint width, gint first_row, gint n_rows) +{ + ChafaPixel *p0, *p1; + + p0 = pixels + first_row * width; + p1 = p0 + n_rows * width; + + for ( ; p0 < p1; p0++) + { + p0->col.ch [0] += (bg_color.ch [0] * (255 - (guint32) p0->col.ch [3])) / 255; + p0->col.ch [1] += (bg_color.ch [1] * (255 - (guint32) p0->col.ch [3])) / 255; + p0->col.ch [2] += (bg_color.ch [2] * (255 - (guint32) p0->col.ch [3])) / 255; + p0->col.ch [3] = 0xff; + } +} + +void +chafa_composite_rgba_on_solid_color (ChafaColor color, + ChafaPixel *pixels, gint width, gint first_row, gint n_rows) +{ + composite_alpha_on_solid (color, pixels, width, first_row, n_rows); +} + +static void +prepare_pixels_2_worker (ChafaBatchInfo *batch, PrepareContext *prep_ctx) +{ + if (prep_ctx->preprocessing_enabled + && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16 + || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8 + || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_FGBG)) + normalize_rgb (prep_ctx->dest_pixels, &prep_ctx->hist, prep_ctx->dest_width, + batch->first_row, batch->n_rows); + + if (prep_ctx->have_alpha_int) + composite_alpha_on_bg (prep_ctx->bg_color_rgb, + prep_ctx->dest_pixels, prep_ctx->dest_width, + batch->first_row, batch->n_rows); + + if (prep_ctx->color_space == CHAFA_COLOR_SPACE_DIN99D) + { + if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_ORDERED) + { + bayer_and_convert_rgb_to_din99d (prep_ctx->dither, + prep_ctx->dest_pixels, + prep_ctx->dest_width, + batch->first_row, + batch->n_rows); + } + else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION) + { + fs_and_convert_rgb_to_din99d (prep_ctx->dither, + prep_ctx->palette, + prep_ctx->dest_pixels, + prep_ctx->dest_width, + batch->first_row, + batch->n_rows); + } + else + { + convert_rgb_to_din99d (prep_ctx->dest_pixels, + prep_ctx->dest_width, + batch->first_row, + batch->n_rows); + } + } + else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_ORDERED) + { + bayer_dither (prep_ctx->dither, + prep_ctx->dest_pixels, + prep_ctx->dest_width, + batch->first_row, + batch->n_rows); + } + else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION) + { + fs_dither (prep_ctx->dither, + prep_ctx->palette, + prep_ctx->color_space, + prep_ctx->dest_pixels, + prep_ctx->dest_width, + batch->first_row, + batch->n_rows); + } +} + +static gboolean +need_pass_2 (PrepareContext *prep_ctx) +{ + if ((prep_ctx->preprocessing_enabled + && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16 + || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8 + || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_FGBG)) + || prep_ctx->have_alpha_int + || prep_ctx->color_space == CHAFA_COLOR_SPACE_DIN99D + || prep_ctx->dither->mode != CHAFA_DITHER_MODE_NONE) + return TRUE; + + return FALSE; +} + +static void +prepare_pixels_pass_2 (PrepareContext *prep_ctx) +{ + gint n_batches; + gint batch_unit = 1; + + /* Second pass + * ----------- + * + * - Normalization (optional) + * - Dithering (optional) + * - Color space conversion; DIN99d (optional) + */ + + if (!need_pass_2 (prep_ctx)) + return; + + n_batches = chafa_get_n_actual_threads (); + + /* Floyd-Steinberg diffusion needs the batch size to be a multiple of the + * grain height. It also needs to run in a single thread to propagate the + * quantization error correctly. */ + if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION) + { + n_batches = 1; + batch_unit = 1 << prep_ctx->dither->grain_height_shift; + } + + chafa_process_batches (prep_ctx, + (GFunc) prepare_pixels_2_worker, + NULL, /* _post */ + prep_ctx->dest_height, + n_batches, + batch_unit); +} + +void +chafa_prepare_pixel_data_for_symbols (const ChafaPalette *palette, + const ChafaDither *dither, + ChafaColorSpace color_space, + gboolean preprocessing_enabled, + gint work_factor, + ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, + gint src_height, + gint src_rowstride, + ChafaPixel *dest_pixels, + gint dest_width, + gint dest_height) +{ + PrepareContext prep_ctx = { 0 }; + + prep_ctx.palette = palette; + prep_ctx.dither = dither; + prep_ctx.color_space = color_space; + prep_ctx.preprocessing_enabled = preprocessing_enabled; + prep_ctx.work_factor_int = work_factor; + + prep_ctx.palette_type = chafa_palette_get_type (palette); + prep_ctx.bg_color_rgb = *chafa_palette_get_color (palette, + CHAFA_COLOR_SPACE_RGB, + CHAFA_PALETTE_INDEX_BG); + + prep_ctx.src_pixel_type = src_pixel_type; + prep_ctx.src_pixels = src_pixels; + prep_ctx.src_width = src_width; + prep_ctx.src_height = src_height; + prep_ctx.src_rowstride = src_rowstride; + + prep_ctx.dest_pixels = dest_pixels; + prep_ctx.dest_width = dest_width; + prep_ctx.dest_height = dest_height; + + prep_ctx.scale_ctx = smol_scale_new ((SmolPixelType) prep_ctx.src_pixel_type, + (const guint32 *) prep_ctx.src_pixels, + prep_ctx.src_width, + prep_ctx.src_height, + prep_ctx.src_rowstride, + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + NULL, + prep_ctx.dest_width, + prep_ctx.dest_height, + prep_ctx.dest_width * sizeof (guint32)); + + prepare_pixels_pass_1 (&prep_ctx); + prepare_pixels_pass_2 (&prep_ctx); + + smol_scale_destroy (prep_ctx.scale_ctx); +} + +void +chafa_sort_pixel_index_by_channel (guint8 *index, const ChafaPixel *pixels, gint n_pixels, gint ch) +{ + const gint gaps [] = { 57, 23, 10, 4, 1 }; + gint g, i, j; + + /* Since we don't care about stability and the number of elements + * is small and known in advance, use a simple in-place shellsort. + * + * Due to locality and callback overhead this is probably faster + * than qsort(), although admittedly I haven't benchmarked it. + * + * Another option is to use radix, but since we support multiple + * color spaces with fixed-point reals, we could get more buckets + * than is practical. */ + + for (g = 0; ; g++) + { + gint gap = gaps [g]; + + for (i = gap; i < n_pixels; i++) + { + guint8 ptemp = index [i]; + + for (j = i; j >= gap && pixels [index [j - gap]].col.ch [ch] + > pixels [ptemp].col.ch [ch]; j -= gap) + { + index [j] = index [j - gap]; + } + + index [j] = ptemp; + } + + /* After gap == 1 the array is always left sorted */ + if (gap == 1) + break; + } +} diff -Nru chafa-1.2.1/chafa/internal/chafa-pixops.h chafa-1.12.4/chafa/internal/chafa-pixops.h --- chafa-1.2.1/chafa/internal/chafa-pixops.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-pixops.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2020-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_PIXOPS_H__ +#define __CHAFA_PIXOPS_H__ + +#include +#include "internal/chafa-private.h" + +G_BEGIN_DECLS + +void chafa_prepare_pixel_data_for_symbols (const ChafaPalette *palette, + const ChafaDither *dither, + ChafaColorSpace color_space, + gboolean preprocessing_enabled, + gint work_factor, + ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, + gint src_height, + gint src_rowstride, + ChafaPixel *dest_pixels, + gint dest_width, + gint dest_height); + +void chafa_sort_pixel_index_by_channel (guint8 *index, + const ChafaPixel *pixels, gint n_pixels, + gint ch); + +void chafa_composite_rgba_on_solid_color (ChafaColor color, + ChafaPixel *pixels, gint width, gint first_row, gint n_rows); + +G_END_DECLS + +#endif /* __CHAFA_PIXOPS_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-popcnt.c chafa-1.12.4/chafa/internal/chafa-popcnt.c --- chafa-1.2.1/chafa/internal/chafa-popcnt.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-popcnt.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,115 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include "chafa.h" +#include "internal/chafa-private.h" + +gint +chafa_pop_count_u64_builtin (guint64 v) +{ +#if defined(HAVE_POPCNT64_INTRINSICS) + return (gint) _mm_popcnt_u64 (v); +#else /* HAVE_POPCNT32_INTRINSICS */ + const guint32 *w = (const guint32 *) &v; + return (gint) _mm_popcnt_u32(w[0]) + _mm_popcnt_u32(w[1]); +#endif +} + +void +chafa_pop_count_vu64_builtin (const guint64 *vv, gint *vc, gint n) +{ + while (n--) + { +#if defined(HAVE_POPCNT64_INTRINSICS) + *(vc++) = _mm_popcnt_u64 (*(vv++)); +#else /* HAVE_POPCNT32_INTRINSICS */ + const guint32 *w = (const guint32 *)vv; + *(vc++) = _mm_popcnt_u32(w[0]) + _mm_popcnt_u32(w[1]); + vv++; +#endif + } +} + +void +chafa_hamming_distance_vu64_builtin (guint64 a, const guint64 *vb, gint *vc, gint n) +{ +#if defined(HAVE_POPCNT64_INTRINSICS) + while (n >= 4) + { + n -= 4; + *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); + *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); + *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); + *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); + } + + while (n--) + { + *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); + } +#else /* HAVE_POPCNT32_INTRINSICS */ + const guint32 *aa = (const guint32 *) &a; + const guint32 *wb = (const guint32 *) vb; + + while (n--) + { + *(vc++) = _mm_popcnt_u32 (aa [0] ^ wb [0]) + _mm_popcnt_u32 (aa [1] ^ wb [1]); + wb += 2; + } +#endif +} + +/* Two bitmaps per item (a points to a pair, vb points to array of pairs) */ +void +chafa_hamming_distance_2_vu64_builtin (const guint64 *a, const guint64 *vb, gint *vc, gint n) +{ +#if defined(HAVE_POPCNT64_INTRINSICS) + while (n >= 4) + { + n -= 4; + *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); + *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); + *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); + *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); + *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); + *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); + *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); + *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); + } + + while (n--) + { + *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); + *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); + } +#else /* HAVE_POPCNT32_INTRINSICS */ + const guint32 *aa = (const guint32 *) a; + const guint32 *wb = (const guint32 *) vb; + + while (n--) + { + *(vc++) = _mm_popcnt_u32 (aa [0] ^ wb [0]) + _mm_popcnt_u32 (aa [1] ^ wb [1]) + + _mm_popcnt_u32 (aa [2] ^ wb [2]) + _mm_popcnt_u32 (aa [3] ^ wb [3]); + wb += 4; + } +#endif +} diff -Nru chafa-1.2.1/chafa/internal/chafa-private.h chafa-1.12.4/chafa/internal/chafa-private.h --- chafa-1.2.1/chafa/internal/chafa-private.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-private.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,275 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_PRIVATE_H__ +#define __CHAFA_PRIVATE_H__ + +#include +#include "internal/chafa-bitfield.h" +#include "internal/chafa-color-hash.h" +#include "internal/chafa-dither.h" +#include "internal/chafa-indexed-image.h" +#include "internal/chafa-iterm2-canvas.h" +#include "internal/chafa-kitty-canvas.h" +#include "internal/chafa-palette.h" +#include "internal/chafa-sixel-canvas.h" + +G_BEGIN_DECLS + +/* Character symbols and symbol classes */ + +#define CHAFA_N_SYMBOLS_MAX 1024 /* For static temp arrays */ +#define CHAFA_SYMBOL_N_PIXELS (CHAFA_SYMBOL_WIDTH_PIXELS * CHAFA_SYMBOL_HEIGHT_PIXELS) + +typedef struct +{ + ChafaSymbolTags sc; + gunichar c; + gchar *coverage; + gint fg_weight, bg_weight; + guint64 bitmap; + gint popcount; +} +ChafaSymbol; + +/* Double-width symbol */ +typedef struct +{ + ChafaSymbol sym [2]; +} +ChafaSymbol2; + +struct ChafaSymbolMap +{ + gint refs; + + guint need_rebuild : 1; + guint use_builtin_glyphs : 1; + + GHashTable *glyphs; + GHashTable *glyphs2; /* Wide glyphs with left/right bitmaps */ + GArray *selectors; + + /* Remaining fields are populated by chafa_symbol_map_prepare () */ + + /* Narrow symbols */ + ChafaSymbol *symbols; + gint n_symbols; + guint64 *packed_bitmaps; + + /* Wide symbols */ + ChafaSymbol2 *symbols2; + gint n_symbols2; + guint64 *packed_bitmaps2; +}; + +/* Symbol selection candidate */ + +typedef struct +{ + gint16 symbol_index; + guint8 hamming_distance; + guint8 is_inverted; +} +ChafaCandidate; + +/* Canvas config */ + +struct ChafaCanvasConfig +{ + gint refs; + + gint width, height; + gint cell_width, cell_height; + ChafaCanvasMode canvas_mode; + ChafaColorSpace color_space; + ChafaDitherMode dither_mode; + ChafaColorExtractor color_extractor; + ChafaPixelMode pixel_mode; + gint dither_grain_width, dither_grain_height; + gfloat dither_intensity; + guint32 fg_color_packed_rgb; + guint32 bg_color_packed_rgb; + gint alpha_threshold; /* 0-255. 255 = no alpha in output */ + gfloat work_factor; + ChafaSymbolMap symbol_map; + ChafaSymbolMap fill_symbol_map; + guint preprocessing_enabled : 1; + guint fg_only_enabled : 1; + ChafaOptimizations optimizations; +}; + +/* Canvas */ + +typedef struct ChafaCanvasCell ChafaCanvasCell; + +/* Library functions */ + +extern ChafaSymbol *chafa_symbols; +extern ChafaSymbol2 *chafa_symbols2; + +void chafa_init_palette (void); +void chafa_init_symbols (void); +ChafaSymbolTags chafa_get_tags_for_char (gunichar c); + +void chafa_init (void); +gboolean chafa_have_mmx (void) G_GNUC_PURE; +gboolean chafa_have_sse41 (void) G_GNUC_PURE; +gboolean chafa_have_popcnt (void) G_GNUC_PURE; + +void chafa_symbol_map_init (ChafaSymbolMap *symbol_map); +void chafa_symbol_map_deinit (ChafaSymbolMap *symbol_map); +void chafa_symbol_map_copy_contents (ChafaSymbolMap *dest, const ChafaSymbolMap *src); +void chafa_symbol_map_prepare (ChafaSymbolMap *symbol_map); +gboolean chafa_symbol_map_has_symbol (const ChafaSymbolMap *symbol_map, gunichar symbol); +void chafa_symbol_map_find_candidates (const ChafaSymbolMap *symbol_map, + guint64 bitmap, + gboolean do_inverse, + ChafaCandidate *candidates_out, + gint *n_candidates_inout); +void chafa_symbol_map_find_fill_candidates (const ChafaSymbolMap *symbol_map, + gint popcount, + gboolean do_inverse, + ChafaCandidate *candidates_out, + gint *n_candidates_inout); +void chafa_symbol_map_find_wide_candidates (const ChafaSymbolMap *symbol_map, + const guint64 *bitmaps, + gboolean do_inverse, + ChafaCandidate *candidates_out, + gint *n_candidates_inout); +void chafa_symbol_map_find_wide_fill_candidates (const ChafaSymbolMap *symbol_map, + gint popcount, + gboolean do_inverse, + ChafaCandidate *candidates_out, + gint *n_candidates_inout); + +void chafa_canvas_config_init (ChafaCanvasConfig *canvas_config); +void chafa_canvas_config_deinit (ChafaCanvasConfig *canvas_config); +void chafa_canvas_config_copy_contents (ChafaCanvasConfig *dest, const ChafaCanvasConfig *src); + +gint *chafa_gen_bayer_matrix (gint matrix_size, gdouble magnitude); + +/* Math stuff */ + +#ifdef HAVE_MMX_INTRINSICS +void calc_colors_mmx (const ChafaPixel *pixels, ChafaColorAccum *accums_out, const guint8 *cov); +void chafa_leave_mmx (void); +#else +# define chafa_leave_mmx() +#endif + +#ifdef HAVE_SSE41_INTRINSICS +gint calc_error_sse41 (const ChafaPixel *pixels, const ChafaColorPair *color_pair, const guint8 *cov) G_GNUC_PURE; +#endif + +#if defined(HAVE_POPCNT64_INTRINSICS) || defined(HAVE_POPCNT32_INTRINSICS) +#define HAVE_POPCNT_INTRINSICS +#endif + +#ifdef HAVE_POPCNT_INTRINSICS +gint chafa_pop_count_u64_builtin (guint64 v) G_GNUC_PURE; +void chafa_pop_count_vu64_builtin (const guint64 *vv, gint *vc, gint n); +void chafa_hamming_distance_vu64_builtin (guint64 a, const guint64 *vb, gint *vc, gint n); +void chafa_hamming_distance_2_vu64_builtin (const guint64 *a, const guint64 *vb, gint *vc, gint n); +#endif + +/* Inline functions */ + +static inline guint64 chafa_slow_pop_count (guint64 v) G_GNUC_UNUSED; +static inline gint chafa_population_count_u64 (guint64 v) G_GNUC_UNUSED; +static inline void chafa_population_count_vu64 (const guint64 *vv, gint *vc, gint n) G_GNUC_UNUSED; + +static inline guint64 +chafa_slow_pop_count (guint64 v) +{ + /* Generic population count from + * http://www.graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + * + * Peter Kankowski has more hacks, including better SIMD versions, at + * https://www.strchr.com/crc32_popcnt */ + + v = v - ((v >> 1) & (guint64) ~(guint64) 0 / 3); + v = (v & (guint64) ~(guint64) 0 / 15 * 3) + ((v >> 2) & (guint64) ~(guint64) 0 / 15 * 3); + v = (v + (v >> 4)) & (guint64) ~(guint64) 0 / 255 * 15; + return (guint64) (v * ((guint64) ~(guint64) 0 / 255)) >> (sizeof (guint64) - 1) * 8; +} + +static inline gint +chafa_population_count_u64 (guint64 v) +{ +#ifdef HAVE_POPCNT_INTRINSICS + if (chafa_have_popcnt ()) + return chafa_pop_count_u64_builtin (v); +#endif + + return chafa_slow_pop_count (v); +} + +static inline void +chafa_population_count_vu64 (const guint64 *vv, gint *vc, gint n) +{ +#ifdef HAVE_POPCNT_INTRINSICS + if (chafa_have_popcnt ()) + { + chafa_pop_count_vu64_builtin (vv, vc, n); + return; + } +#endif + + while (n--) + *(vc++) = chafa_slow_pop_count (*(vv++)); +} + +static inline void +chafa_hamming_distance_vu64 (guint64 a, const guint64 *vb, gint *vc, gint n) +{ +#ifdef HAVE_POPCNT_INTRINSICS + if (chafa_have_popcnt ()) + { + chafa_hamming_distance_vu64_builtin (a, vb, vc, n); + return; + } +#endif + + while (n--) + *(vc++) = chafa_slow_pop_count (a ^ *(vb++)); +} + +static inline void +chafa_hamming_distance_2_vu64 (const guint64 *a, const guint64 *vb, gint *vc, gint n) +{ +#ifdef HAVE_POPCNT_INTRINSICS + if (chafa_have_popcnt ()) + { + chafa_hamming_distance_2_vu64_builtin (a, vb, vc, n); + return; + } +#endif + + while (n--) + { + *(vc++) = chafa_slow_pop_count (a [0] ^ vb [0]) + + chafa_slow_pop_count (a [1] ^ vb [1]); + vb += 2; + } +} + +G_END_DECLS + +#endif /* __CHAFA_PRIVATE_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-sixel-canvas.c chafa-1.12.4/chafa/internal/chafa-sixel-canvas.c --- chafa-1.2.1/chafa/internal/chafa-sixel-canvas.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-sixel-canvas.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,464 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include "chafa.h" +#include "smolscale/smolscale.h" +#include "internal/chafa-batch.h" +#include "internal/chafa-bitfield.h" +#include "internal/chafa-indexed-image.h" +#include "internal/chafa-sixel-canvas.h" +#include "internal/chafa-string-util.h" + +#define SIXEL_CELL_HEIGHT 6 + +typedef struct +{ + ChafaSixelCanvas *sixel_canvas; + GString *out_str; +} +BuildSixelsCtx; + +typedef struct +{ + /* Lower six bytes are vertical pixel strip; LSB is bottom pixel */ + guint64 d; +} +SixelData; + +typedef struct +{ + SixelData *data; + ChafaBitfield filter_bits; +} +SixelRow; + +static gint +round_up_to_multiple_of (gint value, gint m) +{ + value = value + m - 1; + return value - (value % m); +} + +ChafaSixelCanvas * +chafa_sixel_canvas_new (gint width, gint height, + ChafaColorSpace color_space, + const ChafaPalette *palette, + const ChafaDither *dither) +{ + ChafaSixelCanvas *sixel_canvas; + + sixel_canvas = g_new (ChafaSixelCanvas, 1); + sixel_canvas->width = width; + sixel_canvas->height = height; + sixel_canvas->color_space = color_space; + sixel_canvas->image = chafa_indexed_image_new (width, round_up_to_multiple_of (height, SIXEL_CELL_HEIGHT), + palette, dither); + + return sixel_canvas; +} + +void +chafa_sixel_canvas_destroy (ChafaSixelCanvas *sixel_canvas) +{ + chafa_indexed_image_destroy (sixel_canvas->image); + g_free (sixel_canvas); +} + +void +chafa_sixel_canvas_draw_all_pixels (ChafaSixelCanvas *sixel_canvas, ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride) +{ + g_return_if_fail (sixel_canvas != NULL); + g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); + g_return_if_fail (src_pixels != NULL); + g_return_if_fail (src_width >= 0); + g_return_if_fail (src_height >= 0); + + if (src_width == 0 || src_height == 0) + return; + + chafa_indexed_image_draw_pixels (sixel_canvas->image, + sixel_canvas->color_space, + src_pixel_type, + src_pixels, + src_width, src_height, src_rowstride, + sixel_canvas->width, sixel_canvas->height); +} + +#define FILTER_BANK_WIDTH 64 + +static void +filter_set (SixelRow *srow, guint8 pen, gint bank) +{ + chafa_bitfield_set_bit (&srow->filter_bits, bank * 256 + (gint) pen, TRUE); +} + +static gboolean +filter_get (const SixelRow *srow, guint8 pen, gint bank) +{ + return chafa_bitfield_get_bit (&srow->filter_bits, bank * 256 + (gint) pen); +} + +static void +fetch_sixel_row (SixelRow *srow, const guint8 *pixels, gint width) +{ + const guint8 *pixels_end, *p; + SixelData *sdata = srow->data; + gint x; + + /* The ordering of output bytes is 351240; this is the inverse of + * 140325. see sixel_data_do_schar(). */ + + for (pixels_end = pixels + width, x = 0; pixels < pixels_end; pixels++, x++) + { + guint64 d; + gint bank = x / FILTER_BANK_WIDTH; + + p = pixels; + + filter_set (srow, *p, bank); + d = (guint64) *p; + p += width; + + filter_set (srow, *p, bank); + d |= (guint64) *p << (3 * 8); + p += width; + + filter_set (srow, *p, bank); + d |= (guint64) *p << (2 * 8); + p += width; + + filter_set (srow, *p, bank); + d |= (guint64) *p << (5 * 8); + p += width; + + filter_set (srow, *p, bank); + d |= (guint64) *p << (1 * 8); + p += width; + + filter_set (srow, *p, bank); + d |= (guint64) *p << (4 * 8); + + (sdata++)->d = d; + } +} + +static gchar +sixel_data_to_schar (const SixelData *sdata, guint64 expanded_pen) +{ + guint64 a; + gchar c; + + a = ~(sdata->d ^ expanded_pen); + + /* Matching bytes will now contain 0xff. Any other value is a mismatch. */ + + a &= (a & 0x0000f0f0f0f0f0f0) >> 4; + a &= (a & 0x00000c0c0c0c0c0c) >> 2; + a &= (a & 0x0000020202020202) >> 1; + + /* Matching bytes will now contain 0x01. Misses contain 0x00. */ + + a |= a >> (24 - 1); + a |= a >> (16 - 2); + a |= a >> (8 - 4); + + /* Set bits are now packed in the lower 6 bits, reordered like this: + * + * 012345 -> 03/14/25 -> 14/0325 -> 140325 */ + + c = a & 0x3f; + + return '?' + c; +} + +static gchar * +format_schar_reps (gchar rep_schar, gint n_reps, gchar *p) +{ + g_assert (n_reps > 0); + + for (;;) + { + if (n_reps < 4) + { + do *(p++) = rep_schar; + while (--n_reps); + + goto out; + } + else if (n_reps < 255) + { + *(p++) = '!'; + p = chafa_format_dec_u8 (p, n_reps); + *(p++) = rep_schar; + goto out; + } + else + { + strcpy (p, "!255"); + p += 4; + *(p++) = rep_schar; + n_reps -= 255; + + if (n_reps == 0) + goto out; + } + } + +out: + return p; +} + +static gchar * +format_pen (guint8 pen, gchar *p) +{ + *(p++) = '#'; + return chafa_format_dec_u8 (p, pen); +} + +/* force_full_width is a workaround for a bug in mlterm; we need to + * draw the entire first row even if the rightmost pixels are transparent, + * otherwise the first row with non-transparent pixels will have + * garbage rendered in it */ +static gchar * +build_sixel_row_ansi (const ChafaSixelCanvas *scanvas, const SixelRow *srow, gchar *p, gboolean force_full_width) +{ + gint pen = 0; + gboolean need_cr = FALSE; + gboolean need_cr_next = FALSE; + const SixelData *sdata = srow->data; + gint width = scanvas->width; + + do + { + guint64 expanded_pen; + gboolean need_pen = TRUE; + gchar rep_schar; + gint n_reps; + gint i; + + if (pen == chafa_palette_get_transparent_index (&scanvas->image->palette)) + continue; + + /* Assign pen value to each of lower six bytes */ + expanded_pen = pen; + expanded_pen |= expanded_pen << 8; + expanded_pen |= expanded_pen << 16; + expanded_pen |= expanded_pen << 16; + + rep_schar = 0; + n_reps = 0; + + for (i = 0; i < width; ) + { + gint step = MIN (FILTER_BANK_WIDTH, width - i); + gchar schar; + + /* Skip over FILTER_BANK_WIDTH sixels at once if possible */ + + if (!filter_get (srow, pen, i / FILTER_BANK_WIDTH)) + { + if (rep_schar != '?' && rep_schar != 0) + { + if (need_cr) + { + *(p++) = '$'; + need_cr = FALSE; + } + if (need_pen) + { + p = format_pen (pen, p); + need_pen = FALSE; + } + + p = format_schar_reps (rep_schar, n_reps, p); + need_cr_next = TRUE; + n_reps = 0; + } + + rep_schar = '?'; + n_reps += step; + i += step; + continue; + } + + /* The pen appears in this bank; iterate over sixels */ + + for ( ; step > 0; step--, i++) + { + schar = sixel_data_to_schar (&sdata [i], expanded_pen); + + if (schar == rep_schar) + { + n_reps++; + } + else if (rep_schar == 0) + { + rep_schar = schar; + n_reps = 1; + } + else + { + if (need_cr) + { + *(p++) = '$'; + need_cr = FALSE; + } + if (need_pen) + { + p = format_pen (pen, p); + need_pen = FALSE; + } + + p = format_schar_reps (rep_schar, n_reps, p); + need_cr_next = TRUE; + + rep_schar = schar; + n_reps = 1; + } + } + } + + if (rep_schar != '?' || force_full_width) + { + if (need_cr) + { + *(p++) = '$'; + need_cr = FALSE; + } + if (need_pen) + { + p = format_pen (pen, p); + need_pen = FALSE; + } + + p = format_schar_reps (rep_schar, n_reps, p); + need_cr_next = TRUE; + + /* Only need to do this for a single pen */ + force_full_width = FALSE; + } + + need_cr = need_cr_next; + } + while (++pen < chafa_palette_get_n_colors (&scanvas->image->palette)); + + *(p++) = '-'; + return p; +} + +static void +build_sixel_row_worker (ChafaBatchInfo *batch, const BuildSixelsCtx *ctx) +{ + SixelRow srow; + gchar *sixel_ansi, *p; + gint n_sixel_rows; + gint i; + + n_sixel_rows = (batch->n_rows + SIXEL_CELL_HEIGHT - 1) / SIXEL_CELL_HEIGHT; + srow.data = g_malloc (sizeof (SixelData) * ctx->sixel_canvas->width); + chafa_bitfield_init (&srow.filter_bits, ((ctx->sixel_canvas->width + FILTER_BANK_WIDTH - 1) / FILTER_BANK_WIDTH) * 256); + + sixel_ansi = p = g_malloc (256 * (ctx->sixel_canvas->width + 5) * n_sixel_rows + 1); + + for (i = 0; i < n_sixel_rows; i++) + { + fetch_sixel_row (&srow, + ctx->sixel_canvas->image->pixels + + ctx->sixel_canvas->image->width * (batch->first_row + i * SIXEL_CELL_HEIGHT), + ctx->sixel_canvas->image->width); + p = build_sixel_row_ansi (ctx->sixel_canvas, &srow, p, + (i == 0) || (i == n_sixel_rows - 1) + ? TRUE : FALSE); + chafa_bitfield_clear (&srow.filter_bits); + } + + batch->ret_p = sixel_ansi; + batch->ret_n = p - sixel_ansi; + + chafa_bitfield_deinit (&srow.filter_bits); + g_free (srow.data); +} + +static void +build_sixel_row_post (ChafaBatchInfo *batch, BuildSixelsCtx *ctx) +{ + g_string_append_len (ctx->out_str, batch->ret_p, batch->ret_n); + g_free (batch->ret_p); +} + +static void +build_sixel_palette (ChafaSixelCanvas *sixel_canvas, GString *out_str) +{ + gchar str [256 * 20 + 1]; + gchar *p = str; + gint first_color; + gint pen; + + first_color = chafa_palette_get_first_color (&sixel_canvas->image->palette); + + for (pen = 0; pen < chafa_palette_get_n_colors (&sixel_canvas->image->palette); pen++) + { + const ChafaColor *col; + + if (pen == chafa_palette_get_transparent_index (&sixel_canvas->image->palette)) + continue; + + col = chafa_palette_get_color (&sixel_canvas->image->palette, CHAFA_COLOR_SPACE_RGB, + first_color + pen); + *(p++) = '#'; + p = chafa_format_dec_u8 (p, pen); + *(p++) = ';'; + *(p++) = '2'; /* Color space: RGB */ + *(p++) = ';'; + + /* Sixel color channel range is 0..100 */ + + p = chafa_format_dec_u8 (p, (col->ch [0] * 100) / 255); + *(p++) = ';'; + p = chafa_format_dec_u8 (p, (col->ch [1] * 100) / 255); + *(p++) = ';'; + p = chafa_format_dec_u8 (p, (col->ch [2] * 100) / 255); + } + + g_string_append_len (out_str, str, p - str); +} + +void +chafa_sixel_canvas_build_ansi (ChafaSixelCanvas *sixel_canvas, GString *out_str) +{ + BuildSixelsCtx ctx; + + g_assert (sixel_canvas->image->height % SIXEL_CELL_HEIGHT == 0); + + ctx.sixel_canvas = sixel_canvas; + ctx.out_str = out_str; + + build_sixel_palette (sixel_canvas, out_str); + + chafa_process_batches (&ctx, + (GFunc) build_sixel_row_worker, + (GFunc) build_sixel_row_post, + sixel_canvas->image->height, + chafa_get_n_actual_threads (), + SIXEL_CELL_HEIGHT); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-sixel-canvas.h chafa-1.12.4/chafa/internal/chafa-sixel-canvas.h --- chafa-1.2.1/chafa/internal/chafa-sixel-canvas.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-sixel-canvas.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_SIXEL_CANVAS_H__ +#define __CHAFA_SIXEL_CANVAS_H__ + +#include "chafa.h" + +G_BEGIN_DECLS + +typedef struct +{ + gint width, height; + ChafaColorSpace color_space; + ChafaIndexedImage *image; +} +ChafaSixelCanvas; + +ChafaSixelCanvas *chafa_sixel_canvas_new (gint width, gint height, + ChafaColorSpace color_space, + const ChafaPalette *palette, + const ChafaDither *dither); +void chafa_sixel_canvas_destroy (ChafaSixelCanvas *sixel_canvas); + +void chafa_sixel_canvas_draw_all_pixels (ChafaSixelCanvas *sixel_canvas, ChafaPixelType src_pixel_type, + gconstpointer src_pixels, + gint src_width, gint src_height, gint src_rowstride); +void chafa_sixel_canvas_build_ansi (ChafaSixelCanvas *sixel_canvas, GString *out_str); + +G_END_DECLS + +#endif /* __CHAFA_SIXEL_CANVAS_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-sse41.c chafa-1.12.4/chafa/internal/chafa-sse41.c --- chafa-1.2.1/chafa/internal/chafa-sse41.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-sse41.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include "chafa.h" +#include "internal/chafa-private.h" + +gint +calc_error_sse41 (const ChafaPixel *pixels, const ChafaColorPair *color_pair, const guint8 *cov) +{ + const guint32 *u32p0 = (const guint32 *) pixels; + const guint32 *u32p1 = (const guint32 *) color_pair->colors; + __m128i err4 = { 0 }; + const gint32 *e = (gint32 *) &err4; + gint i; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + __m128i t0, t1, t; + + t0 = _mm_cvtepu8_epi32 (_mm_cvtsi32_si128 (u32p0 [i])); + t1 = _mm_cvtepu8_epi32 (_mm_cvtsi32_si128 (u32p1 [cov [i]])); + + t = t0 - t1; + t = _mm_mullo_epi32 (t, t); + err4 += t; + } + + return e [0] + e [1] + e [2]; +} diff -Nru chafa-1.2.1/chafa/internal/chafa-string-util.c chafa-1.12.4/chafa/internal/chafa-string-util.c --- chafa-1.2.1/chafa/internal/chafa-string-util.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-string-util.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,128 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2021-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include "internal/chafa-string-util.h" + +/* Generate a const table of the ASCII decimal numbers 0..255, avoiding leading + * zeroes. Each entry is exactly 4 bytes. The strings are not zero-terminated; + * instead their lengths are stored in the 4th byte, potentially leaving a gap + * between the string and the length. + * + * This allows us to fetch a string using fixed-length memcpy() followed by + * incrementing the target pointer. We copy all four bytes (32 bits) in the + * hope that the compiler will generate register-wide loads and stores where + * alignment is not an issue. + * + * The idea is to speed up printing for decimal numbers in this range (common + * with palette indexes and color channels) at the cost of exactly 1kiB in the + * executable. + * + * We require C99 due to __VA_ARGS__ and designated init but use no extensions. */ + +#define GEN_1(len, ...) { __VA_ARGS__, [3] = len } + +#define GEN_10(len, ...) \ + GEN_1 (len, __VA_ARGS__, '0'), GEN_1 (len, __VA_ARGS__, '1'), \ + GEN_1 (len, __VA_ARGS__, '2'), GEN_1 (len, __VA_ARGS__, '3'), \ + GEN_1 (len, __VA_ARGS__, '4'), GEN_1 (len, __VA_ARGS__, '5'), \ + GEN_1 (len, __VA_ARGS__, '6'), GEN_1 (len, __VA_ARGS__, '7'), \ + GEN_1 (len, __VA_ARGS__, '8'), GEN_1 (len, __VA_ARGS__, '9') + +#define GEN_100(len, ...) \ + GEN_10 (len, __VA_ARGS__, '0'), GEN_10 (len, __VA_ARGS__, '1'), \ + GEN_10 (len, __VA_ARGS__, '2'), GEN_10 (len, __VA_ARGS__, '3'), \ + GEN_10 (len, __VA_ARGS__, '4'), GEN_10 (len, __VA_ARGS__, '5'), \ + GEN_10 (len, __VA_ARGS__, '6'), GEN_10 (len, __VA_ARGS__, '7'), \ + GEN_10 (len, __VA_ARGS__, '8'), GEN_10 (len, __VA_ARGS__, '9') + +const char chafa_ascii_dec_u8 [256] [4] = +{ + /* 0-9 */ + GEN_1 (1, '0'), GEN_1 (1, '1'), GEN_1 (1, '2'), GEN_1 (1, '3'), + GEN_1 (1, '4'), GEN_1 (1, '5'), GEN_1 (1, '6'), GEN_1 (1, '7'), + GEN_1 (1, '8'), GEN_1 (1, '9'), + + /* 10-99 */ + GEN_10 (2, '1'), GEN_10 (2, '2'), GEN_10 (2, '3'), GEN_10 (2, '4'), + GEN_10 (2, '5'), GEN_10 (2, '6'), GEN_10 (2, '7'), GEN_10 (2, '8'), + GEN_10 (2, '9'), + + /* 100-199 */ + GEN_100 (3, '1'), + + /* 200-249 */ + GEN_10 (3, '2', '0'), GEN_10 (3, '2', '1'), GEN_10 (3, '2', '2'), + GEN_10 (3, '2', '3'), GEN_10 (3, '2', '4'), + + /* 250-255 */ + GEN_1 (3, '2', '5', '0'), GEN_1 (3, '2', '5', '1'), GEN_1 (3, '2', '5', '2'), + GEN_1 (3, '2', '5', '3'), GEN_1 (3, '2', '5', '4'), GEN_1 (3, '2', '5', '5') +}; + +/* We need this because reg may contain garbage that will end up being + * harmlessly dumped past end-of-output. Avoiding initialization saves + * us approx. 3%, enough to matter. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" + +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#pragma clang diagnostic ignored "-Wuninitialized" + +gchar * +chafa_format_dec_uint_0_to_9999 (char *dest, guint arg) +{ + guint n, m; + guint32 reg; + gint i = 0; + + m = arg < 9999 ? arg : 9999; + + /* Reduce argument one decimal digit at a time and shift their + * ASCII equivalents into a register. The register can usually be + * written to memory all at once. memcpy() will do that if possible + * while keeping us safe from potential alignment issues. + * + * We take advantage of the fact that registers are backwards on + * x86 to reverse the result. GUINT32_TO_LE() will be a no-op there. + * On BE archs, it will manually reverse using a bswap. + * + * With -O2 -fno-inline, this is approx. 15 times faster than sprintf() + * in my tests. */ + + do + { + n = (m * (((1 << 15) + 9) / 10)) >> 15; + reg <<= 8; + reg |= '0' + (m - n * 10); + m = n; + i++; + } + while (m != 0); + + reg = GUINT32_TO_LE (reg); + memcpy (dest, ®, 4); + return dest + i; +} + +#pragma clang diagnostic pop +#pragma GCC diagnostic pop diff -Nru chafa-1.2.1/chafa/internal/chafa-string-util.h chafa-1.12.4/chafa/internal/chafa-string-util.h --- chafa-1.2.1/chafa/internal/chafa-string-util.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-string-util.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2021-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_STRING_UTIL_H__ +#define __CHAFA_STRING_UTIL_H__ + +#include +#include + +G_BEGIN_DECLS + +extern const char chafa_ascii_dec_u8 [256] [4]; + +/* Will overwrite 4 bytes starting at dest. Returns a pointer to the first + * byte after the formatted ASCII decimal number (dest + 1..3). */ +static inline gchar * +chafa_format_dec_u8 (gchar *dest, guint8 n) +{ + memcpy (dest, &chafa_ascii_dec_u8 [n] [0], 4); + return dest + chafa_ascii_dec_u8 [n] [3]; +} + +/* Will overwrite 4 bytes starting at dest. Returns a pointer to the first + * byte after the formatted ASCII decimal number (dest + 1..4). */ +gchar *chafa_format_dec_uint_0_to_9999 (char *dest, guint arg); + +G_END_DECLS + +#endif /* __CHAFA_STRING_UTIL_H__ */ diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols-ascii.h chafa-1.12.4/chafa/internal/chafa-symbols-ascii.h --- chafa-1.2.1/chafa/internal/chafa-symbols-ascii.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols-ascii.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,1359 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* This is meant to be #included in the symbol definition table of + * chafa-symbols.c. It's kept in a separate file due to its size. + * + * These are 7-bit ASCII symbols. The bitmaps are a close match to the + * Terminus font (specifically ter-x14n.pcf). + * + * ASCII symbols are also "Latin" symbols, loosely corresponding to the + * symbols you'd find in the European ISO/IEC 8859 charsets. + */ + + { + /* [ ] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN | CHAFA_SYMBOL_TAG_SPACE, + 0x20, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [!] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x21, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X " + " X " + " " + " X " + " ") + }, + { + /* ["] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x22, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " X X " + " " + " " + " " + " " + " " + " ") + }, + { + /* [#] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x23, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXX XX " + " XX XXX " + " X X " + " X X " + " ") + }, + { + /* [$] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x24, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " XXXXX " + "X X " + " XXXXXX " + " X X " + " XXXXX " + " X ") + }, + { + /* [%] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x25, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX X " + " XX X " + " XX " + " XX " + " X XX " + " X XX " + " ") + }, + { + /* [&] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x26, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XXX " + " X X X " + " X X " + " XXX X " + " ") + }, + { + /* ['] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x27, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " " + " " + " " + " " + " ") + }, + { + /* [(] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x28, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X " + " X " + " X " + " XX " + " ") + }, + { + /* [)] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x29, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X " + " X " + " X " + " XX " + " ") + }, + { + /* [*] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x2a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXX " + " XXXXXX " + " X X " + " " + " ") + }, + { + /* [+] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x2b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X " + " XXXXX " + " X " + " " + " ") + }, + { + /* [,] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x2c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " X " + " X ") + }, + { + /* [-] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x2d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XXXXXX " + " " + " " + " ") + }, + { + /* [.] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x2e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " X " + " ") + }, + { + /* [/] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x2f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " XX " + " X " + " X " + " ") + }, + { + /* [0] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x30, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X XXX " + " XXX X " + " X X " + " XXXX " + " ") + }, + { + /* [1] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x31, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " X " + " X " + " X " + " XXXXX " + " ") + }, + { + /* [2] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x32, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [3] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x33, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [4] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x34, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XX X " + " X X " + " XXXXXX " + " X " + " X " + " ") + }, + { + /* [5] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x35, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [6] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x36, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X " + " XXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [7] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x37, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [8] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x38, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [9] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x39, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " XXXXX " + " X " + " XXXX " + " ") + }, + { + /* [:] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x3a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X " + " " + " " + " X " + " ") + }, + { + /* [;] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x3b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X " + " " + " " + " X " + " X ") + }, + { + /* [<] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x3c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " XX " + " XX " + " X " + " ") + }, + { + /* [=] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x3d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXXXX " + " " + " " + " ") + }, + { + /* [>] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x3e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " XX " + " XX " + " X " + " ") + }, + { + /* [?] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x3f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X XX " + " X " + " " + " X " + " ") + }, + { + /* [@] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x40, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + "X X " + "X XXX X " + "X X XX " + "X XX " + " XXXXXX " + " ") + }, + { + /* [A] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x41, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [B] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x42, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " XXXXX " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [C] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x43, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [D] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x44, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [E] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x45, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [F] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x46, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " X " + " ") + }, + { + /* [G] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x47, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " ") + }, + { + /* [H] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x48, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [I] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x49, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [J] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x4a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X X " + " XXX " + " ") + }, + { + /* [K] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x4b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X XX " + " XXX " + " XXX " + " X XX " + " X X " + " ") + }, + { + /* [L] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x4c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [M] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x4d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XX XX " + "X X X X " + "X X X " + "X X " + "X X " + "X X " + " ") + }, + { + /* [N] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x4e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX X " + " X X X " + " X X X " + " X XX " + " X X " + " ") + }, + { + /* [O] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x4f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [P] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x50, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X " + " X " + " ") + }, + { + /* [Q] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x51, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " X ") + }, + { + /* [R] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x52, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X XX " + " X X " + " ") + }, + { + /* [S] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x53, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [T] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x54, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXX " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [U] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x55, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [V] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x56, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " X X " + " X X " + " X X " + " XX " + " ") + }, + { + /* [W] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x57, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X X " + "X X " + "X X " + "X X X " + "X X X X " + "XX XX " + " ") + }, + { + /* [X] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x58, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XX " + " XX " + " X X " + " X X " + " ") + }, + { + /* [Y] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x59, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X X " + " X X " + " XX XX " + " X " + " X " + " X " + " ") + }, + { + /* [Z] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x5a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [[] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x5b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [\] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x5c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " XX " + " X " + " X " + " ") + }, + { + /* []] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x5d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [^] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x5e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X X " + " X X " + " " + " " + " " + " " + " ") + }, + { + /* [_] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x5f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " " + " XXXXXX ") + }, + { + /* [`] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x60, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [a] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x61, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [b] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x62, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [c] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x63, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [d] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x64, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [e] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x65, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [f] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x66, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " XXXXX " + " X " + " X " + " X " + " ") + }, + { + /* [g] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x67, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [h] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x68, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [i] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x69, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [j] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x6a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XX " + " X " + " X " + " X X " + " XXX ") + }, + { + /* [k] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x6b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X XX " + " XXXX " + " X X " + " X XX " + " ") + }, + { + /* [l] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x6c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [m] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x6d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXX " + "X X X " + "X X X " + "X X X " + " ") + }, + { + /* [n] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x6e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [o] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x6f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [p] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x70, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " X ") + }, + { + /* [q] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x71, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " X ") + }, + { + /* [r] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x72, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X " + " X " + " ") + }, + { + /* [s] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x73, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXXX " + " X " + " XXXXX " + " ") + }, + { + /* [t] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x74, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXX " + " X " + " X " + " XXX " + " ") + }, + { + /* [u] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x75, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [v] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x76, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " XX XX " + " X X " + " XX " + " ") + }, + { + /* [w] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x77, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "X X " + "X X X " + "X X X " + " XXXXX " + " ") + }, + { + /* [x] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x78, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " XXXX " + " X X " + " X X " + " ") + }, + { + /* [y] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x79, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [z] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x7a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XX " + " XX " + " XXXXXX " + " ") + }, + { + /* [{] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x7b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X " + " X " + " X " + " XX " + " ") + }, + { + /* [|] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x7c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [}] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x7d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X " + " X " + " X " + " XX " + " ") + }, + { + /* [~] */ + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, + 0x7e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX X " + "X XXX " + " " + " " + " " + " " + " " + " ") + }, diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols-ascii-ibm.h chafa-1.12.4/chafa/internal/chafa-symbols-ascii-ibm.h --- chafa-1.2.1/chafa/internal/chafa-symbols-ascii-ibm.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols-ascii-ibm.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,1289 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* This is meant to be #included in the symbol definition table of + * chafa-symbols.c. It's kept in a separate file due to its size. + * + * The symbol bitmaps are derived from https://github.com/dhepper/font8x8 by + * Daniel Hepper . Excerpt from the accompanying README: + * + * 8x8 monochrome bitmap font for rendering + * ======================================== + * + * A collection of header files containing a 8x8 bitmap font. + * + * [...] + * + * Author: Daniel Hepper + * License: Public Domain + * + * Credits + * ======= + * + * These header files are directly derived from an assembler file fetched from: + * http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm + * + * Original header: + * + * ; Summary: font8_8.asm + * ; 8x8 monochrome bitmap fonts for rendering + * ; + * ; Author: + * ; Marcel Sondaar + * ; International Business Machines (public domain VGA fonts) + * ; + * ; License: + * ; Public Domain + */ + + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_SPACE, + ' ', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '!', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXX " + " XXXX " + " XX " + " XX " + " " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '"', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX XX " + " XX XX " + " " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '#', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX XX " + " XX XX " + "XXXXXXX " + " XX XX " + "XXXXXXX " + " XX XX " + " XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '$', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXXX " + "XX " + " XXXX " + " XX " + "XXXXX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '%', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XX XX " + "XX XX " + " XX " + " XX " + " XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '&', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX XX " + " XXX " + " XXX XX " + "XX XXX " + "XX XX " + " XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + 0x27, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + "XX " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '(', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + ')', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '*', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX XX " + " XXXX " + "XXXXXXXX" + " XXXX " + " XX XX " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '+', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XX " + "XXXXXX " + " XX " + " XX " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + ',', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '-', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXX " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '.', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '/', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XX " + "XX " + "X " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '0', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXX " + "XX XX " + "XX XXX " + "XX XXXX " + "XXXX XX " + "XXX XX " + " XXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '1', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXX " + " XX " + " XX " + " XX " + " XX " + "XXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '2', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + " XX " + " XXX " + " XX " + "XX XX " + "XXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '3', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + " XX " + " XXX " + " XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '4', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXX " + " XX XX " + "XX XX " + "XXXXXXX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '5', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + "XX " + "XXXXX " + " XX " + " XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '6', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX " + "XX " + "XXXXX " + "XX XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '7', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + "XX XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '8', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + "XX XX " + " XXXX " + "XX XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, + '9', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + "XX XX " + " XXXXX " + " XX " + " XX " + " XXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + ':', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XX " + " " + " " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + ';', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XX " + " " + " " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '<', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XX " + " XX " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '=', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXX " + " " + " " + "XXXXXX " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '>', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '?', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + " XX " + " XX " + " XX " + " " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '@', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXX " + "XX XX " + "XX XXXX " + "XX XXXX " + "XX XXXX " + "XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'A', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXX " + "XX XX " + "XX XX " + "XXXXXX " + "XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'B', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + " XX XX " + " XX XX " + " XXXXX " + " XX XX " + " XX XX " + "XXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'C', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX XX " + "XX " + "XX " + "XX " + " XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'D', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXX " + " XX XX " + " XX XX " + " XX XX " + " XX XX " + " XX XX " + "XXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'E', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXX " + " XX X " + " XX X " + " XXXX " + " XX X " + " XX X " + "XXXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'F', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXX " + " XX X " + " XX X " + " XXXX " + " XX X " + " XX " + "XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'G', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX XX " + "XX " + "XX " + "XX XXX " + " XX XX " + " XXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'H', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + "XX XX " + "XXXXXX " + "XX XX " + "XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'I', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'J', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX " + " XX " + " XX " + "XX XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'K', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXX XX " + " XX XX " + " XX XX " + " XXXX " + " XX XX " + " XX XX " + "XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'L', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + " XX " + " XX " + " XX " + " XX X " + " XX XX " + "XXXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'M', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XXX XXX " + "XXXXXXX " + "XXXXXXX " + "XX X XX " + "XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'N', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XXX XX " + "XXXX XX " + "XX XXXX " + "XX XXX " + "XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'O', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX XX " + "XX XX " + "XX XX " + "XX XX " + " XX XX " + " XXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'P', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + " XX XX " + " XX XX " + " XXXXX " + " XX " + " XX " + "XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'Q', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + "XX XX " + "XX XX " + "XX XXX " + " XXXX " + " XXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'R', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + " XX XX " + " XX XX " + " XXXXX " + " XX XX " + " XX XX " + "XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'S', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + "XX XX " + "XXX " + " XXX " + " XXX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'T', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + "X XX X " + " XX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'U', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + "XX XX " + "XX XX " + "XX XX " + "XX XX " + "XXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'V', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + "XX XX " + "XX XX " + "XX XX " + " XXXX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'W', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + "XX XX " + "XX X XX " + "XXXXXXX " + "XXX XXX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'X', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + " XX XX " + " XXX " + " XXX " + " XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'Y', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + "XX XX " + " XXXX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'Z', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXX " + "XX XX " + "X XX " + " XX " + " XX X " + " XX XX " + "XXXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '[', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '\\', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " X " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + ']', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '^', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXX " + " XX XX " + "XX XX " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '_', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + "XXXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '`', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'a', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + " XX " + " XXXXX " + "XX XX " + " XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'b', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXX " + " XX " + " XX " + " XXXXX " + " XX XX " + " XX XX " + "XX XXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'c', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + "XX XX " + "XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'd', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX " + " XX " + " XXXXX " + "XX XX " + "XX XX " + " XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'e', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + "XX XX " + "XXXXXX " + "XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'f', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX XX " + " XX " + "XXXX " + " XX " + " XX " + "XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'g', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXX XX " + "XX XX " + "XX XX " + " XXXXX " + " XX " + "XXXXX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'h', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXX " + " XX " + " XX XX " + " XXX XX " + " XX XX " + " XX XX " + "XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'i', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " " + " XXX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'j', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " " + " XX " + " XX " + " XX " + "XX XX " + "XX XX " + " XXXX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'k', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXX " + " XX " + " XX XX " + " XX XX " + " XXXX " + " XX XX " + "XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'l', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'm', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XX " + "XXXXXXX " + "XXXXXXX " + "XX X XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'n', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXX " + "XX XX " + "XX XX " + "XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'o', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + "XX XX " + "XX XX " + "XX XX " + " XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'p', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XXX " + " XX XX " + " XX XX " + " XXXXX " + " XX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'q', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXX XX " + "XX XX " + "XX XX " + " XXXXX " + " XX " + " XXXX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'r', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XXX " + " XXX XX " + " XX XX " + " XX " + "XXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 's', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXXX " + "XX " + " XXXX " + " XX " + "XXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 't', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XX " + " XXXXX " + " XX " + " XX " + " XX X " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'u', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XX " + "XX XX " + "XX XX " + "XX XX " + " XXX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'v', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XX " + "XX XX " + "XX XX " + " XXXX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'w', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XX " + "XX X XX " + "XXXXXXX " + "XXXXXXX " + " XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'x', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XX " + " XX XX " + " XXX " + " XX XX " + "XX XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'y', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XX XX " + "XX XX " + "XX XX " + " XXXXX " + " XX " + "XXXXX ") + }, + { + CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, + 'z', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXX " + "X XX " + " XX " + " XX X " + "XXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '{', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX " + " XX " + "XXX " + " XX " + " XX " + " XXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '|', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " " + " XX " + " XX " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '}', + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXX " + " XX " + " XX " + " XXX " + " XX " + " XX " + "XXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_ASCII, + '~', + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX XX " + "XX XXX " + " " + " " + " " + " " + " " + " ") + }, diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols-block.h chafa-1.12.4/chafa/internal/chafa-symbols-block.h --- chafa-1.2.1/chafa/internal/chafa-symbols-block.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols-block.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,2891 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* Block and border characters + * --------------------------- + * + * This is meant to be #included in the symbol definition table of + * chafa-symbols.c. It's kept in a separate file due to its size. */ + + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_VHALF | CHAFA_SYMBOL_TAG_INVERTED, + 0x2580, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2581, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " " + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2582, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2583, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_VHALF, + 0x2584, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2585, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2586, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2587, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + /* Full block */ + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_SOLID | CHAFA_SYMBOL_TAG_SEXTANT, + 0x2588, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x2589, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXX " + "XXXXXXX " + "XXXXXXX " + "XXXXXXX " + "XXXXXXX " + "XXXXXXX " + "XXXXXXX " + "XXXXXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x258a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x258b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXX " + "XXXXX " + "XXXXX " + "XXXXX " + "XXXXX " + "XXXXX " + "XXXXX " + "XXXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_HHALF | CHAFA_SYMBOL_TAG_SEXTANT, + 0x258c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXX " + "XXXX " + "XXXX " + "XXXX " + "XXXX " + "XXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x258d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXX " + "XXX " + "XXX " + "XXX " + "XXX " + "XXX " + "XXX " + "XXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x258e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + "XX " + "XX " + "XX " + "XX " + "XX " + "XX " + "XX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK, + 0x258f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X " + "X " + "X " + "X " + "X " + "X " + "X " + "X ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_HHALF | CHAFA_SYMBOL_TAG_INVERTED | CHAFA_SYMBOL_TAG_SEXTANT, + 0x2590, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXX" + " XXXX" + " XXXX" + " XXXX" + " XXXX" + " XXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_INVERTED, + 0x2594, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + " " + " " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_INVERTED, + 0x2595, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X" + " X" + " X" + " X" + " X" + " X" + " X" + " X") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, + 0x2596, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXX " + "XXXX " + "XXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, + 0x2597, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XXXX" + " XXXX" + " XXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, + 0x2598, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXX " + "XXXX " + "XXXX " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, + 0x2599, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXX " + "XXXX " + "XXXX " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, + 0x259a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXX " + "XXXX " + "XXXX " + " XXXX" + " XXXX" + " XXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, + 0x259b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXX " + "XXXX " + "XXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, + 0x259c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " XXXX" + " XXXX" + " XXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, + 0x259d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXX" + " XXXX" + " XXXX" + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, + 0x259e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXX" + " XXXX" + " XXXX" + "XXXX " + "XXXX " + "XXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, + 0x259f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXX" + " XXXX" + " XXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + /* Begin box drawing characters */ + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2500, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2501, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2502, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2503, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2504, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XX XX XX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2505, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XX XX XX" + "XX XX XX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2506, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " X " + " X " + " " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2507, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " " + " XX " + " XX " + " " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2508, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "X X X X " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2509, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "X X X X " + "X X X X " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x250a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " " + " X " + " " + " X " + " " + " X " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x250b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " " + " XX " + " " + " XX " + " " + " XX " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x250c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x250d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXX" + " XXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x250e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x250f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX" + " XXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2510, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXX " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2511, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXX " + "XXXXX " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2512, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2513, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXX " + "XXXXX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2514, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " XXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2515, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " XXXX" + " XXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2516, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2517, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XXXXX" + " XXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2518, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + "XXXXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2519, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXX " + "XXXXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x251a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + "XXXXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x251b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXX " + "XXXXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x251c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " XXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x251d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " XXXX" + " XXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x251e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x251f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " XXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2520, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " XXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2521, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XXXXX" + " XXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2522, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " XXXXX" + " XXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2523, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XXXXX" + " XXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2524, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + "XXXXX " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2525, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXX " + "XXXXX " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2526, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + "XXXXX " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2527, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + "XXXXX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2528, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + "XXXXX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2529, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXX " + "XXXXX " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x252a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXX " + "XXXXX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x252b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXX " + "XXXXX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x252c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x252d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXX " + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x252e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXX" + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x252f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2530, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2531, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXX " + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2532, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX" + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2533, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXXX" + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2534, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2535, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXX " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2536, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " XXXX" + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2537, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXXXXX" + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2538, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2539, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXX " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x253a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XXXXX" + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x253b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXXXXX" + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x253c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x253d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXX " + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x253e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " XXXX" + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x253f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXXXXX" + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2540, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2541, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2542, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2543, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXX " + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2544, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XXXXX" + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2545, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXX " + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2546, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " XXXXX" + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2547, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXXXXX" + "XXXXXXXX" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2548, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "XXXXXXXX" + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x2549, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXX " + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x254a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XXXXX" + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x254b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + "XXXXXXXX" + "XXXXXXXX" + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x254c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXX XXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x254d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXX XXX" + "XXX XXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x254e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " " + " " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x254f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " " + " " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, + 0x2571, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X" + " X " + " X " + " X " + " X " + " X " + " X " + "X ") + }, + { + /* Variant */ + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, + 0x2571, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX" + " XXX" + " XXX " + " XXX " + " XXX " + " XXX " + "XXX " + "XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, + 0x2572, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X " + " X " + " X " + " X " + " X " + " X " + " X " + " X") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, + 0x2572, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + "XXX " + " XXX " + " XXX " + " XXX " + " XXX " + " XXX" + " XX") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, + 0x2573, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X X" + " X X " + " X X " + " XX " + " XX " + " X X " + " X X " + "X X") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2574, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2575, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2576, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2577, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2578, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXX " + "XXXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x2579, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x257a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXX" + " XXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, + 0x257b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x257c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXX" + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x257d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " XX " + " XX " + " XX " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x257e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXX " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_BORDER, + 0x257f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " XX " + " XX " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_STIPPLE, + 0x2591, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X X " + " X X " + "X X " + " X X " + "X X " + " X X " + "X X " + " X X ") + }, + { + CHAFA_SYMBOL_TAG_STIPPLE, + 0x2592, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X X X X " + " X X X X" + "X X X X " + " X X X X" + "X X X X " + " X X X X" + "X X X X " + " X X X X") + }, + { + CHAFA_SYMBOL_TAG_STIPPLE, + 0x2593, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX XXX" + "XX XXX X" + " XXX XXX" + "XX XXX X" + " XXX XXX" + "XX XXX X" + " XXX XXX" + "XX XXX X") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb3c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + "X " + "XXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb3d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + "XX " + "XXXXX " + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb3e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "X " + "XX " + "XX " + "XXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb3f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XX " + "XXX " + "XXXXX " + "XXXXXXX " + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb40, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X " + "X " + "XX " + "XX " + "XXX " + "XXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb41, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXXX" + " XXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb42, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX" + " XXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb43, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXXX" + " XXXXX" + " XXXXXX" + " XXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb44, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X" + " XX" + " XXXX" + " XXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb45, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXX" + " XXXXX" + " XXXXX" + " XXXXXX" + " XXXXXX" + " XXXXXXX" + " XXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb46, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXX" + " XXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb47, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " X" + " XXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb48, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " XX" + " XXXXX" + " XXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb49, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X" + " XX" + " XX" + " XXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb4a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X" + " XXX" + " XXXXX" + " XXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb4b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X" + " X" + " XX" + " XX" + " XXX" + " XXX" + " XXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb4c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXXXX " + "XXXXXXX " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb4d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X " + "XXX " + "XXXXXX " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb4e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXXX " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb4f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X " + "XX " + "XXXX " + "XXXXXX " + "XXXXXXX " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb50, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXX " + "XXXXX " + "XXXXX " + "XXXXXX " + "XXXXXX " + "XXXXXXX " + "XXXXXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb51, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXX " + "XXXXX " + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb52, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " XXXXXXX" + " XXXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb53, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " XXXXXX" + " XXX" + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb54, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " XXXXXXX" + " XXXXXX" + " XXXXXX" + " XXXXX" + " XXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb55, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " XXXXXXX" + " XXXXXX" + " XXXX" + " XXX" + " X") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb56, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXXXX" + " XXXXXXX" + " XXXXXX" + " XXXXXX" + " XXXXX" + " XXXXX" + " XXXX" + " XXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb57, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXX " + "X " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb58, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXX " + "XXX " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb59, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXX " + "XXX " + "X " + "X " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb5a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXX " + "XXXXX " + "XXX " + "XX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb5b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + "XXXX " + "XXX " + "XXX " + "XX " + "X " + "X " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb5c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXX " + "XXX " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb5d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXX " + "XXXXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb5e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXX " + "XXX " + "X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb5f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXX " + "XXXXXXX " + "XXXXXX " + "XXXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb60, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXX " + "XXXXXX " + "XXXX " + "XXX " + "X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb61, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXX " + "XXXXXXX " + "XXXXXX " + "XXXXXX " + "XXXXX " + "XXXXX " + "XXXX " + "XXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb62, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XX" + " X" + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb63, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + " XXXXX" + " XX" + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb64, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXX" + " XX" + " XX" + " X" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb65, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXXXX" + " XXXXXX" + " XXXX" + " XXX" + " X" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb66, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX" + " XXXX" + " XXX" + " XXX" + " XX" + " XX" + " X" + " X") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb67, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + " XXXXX" + " XXX" + " " + " " + " ") + }, + + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb68, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + " XXXXXXX" + " XXXXXX" + " XXXXX" + " XXXX" + " XXXXX" + " XXXXXX" + " XXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb69, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X X" + "XX XX" + "XXX XXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb6a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXX " + "XXXXXX " + "XXXXX " + "XXXX " + "XXXXX " + "XXXXXX " + "XXXXXXX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb6b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXXXXXXX" + "XXX XXX" + "XX XX" + "X X" + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb6c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X " + "XX " + "XXX " + "XXXX " + "XXX " + "XX " + "X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb6d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + " XXXXXX " + " XXXX " + " XX " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb6e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X" + " XX" + " XXX" + " XXXX" + " XXX" + " XX" + " X") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb6f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XX " + " XXXX " + " XXXXXX " + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb9a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXX " + " XXXXX " + " XXX " + " X " + " X " + " XXX " + " XXXXX " + " XXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, + 0x1fb9b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X" + "X XX" + "XX XXX" + "XXX XXXX" + "XXXX XXX" + "XXX XX" + "XX X" + "X ") + }, + + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb98, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X X X " + " X X X" + " X X " + "X X X " + " X X X" + " X X " + "X X X " + " X X X") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb99, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X X" + "X X X " + " X X " + " X X X" + "X X X " + " X X " + " X X X" + "X X X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb7c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "X " + "X " + "X " + "X " + "X " + "X " + "X " + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb7d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + "X " + "X " + "X " + "X " + "X " + "X " + "X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb7e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + " X" + " X" + " X" + " X" + " X" + " X" + " X") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb7f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X" + " X" + " X" + " X" + " X" + " X" + " X" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb80, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXXXXXX" + " " + " " + " " + " " + " " + " " + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb70, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb71, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb72, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb73, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb74, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb75, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X " + " X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb76, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXXX" + " " + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb77, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXXXX" + " " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb78, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXXX" + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb79, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXXXXX" + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb7a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + "XXXXXXXX" + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb7b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + "XXXXXXXX" + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb95, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX XX " + "XX XX " + " XX XX" + " XX XX" + "XX XX " + "XX XX " + " XX XX" + " XX XX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb96, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX XX" + " XX XX" + "XX XX " + "XX XX " + " XX XX" + " XX XX" + "XX XX " + "XX XX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fb97, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXXXX" + "XXXXXXXX" + " " + " " + "XXXXXXXX" + "XXXXXXXX") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "X " + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X" + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " X" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "X " + "X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X" + " X" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "X X" + " X X " + " X X " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X X " + "X X" + " " + " " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "X " + " X" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fba9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X" + "X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fbaa, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + " X" + "X X" + " X X " + " X X " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fbab, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " X " + "X " + "X X" + " X X " + " X X " + " XX ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fbac, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X X " + "X X" + " X" + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fbad, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X X " + "X X" + "X " + " X " + " X " + " X ") + }, + { + CHAFA_SYMBOL_TAG_LEGACY, + 0x1fbae, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X X " + "X X" + "X X" + " X X " + " X X " + " XX ") + }, diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols.c chafa-1.12.4/chafa/internal/chafa-symbols.c --- chafa-1.2.1/chafa/internal/chafa-symbols.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,503 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include /* memset */ +#include "chafa.h" +#include "internal/chafa-private.h" + +/* Standard C doesn't require that "s"[0] be considered a compile-time constant. + * Modern compilers support it as an extension, but gcc < 8.1 does not. That's a + * bit too recent, enough to make our tests fail. Therefore we disable it for now. + * + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69960 + * + * Another option is to use binary literals, but that is itself an extension, + * and the symbol outlines would be less legible that way. */ +#undef CHAFA_USE_CONSTANT_STRING_EXPR + +#ifdef CHAFA_USE_CONSTANT_STRING_EXPR + +/* Fancy macros that turn our ASCII symbol outlines into compact bitmaps */ +#define CHAFA_FOLD_BYTE_TO_BIT(x) ((((x) >> 0) | ((x) >> 1) | ((x) >> 2) | ((x) >> 3) | \ + ((x) >> 4) | ((x) >> 5) | ((x) >> 6) | ((x) >> 7)) & 1) +#define CHAFA_OUTLINE_CHAR_TO_BIT(c) ((guint64) CHAFA_FOLD_BYTE_TO_BIT ((c) ^ 0x20)) +#define CHAFA_OUTLINE_8_CHARS_TO_BITS(s, i) \ + ((CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 0]) << 7) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 1]) << 6) | \ + (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 2]) << 5) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 3]) << 4) | \ + (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 4]) << 3) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 5]) << 2) | \ + (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 6]) << 1) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 7]) << 0)) +#define CHAFA_OUTLINE_TO_BITMAP_8X8(s) { \ + ((CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 0) << 56) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 8) << 48) | \ + (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 16) << 40) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 24) << 32) | \ + (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 32) << 24) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 40) << 16) | \ + (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 48) << 8) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 56) << 0)), 0 } +#define CHAFA_SYMBOL_OUTLINE_8X8(x) CHAFA_OUTLINE_TO_BITMAP_8X8(x) + +#else + +#define CHAFA_SYMBOL_OUTLINE_8X8(x) x +#define CHAFA_SYMBOL_OUTLINE_16X8(x) x + +#endif + +typedef struct +{ + gunichar first, last; +} +UnicharRange; + +typedef struct +{ + ChafaSymbolTags sc; + gunichar c; + +#ifdef CHAFA_USE_CONSTANT_STRING_EXPR + /* Each 64-bit integer represents an 8x8 bitmap, scanning left-to-right + * and top-to-bottom, stored in host byte order. + * + * Narrow symbols use bitmap [0], with bitmap [1] set to zero. Wide + * symbols are implemented as two narrow symbols side-by-side, with + * the leftmost in [0] and rightmost in [1]. */ + guint64 bitmap [2]; +#else + const gchar *outline; +#endif +} +ChafaSymbolDef; + +ChafaSymbol *chafa_symbols; +ChafaSymbol2 *chafa_symbols2; +static gboolean symbols_initialized; + +/* Ranges we treat as ambiguous-width in addition to the ones defined by + * GLib. For instance: VTE, although spacing correctly, has many glyphs + * extending well outside their cells resulting in ugly overlapping. */ +static const UnicharRange ambiguous_ranges [] = +{ + { 0x00ad, 0x00ad }, /* Soft hyphen */ + { 0x2196, 0x21ff }, /* Arrows (most) */ + + { 0x222c, 0x2237 }, /* Mathematical ops (some) */ + { 0x2245, 0x2269 }, /* Mathematical ops (some) */ + { 0x226d, 0x2279 }, /* Mathematical ops (some) */ + { 0x2295, 0x22af }, /* Mathematical ops (some) */ + { 0x22bf, 0x22bf }, /* Mathematical ops (some) */ + { 0x22c8, 0x22ff }, /* Mathematical ops (some) */ + + { 0x2300, 0x23ff }, /* Technical */ + { 0x2460, 0x24ff }, /* Enclosed alphanumerics */ + { 0x25a0, 0x25ff }, /* Geometric */ + { 0x2700, 0x27bf }, /* Dingbats */ + { 0x27c0, 0x27e5 }, /* Miscellaneous mathematical symbols A (most) */ + { 0x27f0, 0x27ff }, /* Supplemental arrows A */ + { 0x2900, 0x297f }, /* Supplemental arrows B */ + { 0x2980, 0x29ff }, /* Miscellaneous mathematical symbols B */ + { 0x2b00, 0x2bff }, /* Miscellaneous symbols and arrows */ + { 0x1f100, 0x1f1ff }, /* Enclosed alphanumeric supplement */ + + { 0, 0 } +}; + +/* Emojis of various kinds; usually multicolored. We have no control over + * the foreground colors of these, and they may render poorly for other + * reasons (e.g. too wide). */ +static const UnicharRange emoji_ranges [] = +{ + { 0x2600, 0x26ff }, /* Miscellaneous symbols */ + { 0x1f000, 0x1fb3b }, /* Emojis first part */ + { 0x1fbcb, 0x1ffff }, /* Emojis second part, the gap is legacy computing */ + + /* This symbol usually prints fine, but we don't want it randomly + * popping up in our output anyway. So we add it to the "ugly" category, + * which is excluded from "all". */ + { 0x534d, 0x534d }, + + { 0, 0 } +}; + +static const UnicharRange meta_ranges [] = +{ + /* Arabic tatweel -- RTL but it's a modifier and not formally part + * of a script, so can't simply be excluded on that basis in + * ChafaSymbolMap::char_is_selected() */ + { 0x0640, 0x0640 }, + + /* Ideographic description characters. These convert poorly to our + * internal format. */ + { 0x2ff0, 0x2fff }, + + { 0, 0 } +}; + +static const ChafaSymbolDef symbol_defs [] = +{ +#include "chafa-symbols-ascii.h" +#include "chafa-symbols-latin.h" +#include "chafa-symbols-block.h" +#include "chafa-symbols-kana.h" +#include "chafa-symbols-misc-narrow.h" + { +#ifdef CHAFA_USE_CONSTANT_STRING_EXPR + 0, 0, { 0, 0 } +#else + 0, 0, NULL +#endif + } +}; + +/* ranges must be terminated by zero first, last */ +static gboolean +unichar_is_in_ranges (gunichar c, const UnicharRange *ranges) +{ + for ( ; ranges->first != 0 || ranges->last != 0; ranges++) + { + g_assert (ranges->first <= ranges->last); + + if (c >= ranges->first && c <= ranges->last) + return TRUE; + } + + return FALSE; +} + +static void +calc_weights (ChafaSymbol *sym) +{ + gint i; + + sym->fg_weight = 0; + sym->bg_weight = 0; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + guchar p = sym->coverage [i]; + + sym->fg_weight += p; + sym->bg_weight += 1 - p; + } +} + +static void +outline_to_coverage (const gchar *outline, gchar *coverage_out, gint rowstride) +{ + gchar xlate [256]; + gint x, y; + + xlate [' '] = 0; + xlate ['X'] = 1; + + for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) + { + for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) + { + guchar p = (guchar) outline [y * rowstride + x]; + coverage_out [y * CHAFA_SYMBOL_WIDTH_PIXELS + x] = xlate [p]; + } + } +} + +static guint64 +coverage_to_bitmap (const gchar *cov, gint rowstride) +{ + guint64 bitmap = 0; + gint x, y; + + for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) + { + for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) + { + bitmap <<= 1; + if (cov [y * rowstride + x]) + bitmap |= 1; + } + } + + return bitmap; +} + +static void +bitmap_to_coverage (guint64 bitmap, gchar *cov_out) +{ + gint i; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + cov_out [i] = (bitmap >> (63 - i)) & 1; + } +} + +static void +gen_braille_sym (gchar *cov, guint8 val) +{ + memset (cov, 0, CHAFA_SYMBOL_N_PIXELS); + + cov [1] = cov [2] = (val & 1); + cov [5] = cov [6] = ((val >> 3) & 1); + cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; + + cov [1] = cov [2] = ((val >> 1) & 1); + cov [5] = cov [6] = ((val >> 4) & 1); + cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; + + cov [1] = cov [2] = ((val >> 2) & 1); + cov [5] = cov [6] = ((val >> 5) & 1); + cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; + + cov [1] = cov [2] = ((val >> 6) & 1); + cov [5] = cov [6] = ((val >> 7) & 1); +} + +static int +generate_braille_syms (ChafaSymbol *syms, gint first_ofs) +{ + gunichar c; + gint i = first_ofs; + + /* Braille 2x4 range */ + + c = 0x2800; + + for (i = first_ofs; c < 0x2900; c++, i++) + { + ChafaSymbol *sym = &syms [i]; + + sym->sc = CHAFA_SYMBOL_TAG_BRAILLE; + sym->c = c; + sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); + + gen_braille_sym (sym->coverage, c - 0x2800); + calc_weights (&syms [i]); + syms [i].bitmap = coverage_to_bitmap (syms [i].coverage, CHAFA_SYMBOL_WIDTH_PIXELS); + syms [i].popcount = chafa_population_count_u64 (syms [i].bitmap); + } + return i; +} + +static void +gen_sextant_sym (gchar *cov, guint8 val) +{ + gint x, y; + + memset (cov, 0, CHAFA_SYMBOL_N_PIXELS); + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 2; x++) + { + gint bit = y * 2 + x; + + if (val & (1 << bit)) + { + gint u, v; + + for (v = 0; v < 3; v++) + { + for (u = 0; u < 4; u++) + { + gint row = y * 3 + v; + if (row > 3) + row--; + + cov [(row * 8) + x * 4 + u] = 1; + } + } + } + } + } +} + +static int +generate_sextant_syms (ChafaSymbol *syms, gint first_ofs) +{ + gunichar c; + gint i = first_ofs; + + /* Teletext sextant/2x3 mosaic range */ + + c = 0x1fb00; + + for (i = first_ofs; c < 0x1fb3b; c++, i++) + { + ChafaSymbol *sym = &syms [i]; + gint bitmap; + + sym->sc = CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_SEXTANT; + sym->c = c; + sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); + + bitmap = c - 0x1fb00 + 1; + if (bitmap > 20) bitmap++; + if (bitmap > 41) bitmap++; + + gen_sextant_sym (sym->coverage, bitmap); + calc_weights (&syms [i]); + syms [i].bitmap = coverage_to_bitmap (syms [i].coverage, CHAFA_SYMBOL_WIDTH_PIXELS); + syms [i].popcount = chafa_population_count_u64 (syms [i].bitmap); + } + + return i; +} + +static ChafaSymbolTags +get_default_tags_for_char (gunichar c) +{ + ChafaSymbolTags tags = CHAFA_SYMBOL_TAG_NONE; + + if (g_unichar_iswide (c)) + tags |= CHAFA_SYMBOL_TAG_WIDE; + else if (g_unichar_iswide_cjk (c)) + tags |= CHAFA_SYMBOL_TAG_AMBIGUOUS; + + if (g_unichar_ismark (c) + || g_unichar_iszerowidth (c) + || unichar_is_in_ranges (c, ambiguous_ranges)) + tags |= CHAFA_SYMBOL_TAG_AMBIGUOUS; + + if (unichar_is_in_ranges (c, emoji_ranges) + || unichar_is_in_ranges (c, meta_ranges)) + tags |= CHAFA_SYMBOL_TAG_UGLY; + + if (c <= 0x7f) + tags |= CHAFA_SYMBOL_TAG_ASCII; + else if (c >= 0x2300 && c <= 0x23ff) + tags |= CHAFA_SYMBOL_TAG_TECHNICAL; + else if (c >= 0x25a0 && c <= 0x25ff) + tags |= CHAFA_SYMBOL_TAG_GEOMETRIC; + else if (c >= 0x2800 && c <= 0x28ff) + tags |= CHAFA_SYMBOL_TAG_BRAILLE; + else if (c >= 0x1fb00 && c <= 0x1fb3b) + tags |= CHAFA_SYMBOL_TAG_SEXTANT; + + if (g_unichar_isalpha (c)) + tags |= CHAFA_SYMBOL_TAG_ALPHA; + if (g_unichar_isdigit (c)) + tags |= CHAFA_SYMBOL_TAG_DIGIT; + + if (!(tags & CHAFA_SYMBOL_TAG_WIDE)) + tags |= CHAFA_SYMBOL_TAG_NARROW; + + return tags; +} + +static void +def_to_symbol (const ChafaSymbolDef *def, ChafaSymbol *sym, gint x_ofs, gint rowstride) +{ + sym->c = def->c; + + /* FIXME: g_unichar_iswide_cjk() will erroneously mark many of our + * builtin symbols as ambiguous. Find a better way to deal with it. */ + sym->sc = def->sc | (get_default_tags_for_char (def->c) & ~CHAFA_SYMBOL_TAG_AMBIGUOUS); + + sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); + outline_to_coverage (def->outline + x_ofs, sym->coverage, rowstride); + + sym->bitmap = coverage_to_bitmap (sym->coverage, CHAFA_SYMBOL_WIDTH_PIXELS); + sym->popcount = chafa_population_count_u64 (sym->bitmap); + + calc_weights (sym); +} + +static ChafaSymbol * +init_symbol_array (const ChafaSymbolDef *defs) +{ + ChafaSymbol *syms; + gint i, j; + + syms = g_new0 (ChafaSymbol, CHAFA_N_SYMBOLS_MAX); + + for (i = 0, j = 0; defs [i].c; i++) + { + gint outline_len; + + outline_len = strlen (defs [i].outline); + g_assert (outline_len == CHAFA_SYMBOL_N_PIXELS + || outline_len == CHAFA_SYMBOL_N_PIXELS * 2); + + if (outline_len != CHAFA_SYMBOL_N_PIXELS + || g_unichar_iswide (defs [i].c)) + continue; + + def_to_symbol (&defs [i], &syms [j], 0, CHAFA_SYMBOL_WIDTH_PIXELS); + j++; + } + + j = generate_braille_syms (syms, j); + generate_sextant_syms (syms, j); + return syms; +} + +static ChafaSymbol2 * +init_symbol_array_wide (const ChafaSymbolDef *defs) +{ + ChafaSymbol2 *syms; + gint i, j; + + syms = g_new0 (ChafaSymbol2, CHAFA_N_SYMBOLS_MAX); + + for (i = 0, j = 0; defs [i].c; i++) + { + gint outline_len; + + outline_len = strlen (defs [i].outline); + g_assert (outline_len == CHAFA_SYMBOL_N_PIXELS + || outline_len == CHAFA_SYMBOL_N_PIXELS * 2); + + if (outline_len != CHAFA_SYMBOL_N_PIXELS * 2 + || !g_unichar_iswide (defs [i].c)) + continue; + + def_to_symbol (&defs [i], &syms [j].sym [0], + 0, CHAFA_SYMBOL_WIDTH_PIXELS * 2); + def_to_symbol (&defs [i], &syms [j].sym [1], + CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 2); + j++; + } + + return syms; +} + +void +chafa_init_symbols (void) +{ + if (symbols_initialized) + return; + + chafa_symbols = init_symbol_array (symbol_defs); + chafa_symbols2 = init_symbol_array_wide (symbol_defs); + + symbols_initialized = TRUE; +} + +ChafaSymbolTags +chafa_get_tags_for_char (gunichar c) +{ + gint i; + + for (i = 0; symbol_defs [i].c; i++) + { + const ChafaSymbolDef *def = &symbol_defs [i]; + + if (def->c == c) + return def->sc | (get_default_tags_for_char (def->c) & ~CHAFA_SYMBOL_TAG_AMBIGUOUS); + } + + return get_default_tags_for_char (c); +} diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols-kana.h chafa-1.12.4/chafa/internal/chafa-symbols-kana.h --- chafa-1.2.1/chafa/internal/chafa-symbols-kana.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols-kana.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,2564 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* Japanese hiragana and katakana + * ------------------------------ + * + * This is meant to be #included in the symbol definition table of + * chafa-symbols.c. It's kept in a separate file due to its size. */ + +/* Hiragana */ + + { + /* [ぁ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3041, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXXXXXXXX " + " X X " + " XXXXX XX " + " XX X X XX " + " X XX X " + " XX XXX ") + }, + { + /* [あ] freq=374 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3042, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XXXXXXXXXXX " + " X XX " + " XXXXXXXX " + " XX X X X " + " X X X X " + " X XX X " + " XXX XXXX ") + }, + { + /* [ぃ] freq=73 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3043, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " X X " + " X X " + " X X " + " X X X " + " XX ") + }, + { + /* [い] freq=12 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3044, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " X XX " + " X XX " + " XX X " + " X X XX " + " XX X " + " XXX ") + }, + { + /* [ぅ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3045, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXX " + " " + " XXXX " + " XX XX " + " X " + " XX " + " XXXX ") + }, + { + /* [う] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3046, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXX " + " XXX " + " XXXX XXX " + " X " + " X " + " XX " + " XXXX ") + }, + { + /* [ぇ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3047, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXXXX " + " " + " XXXXXXX " + " XX " + " XX XX " + " XX XXXX ") + }, + { + /* [え] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3048, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXX " + " " + " XXXXXXXX " + " XX " + " XXXXX " + " XX X " + " X XXXX ") + }, + { + /* [ぉ] freq=11 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3049, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XXXXXX XXX " + " X " + " XXXXXXXXX " + " X X X " + " XXXX XXXX ") + }, + { + /* [お] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x304a, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " X X " + " XXXXXXX XXX " + " X XX " + " XXXXX XXX " + " XX X X " + " X X X " + " XXX XXXX ") + }, + { + /* [か] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x304b, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XX X " + " XXXXXXXXX XX " + " X X XX " + " X X X " + " XX XX " + " XX X " + " X XXX ") + }, + { + /* [が] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x304c, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X X " + " X X " + " XXXXXXXXX X " + " XX XX X " + " X XX X " + " X X " + " XX X " + " X XXXX ") + }, + { + /* [き] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x304d, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XX XXXXX " + " XXX X " + " XXXXXXXXXX " + " XX " + " X XXX " + " X " + " XXXXXXXX ") + }, + { + /* [ぎ] freq=48 */ + CHAFA_SYMBOL_TAG_NONE, + 0x304e, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X XX " + " XX XXXXXX " + " XXX X " + " XXX XXXXXXX " + " X X " + " X XXXX " + " XX " + " XXXXXXXX ") + }, + { + /* [く] freq=20 */ + CHAFA_SYMBOL_TAG_NONE, + 0x304f, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XX " + " XX " + " XX " + " XX " + " XXX " + " X ") + }, + { + /* [ぐ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3050, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XX " + " XXX X X " + " XX " + " XX " + " XX " + " XX ") + }, + { + /* [け] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3051, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XX X " + " X XXXXXXXXX " + " X X " + " X X " + " XX X " + " XX X " + " XX ") + }, + { + /* [げ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3052, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " XX X X " + " X XXXXXXXXX " + " X X " + " X X " + " XX XX " + " XX X " + " X XXX ") + }, + { + /* [こ] freq=6 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3053, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXX " + " " + " " + " " + " X " + " XX " + " XXXXXXXX ") + }, + { + /* [ご] freq=7 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3054, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " XXXXXXXXX X " + " " + " " + " " + " X " + " X " + " XXXXXXXXX ") + }, + { + /* [さ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3055, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X " + " XXXXXXXXXX " + " X " + " XX " + " X XX " + " XX " + " XXXXXXX ") + }, + { + /* [ざ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3056, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X X " + " XX " + " XXXXXXXXXXX " + " X " + " X X " + " X XX " + " X " + " XXXXXXXX ") + }, + { + /* [し] freq=21 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3057, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " X " + " X " + " X " + " X X " + " X XX " + " XXXXXXX ") + }, + { + /* [じ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3058, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X XX " + " X X " + " X " + " X " + " X " + " X XX " + " XXXXXXXX ") + }, + { + /* [す] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3059, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXXXXXX " + " XX X " + " XXXX " + " X X " + " XXXXX " + " XX " + " XXX ") + }, + { + /* [ず] freq=10 */ + CHAFA_SYMBOL_TAG_NONE, + 0x305a, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X X " + " XXXXXXXXXX " + " XX X " + " XXXX " + " XX X " + " XXXXX " + " XX " + " XXX ") + }, + { + /* [せ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x305b, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X " + " X XXXXX " + " XXXXXXXXX X " + " X XX " + " X XX " + " XX " + " XXXXXXX ") + }, + { + /* [ぜ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x305c, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X X " + " X XXXX " + " XXXXXXXXXX " + " X X " + " X XXX " + " X " + " XXXXXXXX ") + }, + { + /* [そ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x305d, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXXX " + " XXX XX " + " XX " + " XX XXX " + " XXX XXXX " + " X " + " X " + " XXXXX ") + }, + { + /* [ぞ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x305e, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXX " + " XXXXXX X " + " X XX " + " XX XXXX " + " XXXX XXXX " + " X " + " XX " + " XXXX ") + }, + { + /* [た] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x305f, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXXX " + " X " + " X XXXXX " + " X " + " XX X " + " X X " + " XX XXXXX ") + }, + { + /* [だ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3060, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X " + " X X X " + " XXXXXXXX " + " XX XXXXXX " + " X " + " X X " + " XX X " + " XX XXXXXX ") + }, + { + /* [ち] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3061, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XXXXXXXXXXX " + " X " + " X X " + " XXXX XXX " + " X XX " + " XX " + " XXXXXX ") + }, + { + /* [ぢ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3062, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X " + " X X " + " XXXXXXXXXX " + " X " + " XXXXXXXXX " + " X X " + " XX " + " XXXXXX ") + }, + { + /* [っ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3063, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " XXXXXXX " + " XX XX " + " X " + " XX " + " XXXX ") + }, + { + /* [つ] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3064, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXX " + " XXXXXXX XXX " + " XX " + " X " + " XX " + " XXXXXX " + " ") + }, + { + /* [づ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3065, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X " + " XXXXXXXX " + " XXXX X " + " X " + " XX " + " XX " + " XXXXX ") + }, + { + /* [て] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3066, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " X " + " XX " + " X " + " XX " + " XX " + " XXX ") + }, + { + /* [で] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3067, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " XX X " + " XX XX X " + " X " + " XX " + " XX " + " XXX ") + }, + { + /* [と] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3068, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " X XXXX " + " XXX " + " XX " + " XX " + " XX " + " XXXXXXXX ") + }, + { + /* [ど] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3069, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X XX " + " X XXX " + " XXXX " + " X " + " X " + " XX " + " XXXXXXXX ") + }, + { + /* [な] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x306a, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XX " + " XXXX XX XXX " + " XX X " + " XX X " + " XX X " + " XXXXXXXXX " + " XXXXX ") + }, + { + /* [に] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x306b, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X XXXXXXX " + " XX " + " X " + " X " + " X X X " + " XX X " + " XX XXXXXX ") + }, + { + /* [ぬ] freq=26 */ + CHAFA_SYMBOL_TAG_NONE, + 0x306c, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X XX " + " XXXXXX XXX " + " XXX X X " + " X XX X X " + " X XX XXXXXX " + " XXX X XXX " + " XXX ") + }, + { + /* [ね] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x306d, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X " + " XXXXXXXXX XX " + " XX X " + " XX X " + " X X XXXXXX " + " X X X X X " + " XX XXX ") + }, + { + /* [の] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x306e, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXX " + " XX XX XX " + " X X X " + " XX XX XX " + " X X X " + " XXXX XX " + " XXX ") + }, + { + /* [は] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x306f, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X " + " X XXXXXXXX " + " X X " + " X X " + " XX XXXXXX " + " XX X X XX " + " XX XXXX ") + }, + { + /* [ば] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3070, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X X " + " X XXXXXXXX " + " X X " + " XX XX " + " XXX XXXXXXX " + " X X XX XX " + " X XXX ") + }, + { + /* [ぱ] freq=51 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3071, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXXX" + " X X XX " + " X XXXXXXXX " + " X X " + " XX XX " + " XXX XXXXXXX " + " X X XX XX " + " X XXX ") + }, + { + /* [ひ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3072, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXX X " + " XX XX " + " XX XX " + " X X XX " + " XX X " + " X X " + " XXXXXX ") + }, + { + /* [び] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3073, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " XXXXXXX XX X " + " X X " + " X XX " + " XX X X " + " X XX " + " XX X " + " XXXXXX ") + }, + { + /* [ぴ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3074, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXX " + " XXXXXXX XXX X " + " X X X " + " X XX " + " XX X X " + " X X " + " XX X " + " XXXXXX ") + }, + { + /* [ふ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3075, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXX " + " XX " + " X " + " XX X " + " X X X " + " XXX XX X " + " XXXX ") + }, + { + /* [ぶ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3076, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXX XX " + " XX X X " + " X " + " XX XX " + " XX XX X " + " XX X XX " + " XXXX ") + }, + { + /* [ぷ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3077, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXX XXX " + " XX X X " + " X X " + " XX XX " + " XX XX X " + " XX X XX " + " XXXX ") + }, + { + /* [へ] freq=25 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3078, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXX " + " X XX " + " XX XX " + " X XX " + " XX " + " ") + }, + { + /* [べ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3079, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X " + " XXX X " + " XX XX " + " XX X " + " X XX " + " XX " + " ") + }, + { + /* [ぺ] freq=6 */ + CHAFA_SYMBOL_TAG_NONE, + 0x307a, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXX " + " XXX X X " + " XX XX XX " + " XX X " + " X XX " + " XX " + " ") + }, + { + /* [ほ] freq=9 */ + CHAFA_SYMBOL_TAG_NONE, + 0x307b, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX XXXXXXX " + " X XX " + " X XXXXXXX " + " X X " + " XX XXXXXX " + " XX X X XX " + " XX XXXX ") + }, + { + /* [ぼ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x307c, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X XXXXXX X " + " X X " + " X XXXXXXXX " + " XX X " + " XXX X " + " X XXXXXXXX " + " X XXX ") + }, + { + /* [ぽ] freq=181 */ + CHAFA_SYMBOL_TAG_NONE, + 0x307d, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXXX" + " X XXXXXX XX " + " X X " + " X XXXXXXXX " + " XX X " + " XXX X " + " X XXXXXXXX " + " X XXX ") + }, + { + /* [ま] freq=7 */ + CHAFA_SYMBOL_TAG_NONE, + 0x307e, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXXXXX " + " X " + " XXXXXXXXXXX " + " X " + " XXXXXXX " + " X X XX " + " XXXXXX ") + }, + { + /* [み] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x307f, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXX " + " XX " + " XX X " + " XXXXX XXXXX " + " X X XXXX " + " XXXX XX " + " XX ") + }, + { + /* [む] freq=8 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3080, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXXX XX " + " X X " + " XXXX " + " X X " + " XXXX X " + " X X " + " XXXXXXX ") + }, + { + /* [め] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3081, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X " + " XXXXXXXX " + " XX X XX " + " X X XX X " + " XX XX X " + " XXXX XX " + " XX ") + }, + { + /* [も] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3082, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " XXXXXXXX " + " X " + " XXXXXXXX XX " + " X X " + " X X " + " XXXXXX ") + }, + { + /* [ゃ] freq=6 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3083, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XX " + " X XXXXXX " + " XXXX X " + " X X XXXXX " + " XX " + " X ") + }, + { + /* [や] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3084, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X XX " + " XXXXXX XX " + " XXX X XX " + " X XXXXX " + " XX " + " X " + " XX ") + }, + { + /* [ゅ] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3085, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X XX " + " X XX XXXX " + " X X X XX " + " XX X X XX " + " XX XXXXX " + " XX ") + }, + { + /* [ゆ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3086, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XX X " + " X XXXXX XXX " + " X X X X " + " XX X X " + " XX X XX XX " + " XXXXX " + " XX ") + }, + { + /* [ょ] freq=14 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3087, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XXXXX " + " XX " + " XX " + " XXXXXXXXX " + " XXXXX ") + }, + { + /* [よ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3088, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XX " + " XXXXXX " + " XX " + " XX " + " XXXXXXX " + " X XX XXXX " + " XXXX ") + }, + { + /* [ら] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3089, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXX " + " X " + " XX XXX " + " XXXXX XX " + " X XX " + " XX " + " XXXXXX ") + }, + { + /* [り] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x308a, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X XXXX " + " XXX X " + " XX XX " + " XX XX " + " X " + " XX " + " XXXX ") + }, + { + /* [る] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x308b, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXX " + " XX " + " XX " + " XXX XXXXXX " + " X XX " + " XXXXX X " + " XXXXXX ") + }, + { + /* [れ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x308c, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X " + " XXXXXXXXX XX " + " XX XX " + " XX X " + " X X X " + " X X X " + " X XXXX ") + }, + { + /* [ろ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x308d, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXX " + " XX " + " XX X " + " XXX XX XXX " + " X XX " + " XX " + " XXXXXX ") + }, + { + /* [ゎ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x308e, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XX " + " XXX XXXXXX " + " XX X " + " XXX X " + " X X XX " + " X XX ") + }, + { + /* [わ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x308f, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " XXXXX XXXXXX " + " XXX XX " + " XX X " + " X X X " + " X X XXX " + " XX X ") + }, + { + /* [ゐ] freq=14 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3090, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XXX X " + " XXX " + " XXXX XXX " + " XX X X " + " X XX X " + " XX XX XXXXXXX " + " XXX XXX ") + }, + { + /* [ゑ] freq=17 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3091, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXXXXXX " + " XX " + " XXX XXX " + " X X XX X " + " XXXXXXX " + " XX " + " XX X XXXX " + " XX XX XX ") + }, + { + /* [を] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3092, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXXXXX " + " X " + " XXXXXX XXXX " + " X XXX " + " XX X " + " X " + " XXXXXXX ") + }, + { + /* [ん] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3093, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XX " + " XX " + " XX X " + " X X X " + " X XX X " + " X XX X " + " X XXXX ") + }, + { + /* [ゔ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3094, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXX XX " + " XXXX X " + " XXXX XX " + " X " + " XX " + " XXX " + " XXX ") + }, + { + /* [ゕ] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3095, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " X XXXX XX " + " XXX XX X " + " X XX X " + " X X " + " XX XXXX ") + }, + { + /* [ゖ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x3096, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X XX " + " X XXXXXXX " + " X XX " + " X X XX " + " XX X " + " X XX ") + }, + +/* Katakana */ + + { + /* [゠] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a0, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " XXXX " + " XXXX " + " " + " " + " ") + }, + { + /* [ァ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a1, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXXXXXXXXX " + " XX " + " X X " + " X " + " XX " + " XX ") + }, + { + /* [ア] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a2, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXXX " + " X X " + " X XX " + " X " + " XX " + " XX " + " XX ") + }, + { + /* [ィ] freq=7 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a3, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XX " + " XXX " + " XXXX X " + " X " + " X ") + }, + { + /* [イ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a4, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XXX " + " XXX " + " XXXX X " + " X " + " X " + " XX ") + }, + { + /* [ゥ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a5, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XX " + " XXXXXXXXXX " + " X XX " + " X " + " XX " + " XXXX ") + }, + { + /* [ウ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a6, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XX " + " XXXXXXXXXXXX " + " XX XX " + " XX X " + " XX " + " XXX " + " XX ") + }, + { + /* [ェ] freq=20 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a7, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " XXXXXXXXXX " + " XX " + " XX " + " XX " + " XXXXXXXXXXXX ") + }, + { + /* [エ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a8, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " XX " + " XX " + " XX " + " XX " + " XXXXXXXXXXXXXX " + " ") + }, + { + /* [ォ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30a9, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XXXXXXXXXXX " + " XX " + " XX X " + " XX X " + " X XXX ") + }, + { + /* [オ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30aa, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " X " + " XXXXXXXXXXXX " + " XXX " + " XX XX " + " XXX XX " + " X XX " + " XXX ") + }, + { + /* [カ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ab, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " XXXXXXXXXXXX " + " XX XX " + " X X " + " X X " + " X X " + " XX XXXX ") + }, + { + /* [ガ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ac, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X X " + " XX X X " + " XXXXXXXXXXX " + " X X " + " XX X " + " X X " + " XX XX " + " X XXXX ") + }, + { + /* [キ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ad, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XXXXXX " + " XXXXXX " + " XX XXXX " + " XXXXX XX " + " XX " + " X ") + }, + { + /* [ギ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ae, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X X " + " XXXXXXXXXXX " + " X " + " XXXXXXXXX " + " XXX X " + " X " + " ") + }, + { + /* [ク] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30af, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXX " + " XX X " + " XX XX " + " XX " + " X " + " XXX " + " XX ") + }, + { + /* [グ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b0, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X X " + " XXXXXXXX " + " XX X " + " XX XX " + " X " + " XXX " + " XXX ") + }, + { + /* [ケ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b1, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " X " + " XXXXXXXXXXX " + " XX X " + " XX " + " X " + " XX " + " X ") + }, + { + /* [ゲ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b2, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XX X X " + " XXXXXXXXXX " + " X X " + " XX XX " + " X " + " XX " + " XXX ") + }, + { + /* [コ] freq=6 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b3, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXX " + " XX " + " XX " + " XX " + " XX " + " XXXXXXXXXXX " + " XX ") + }, + { + /* [ゴ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b4, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X XX " + " XXXXXXXXXXX " + " X " + " X " + " X " + " X " + " XXXXXXXXXXX " + " X ") + }, + { + /* [サ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b5, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XX XX " + " XXXXXXXXXXXXXX " + " XX XX " + " XX X " + " X " + " XX " + " XX ") + }, + { + /* [ザ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b6, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X X XXX " + " XXXXXXXXXXXXX " + " X X " + " X X " + " XX " + " XX " + " XX ") + }, + { + /* [シ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b7, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XX " + " XXX XX " + " XX " + " XX " + " XXXXXX " + " ") + }, + { + /* [ジ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b8, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X X " + " XXX X X " + " XX X " + " X X " + " XXX " + " XXX " + " XXX ") + }, + { + /* [ス] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30b9, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX " + " XX " + " XX " + " XX " + " XX XX " + " XXX X " + " X X ") + }, + { + /* [ズ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ba, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X " + " XXXXXXXXXX " + " XX " + " XX " + " XX " + " XX XX " + " XX XX " + " XX XX ") + }, + { + /* [セ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30bb, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " X XXXXXX " + " XXXXXXX XX " + " X XX " + " X " + " X " + " XXXXXXX ") + }, + { + /* [ゼ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30bc, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X " + " X XXXXX " + " XXXXXXXX XX " + " X XX " + " X " + " X " + " XXXXXXX ") + }, + { + /* [ソ] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30bd, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX X " + " XX XX " + " X X " + " X " + " XX " + " XXX " + " ") + }, + { + /* [ゾ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30be, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X X " + " X XX " + " XX X " + " X " + " XX " + " XXX " + " ") + }, + { + /* [タ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30bf, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " XXXXXXX " + " X X " + " XX X XX " + " X XXXXX " + " XXXX " + " XX " + " XXX ") + }, + { + /* [ダ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c0, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X X " + " XXXXXXXX " + " XX XX " + " XX X X " + " XX X " + " XXXX " + " XX " + " XXX ") + }, + { + /* [チ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c1, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXX " + " X " + " XXXXXXXXXXXXXX " + " XX " + " X " + " X " + " XX ") + }, + { + /* [ヂ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c2, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX X " + " X X X " + " XXXXXXXXXXXXX " + " X " + " X " + " XX " + " XX ") + }, + { + /* [ッ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c3, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " X X X " + " X X XX " + " XX " + " XX " + " XXX ") + }, + { + /* [ツ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c4, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX X " + " XX X XX " + " X X " + " X " + " XX " + " XXXX " + " ") + }, + { + /* [ヅ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c5, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X " + " X X XX " + " X XX " + " XX " + " XX " + " XXX " + " ") + }, + { + /* [テ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c6, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX " + " " + " XXXXXXXXXXXXX " + " X " + " XX " + " XX " + " XX ") + }, + { + /* [デ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c7, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " XXXXXXXXX " + " " + " XXXXXXXXXXXXX " + " XX " + " X " + " XX " + " XX ") + }, + { + /* [ト] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c8, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XX " + " XXXX " + " XX XXXXX " + " XX " + " XX " + " ") + }, + { + /* [ド] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30c9, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X X " + " X X " + " XXX " + " X XXXXX " + " X " + " X " + " X ") + }, + { + /* [ナ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ca, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " XXXXXXXXXXXXX " + " X " + " XX " + " X " + " XX " + " X ") + }, + { + /* [ニ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30cb, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX " + " " + " " + " " + " " + " XXXXXXXXXXXXX " + " ") + }, + { + /* [ヌ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30cc, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX " + " X " + " XXX X " + " XXX " + " XXXX " + " XX XX " + " XX ") + }, + { + /* [ネ] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30cd, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX " + " XXXXXXXXXXX " + " XX " + " XX " + " XXXX XX " + " XXXX XX XXX " + " XX " + " XX ") + }, + { + /* [ノ] freq=6 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ce, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XX " + " XX " + " XX " + " XX " + " XXXX " + " ") + }, + { + /* [ハ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30cf, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X " + " X XX " + " XX X " + " X X " + " X XX " + " X XX " + " ") + }, + { + /* [バ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d0, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X " + " X X " + " XX X " + " X X " + " X XX " + " XX X " + " ") + }, + { + /* [パ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d1, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXX " + " X XX X X " + " X XX X " + " XX X " + " X X " + " X XX " + " X X " + " ") + }, + { + /* [ヒ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d2, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X " + " X XXX " + " XXXXXX " + " X " + " X " + " X " + " XXXXXXXX ") + }, + { + /* [ビ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d3, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X " + " X XX " + " XXXXXXX " + " X " + " X " + " X " + " XXXXXXXXX ") + }, + { + /* [ピ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d4, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXX " + " X X X " + " X XXX X " + " XXXXXX " + " X " + " X " + " X " + " XXXXXXXXX ") + }, + { + /* [フ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d5, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " X " + " XX " + " XX " + " XX " + " XXX " + " XX ") + }, + { + /* [ブ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d6, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " XXXXXXXXXXX " + " X " + " XX " + " XX " + " XX " + " XXX " + " XX ") + }, + { + /* [プ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d7, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXXX" + " XXXXXXXXXXX X" + " XXX " + " XX " + " XX " + " XX " + " XXX " + " XX ") + }, + { + /* [ヘ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d8, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXX " + " X XX " + " XX XX " + " X XX " + " XX " + " ") + }, + { + /* [ベ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30d9, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X " + " XXX X " + " XX XX " + " XX XX " + " X XX " + " XX " + " ") + }, + { + /* [ペ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30da, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXX " + " XXX X X " + " XX XX X " + " XX XX " + " X XX " + " XX " + " ") + }, + { + /* [ホ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30db, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X " + " X " + " XXXXXXXXXXXX " + " X " + " X X X " + " XX X X " + " X " + " XXX ") + }, + { + /* [ボ] freq=14 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30dc, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XX X X " + " XX X X " + " XXXXXXXXXXXXX " + " XX " + " XX XX XX " + " X XX X " + " X XX " + " XXX ") + }, + { + /* [ポ] freq=32 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30dd, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " XXXX " + " XX XX " + " XXXXXXXXXXXXX " + " XX " + " XX XX XX " + " XX XX XX " + " XX " + " XXX ") + }, + { + /* [マ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30de, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " XX " + " XX " + " XX XX " + " XXX " + " XX " + " X ") + }, + { + /* [ミ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30df, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXX " + " XXX " + " XXXX " + " XXXX " + " X " + " XXXXXXXX " + " ") + }, + { + /* [ム] freq=8 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e0, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " X " + " XX " + " X X " + " X X " + " XXXXXXXXXXX X " + " ") + }, + { + /* [メ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e1, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " X XX " + " XXX XX " + " XXX " + " XX XX " + " XXX " + " ") + }, + { + /* [モ] freq=9 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e2, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX " + " XX " + " XXXXXXXXXXXXX " + " XX " + " XX " + " XX " + " XXXXXX ") + }, + { + /* [ャ] freq=10 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e3, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XX XXX " + " XXXXXXXXXX " + " X XX " + " X " + " X ") + }, + { + /* [ヤ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e4, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " X XXXXX " + " XXXXXXXX X " + " X XX " + " XX " + " X " + " XX ") + }, + { + /* [ュ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e5, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " XXXXXXXX " + " X " + " X " + " XXXXXXXXXXXX " + " ") + }, + { + /* [ユ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e6, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXX " + " X " + " X " + " XX " + " XX " + " XXXXXXXXXXXXXX " + " ") + }, + { + /* [ョ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e7, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXXXXXXX " + " X " + " XXXXXXXX " + " X " + " X " + " XXXXXXXXX ") + }, + { + /* [ヨ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e8, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXX " + " X " + " X " + " XXXXXXXXXX " + " X " + " XXXXXXXXXXX " + " X ") + }, + { + /* [ラ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30e9, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXX " + " " + " XXXXXXXXXXXX " + " X " + " X " + " XXX " + " XXX ") + }, + { + /* [リ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ea, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X " + " X X " + " X X " + " X X " + " XX " + " XXX " + " ") + }, + { + /* [ル] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30eb, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX XX " + " XX XX " + " X XX " + " X XX X " + " XX XX XX " + " XX XXXX " + " ") + }, + { + /* [レ] freq=6 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ec, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X " + " X " + " X " + " X XX " + " X XXX " + " XXXXX " + " ") + }, + { + /* [ロ] freq=653 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ed, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " XX XX " + " XX XX " + " XX XX " + " XX XX " + " XXXXXXXXXXXX " + " XX XX ") + }, + { + /* [ヮ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ee, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XXXXXXXXXX " + " X X " + " X X " + " XX " + " XX " + " XXXX ") + }, + { + /* [ワ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30ef, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " XX XX " + " XX X " + " XX " + " XX " + " XXX " + " X ") + }, + { + /* [ヰ] freq=2 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f0, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X " + " XXXXXXXXXXXX " + " X X " + " X X " + " XXXXXXXXXXXXXX " + " X " + " X ") + }, + { + /* [ヱ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f1, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXXX " + " X " + " X XX " + " X " + " X " + " XXXXXXXXXXXXXX " + " ") + }, + { + /* [ヲ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f2, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XXXXXXXXXXX " + " XX " + " XXXXXXXXXX " + " X " + " X " + " XXX " + " XX ") + }, + { + /* [ン] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f3, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " XX " + " XX " + " XX " + " X " + " XXX " + " XXXXXX " + " ") + }, + { + /* [ヴ] freq=0 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f4, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X XX " + " X X " + " XXXXXXXXXXXX " + " X X " + " X XX " + " XX " + " XX " + " XXX ") + }, + { + /* [ヵ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f5, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XXXXXXXXX " + " XX XX " + " X XX " + " XX X " + " XX XXXX ") + }, + { + /* [ヶ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f6, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " XX " + " XXXXXXXXX " + " XX X " + " XX " + " XX " + " XXX ") + }, + { + /* [ヷ] freq=3 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f7, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X XX " + " XXXXXXXXXXX " + " X X " + " X X " + " X " + " XX " + " XX " + " XXX ") + }, + { + /* [ヸ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f8, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X X " + " X X " + " XXXXXXXXXXXX " + " X X " + " X X " + " XXXXXXXXXXXXXX " + " X " + " X ") + }, + { + /* [ヹ] freq=1 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30f9, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X XX " + " XXXXXXXXXXXX " + " X " + " X XX " + " X " + " X " + " XXXXXXXXXXXXXX " + " ") + }, + { + /* [ヺ] freq=4 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30fa, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " X XX " + " XXXXXXXXXXX " + " X " + " XXXXXXXXXXX " + " XX " + " XX " + " XX " + " XXX ") + }, + { + /* [・] freq=99 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30fb, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " XXXX " + " XX " + " " + " " + " ") + }, + { + /* [ー] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30fc, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " " + " XXXXXXXXXXXX " + " " + " " + " " + " ") + }, + { + /* [ヽ] freq=19 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30fd, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " " + " X " + " XX " + " XX " + " XX " + " " + " ") + }, + { + /* [ヾ] freq=5 */ + CHAFA_SYMBOL_TAG_NONE, + 0x30fe, + CHAFA_SYMBOL_OUTLINE_16X8 ( + " " + " X X " + " X X " + " XX " + " XX " + " X " + " " + " ") + }, diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols-latin.h chafa-1.12.4/chafa/internal/chafa-symbols-latin.h --- chafa-1.2.1/chafa/internal/chafa-symbols-latin.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols-latin.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,5136 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* This is meant to be #included in the symbol definition table of + * chafa-symbols.c. It's kept in a separate file due to its size. + * + * These are Latin symbols not in the ASCII 7-bit range. The bitmaps are a + * close match to the Terminus font (specifically ter-x14n.pcf). + */ + + { + /* [¡] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [¢] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " X " + "XXXXXXX " + "X X " + "X X X " + " XXXXX " + " X ") + }, + { + /* [£] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [¤] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " X X " + " XXXXX " + " X X " + " XXX " + " X X " + " ") + }, + { + /* [¥] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X X " + " X X " + " XXX " + " XXXXX " + " XXXXX " + " X " + " ") + }, + { + /* [¦] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X " + " " + " X " + " X " + " ") + }, + { + /* [§] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " X X " + " XX " + " X XX " + " XX X " + " XX " + " X X " + " XXX ") + }, + { + /* [¨] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [©] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xa9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXXXX" + "X XXXX X" + "X X X" + "X XXX X" + " XXXXXX " + " ") + }, + { + /* [ª] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xaa, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " X " + " XX X " + " XXX " + " XXXXX " + " " + " " + " ") + }, + { + /* [«] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xab, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX XX " + "XX XX " + " XX XX " + " X X " + " ") + }, + { + /* [¬] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xac, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " " + " " + " ") + }, + { + /* [®] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xae, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + "XXXXXXXX" + "X X X X" + "X XXX X" + "X X X X" + " XXXXXX " + " ") + }, + { + /* [¯] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xaf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [°] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " XX " + " " + " " + " " + " " + " ") + }, + { + /* [±] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X " + " XXXXX " + " X " + " XXXXX " + " ") + }, + { + /* [²] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X " + " XXXX " + " " + " " + " " + " ") + }, + { + /* [³] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XX " + " X " + " XXXX " + " " + " " + " " + " ") + }, + { + /* [´] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [µ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " X XX " + " XXXX X " + " X ") + }, + { + /* [¶] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + "X X X " + "X X X " + " XXX X " + " X X " + " X X " + " ") + }, + { + /* [·] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X " + " X " + " " + " " + " ") + }, + { + /* [¸] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " X " + " X ") + }, + { + /* [¹] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xb9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XX " + " X " + " XXX " + " " + " " + " " + " ") + }, + { + /* [º] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xba, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " X X " + " X X " + " XXX " + " XXXXX " + " " + " " + " ") + }, + { + /* [»] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xbb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XX XX " + " XX XX " + " XX XX " + "X X " + " ") + }, + { + /* [¼] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xbc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X X " + " XX " + " XX XX " + "X X X " + " XXX " + " X ") + }, + { + /* [½] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xbd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X X " + " XX " + " XX XX " + "X X X " + " X " + " XXXX ") + }, + { + /* [¾] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xbe, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XXXX " + " XX X " + "XXX X " + " XX " + " XX XX " + "X X X " + " XXX " + " X ") + }, + { + /* [¿] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xbf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " X " + " XX X " + " X X " + " XXXX " + " ") + }, + { + /* [À] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Á] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Â] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Ã] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Ä] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Å] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Æ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + "X X " + "XXXXXX " + "X X " + "X X " + "X XXXX " + " ") + }, + { + /* [Ç] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " X ") + }, + { + /* [È] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXXXX " + " X " + " X " + " XXXX " + " X " + " XXXXXX " + " ") + }, + { + /* [É] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xc9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXXXX " + " X " + " X " + " XXXX " + " X " + " XXXXXX " + " ") + }, + { + /* [Ê] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xca, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXXXX " + " X " + " X " + " XXXX " + " X " + " XXXXXX " + " ") + }, + { + /* [Ë] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xcb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XXXXXX " + " X " + " X " + " XXXX " + " X " + " XXXXXX " + " ") + }, + { + /* [Ì] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xcc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Í] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xcd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Î] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xce, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ï] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xcf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ð] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + "XXXX X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ñ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXXX " + " X X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X " + " ") + }, + { + /* [Ò] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ó] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ô] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Õ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ö] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [×] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX XX " + " XX " + " X X " + " X X " + " ") + }, + { + /* [Ø] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X XX" + " X XXX " + " XXX X " + "XX X " + " XXXX " + " ") + }, + { + /* [Ù] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xd9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ú] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xda, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Û] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xdb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ü] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xdc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ý] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xdd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + "X X " + " X X " + " XX XX " + " X " + " X " + " X " + " ") + }, + { + /* [Þ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xde, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X " + " ") + }, + { + /* [ß] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xdf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X X " + " XXXXX " + " X X " + " XX X " + " X XXX " + " ") + }, + { + /* [à] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [á] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [â] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [ã] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX X " + " X XX " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [ä] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [å] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XX " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [æ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXX X " + "X XXXX " + " XX XX " + " ") + }, + { + /* [ç] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " X ") + }, + { + /* [è] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [é] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xe9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [ê] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xea, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [ë] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xeb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [ì] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xec, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [í] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xed, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [î] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xee, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [ï] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xef, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [ð] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X XX " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ñ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX X " + " X XX " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [ò] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ó] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ô] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [õ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX X " + " X XX " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ö] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [÷] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " X " + " X " + " XXXXX " + " X " + " " + " ") + }, + { + /* [ø] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " X " + " XXXXXX " + " X XX X " + " XX X " + "X XXXX " + " ") + }, + { + /* [ù] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xf9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [ú] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xfa, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [û] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xfb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [ü] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xfc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [ý] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xfd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X X " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [þ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xfe, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " X ") + }, + { + /* [ÿ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0xff, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " X X " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ā] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x100, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [ā] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x101, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XX " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [Ă] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x102, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X XX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [ă] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x103, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [Ą] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x104, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " XXX") + }, + { + /* [ą] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x105, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " XX") + }, + { + /* [Ć] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x106, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ć] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x107, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [Ĉ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x108, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ĉ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x109, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [Ċ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x10a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ċ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x10b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [Č] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x10c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [č] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x10d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [Ď] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x10e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XXXXX " + " X X " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [ď] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x10f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XX X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Đ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x110, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + "XXXX X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [đ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x111, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " XXX " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ē] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x112, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [ē] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x113, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXX " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [Ĕ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x114, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [ĕ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x115, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [Ė] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x116, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [ė] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x117, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [Ę] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x118, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " XX") + }, + { + /* [ę] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x119, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " XX ") + }, + { + /* [Ě] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x11a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [ě] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x11b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [Ĝ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x11c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " ") + }, + { + /* [ĝ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x11d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ğ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x11e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " ") + }, + { + /* [ğ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x11f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ġ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x120, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " ") + }, + { + /* [ġ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x121, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ģ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x122, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " X ") + }, + { + /* [ģ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x123, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ĥ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x124, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [ĥ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x125, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Ħ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x126, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + "XXX XXX" + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [ħ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x127, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " XXX " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Ĩ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x128, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXXX " + " " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [ĩ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x129, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX XX " + " X X " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ī] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x12a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [ī] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x12b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXX " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ĭ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x12c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [ĭ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x12d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [Į] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x12e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " XX ") + }, + { + /* [į] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x12f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XX " + " X " + " X " + " XXX " + " XX ") + }, + { + /* [İ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x130, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [ı] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x131, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [IJ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x132, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXX XXX" + " X X " + " X X " + " X X " + " X X X " + "XXX XX " + " ") + }, + { + /* [ij] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x133, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + "XX XX " + " X X " + " X X " + "XXXX X " + " XX ") + }, + { + /* [Ĵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x134, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " X " + " X " + " X " + " X X " + " XXX " + " ") + }, + { + /* [ĵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x135, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XX " + " X " + " X " + " X X " + " XXX ") + }, + { + /* [Ķ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x136, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X XX " + " XXX " + " XXX " + " X XX " + " X X X " + " X ") + }, + { + /* [ķ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x137, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X XX " + " XXXX " + " X XX " + " X X X " + " X ") + }, + { + /* [ĸ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x138, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X XX " + " XXXX " + " X XX " + " X X " + " ") + }, + { + /* [Ĺ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x139, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " " + " X " + " X " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [ĺ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x13a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ļ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x13b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X " + " X " + " X " + " XXXXXX " + " X ") + }, + { + /* [ļ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x13c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X " + " X " + " X " + " XXX " + " X ") + }, + { + /* [Ľ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x13d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " X XX " + " X " + " X " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [ľ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x13e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ŀ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x13f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X X " + " X X " + " X " + " XXXXXX " + " ") + }, + { + /* [ŀ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x140, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " X X" + " X X" + " X " + " XXX " + " ") + }, + { + /* [Ł] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x141, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + "XX " + " X " + " XXXXXX " + " ") + }, + { + /* [ł] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x142, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X " + " XX " + " XX " + " X " + " XXX " + " ") + }, + { + /* [Ń] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x143, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " X X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X " + " ") + }, + { + /* [ń] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x144, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Ņ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x145, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X X " + " X ") + }, + { + /* [ņ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x146, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X X " + " X ") + }, + { + /* [Ň] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x147, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " X XX X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X " + " ") + }, + { + /* [ň] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x148, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [ʼn] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x149, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Ŋ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x14a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X " + " XX ") + }, + { + /* [ŋ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x14b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " XX ") + }, + { + /* [Ō] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x14c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ō] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x14d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XX " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ŏ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x14e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ŏ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x14f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ő] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x150, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX XX " + " " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ő] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x151, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Œ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x152, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + "X X " + "X XXX " + "X X " + "X X " + " XXXXXX " + " ") + }, + { + /* [œ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x153, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXX " + "X XXXX " + "X X " + " XXXXXX " + " ") + }, + { + /* [Ŕ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x154, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X X " + " X XX " + " ") + }, + { + /* [ŕ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x155, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X " + " X " + " X " + " ") + }, + { + /* [Ŗ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x156, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X X " + " X X XX " + " X ") + }, + { + /* [ŗ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x157, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X " + " XX " + " X ") + }, + { + /* [Ř] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x158, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X X " + " X XX " + " ") + }, + { + /* [ř] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x159, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X " + " X " + " X " + " ") + }, + { + /* [Ś] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x15a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXX " + " X X " + " X " + " XXXXX " + " X X " + " XXXX " + " ") + }, + { + /* [ś] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x15b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " XXXXX " + " X " + " XXXXX " + " ") + }, + { + /* [Ŝ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x15c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " XXXXX " + " X X " + " XXXX " + " ") + }, + { + /* [ŝ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x15d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XXXXXX " + " XXXXX " + " X " + " XXXXX " + " ") + }, + { + /* [Ş] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x15e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXXXX " + " X " + " X X " + " XXXX " + " X ") + }, + { + /* [ş] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x15f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXXX " + " X " + " XXXXX " + " X ") + }, + { + /* [Š] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x160, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " XXXXX " + " X X " + " XXXX " + " ") + }, + { + /* [š] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x161, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " XXXXX " + " X " + " XXXXX " + " ") + }, + { + /* [Ţ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x162, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXX " + " X " + " X " + " X " + " X " + " XX " + " X ") + }, + { + /* [ţ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x163, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXX " + " X " + " X " + " XXX " + " X ") + }, + { + /* [Ť] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x164, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [ť] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x165, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " " + " X " + " XXXXX " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ŧ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x166, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXX " + " X " + " XXXXX " + " X " + " X " + " X " + " ") + }, + { + /* [ŧ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x167, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXX " + " XXX " + " X " + " XXX " + " ") + }, + { + /* [Ũ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x168, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX X " + " X XX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ũ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x169, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX X " + " XX " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ū] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x16a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ū] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x16b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ŭ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x16c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " X XX X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ŭ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x16d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ů] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x16e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ů] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x16f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XX " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ű] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x170, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX XX " + " " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ű] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x171, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ų] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x172, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " XX ") + }, + { + /* [ų] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x173, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " X X " + " XXXXX " + " XX") + }, + { + /* [Ŵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x174, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " " + "X X " + "X X " + "X X X " + "X X X X " + "XX XX " + " ") + }, + { + /* [ŵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x175, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + "X X " + "X X X " + "X X X " + " XXXXX " + " ") + }, + { + /* [Ŷ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x176, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " " + "X X " + " X X " + " X X " + " X " + " X " + " ") + }, + { + /* [ŷ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x177, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " X X " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ÿ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x178, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " " + "X X " + " X X " + " X X " + " X " + " X " + " ") + }, + { + /* [Ź] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x179, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXXXX " + " X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [ź] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x17a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [Ż] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x17b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXXXXX " + " X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [ż] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x17c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [Ž] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x17d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXXXX " + " X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [ž] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x17e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [ſ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x17f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [Ɔ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x186, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [Ǝ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x18e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [Ə] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x18f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ɛ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x190, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ƒ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x192, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " X X " + " XXXXX " + " X " + " X " + " X " + "XXX ") + }, + { + /* [Ɲ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x19d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X " + "X ") + }, + { + /* [ƞ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x19e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " X ") + }, + { + /* [Ƶ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1b5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXXXX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [ƶ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1b6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXX " + " X " + " XXXXXX " + " ") + }, + { + /* [Ʒ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1b7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [Ǎ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1cd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [ǎ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ce, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXX " + " XXXXXX " + " X X " + " XXXXX " + " ") + }, + { + /* [Ǐ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1cf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [ǐ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1d0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XX " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ǒ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1d1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ǒ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1d2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ǔ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1d3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " X X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ǔ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1d4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " X X " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Ǣ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXX " + " XXXXXX " + "X X " + "XXXXXX " + "X X " + "X X " + "X XXXX " + " ") + }, + { + /* [ǣ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XX " + " XXXXXX " + " XXXXXX " + "X X " + " XX XX " + " ") + }, + { + /* [Ǥ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X " + " X XXX " + " X XXXX" + " XXXX " + " ") + }, + { + /* [ǥ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X XXXX" + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ǧ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " ") + }, + { + /* [ǧ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ǩ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " X X " + " X XX " + " XXX " + " XXX " + " X XX " + " X X " + " ") + }, + { + /* [ǩ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1e9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " X " + " X " + " X XX " + " XXXX " + " X XX " + " X X " + " ") + }, + { + /* [Ǫ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ea, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " XX ") + }, + { + /* [ǫ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1eb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " XX ") + }, + { + /* [Ǭ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ec, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " XX ") + }, + { + /* [ǭ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ed, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXX " + " XXXXXX " + " X X " + " X X " + " XXXX " + " XX ") + }, + { + /* [Ǯ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ee, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXX " + " XXXXX " + " X " + " XXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ǯ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ef, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " XX " + " XXXXXX " + " X " + " XXX " + " X " + " XXXXX ") + }, + { + /* [ǰ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1f0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XX " + " X " + " X " + " X X " + " XXX ") + }, + { + /* [Ǵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1f4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXX " + " X X " + " X " + " X XXX " + " X X " + " XXXX " + " ") + }, + { + /* [ǵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1f5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [Ǽ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1fc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXXXX " + "X X " + "X X " + "XXXXXX " + "X X " + "X XXXX " + " ") + }, + { + /* [ǽ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1fd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " XXXXXX " + "X X " + " XX XX " + " ") + }, + { + /* [Ǿ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1fe, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " XXXX " + " X XXX" + " X X X " + " XXX X " + "XX X " + " XXXX " + " ") + }, + { + /* [ǿ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x1ff, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X X " + " XXXXXX " + " X XX X " + " XX X " + "X XXXX " + " ") + }, + { + /* [Ș] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x218, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXXXX " + " X " + " X X " + " XXXX " + " X ") + }, + { + /* [ș] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x219, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXXX " + " X " + " XXXXX " + " X ") + }, + { + /* [Ț] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x21a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXX " + " X " + " X " + " X " + " X " + " XX " + " X ") + }, + { + /* [ț] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x21b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXX " + " X " + " X " + " XXX " + " X ") + }, + { + /* [Ȳ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x232, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XXXXX " + "X X " + " X X " + " XX XX " + " X " + " X " + " X " + " ") + }, + { + /* [ȳ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x233, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + " X X " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [ȷ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x237, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX " + " X " + " X " + " X X " + " XXX ") + }, + { + /* [ɔ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x254, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ɘ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x258, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X XX X " + " X " + " XXXX " + " ") + }, + { + /* [ə] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x259, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX " + " XXXX X " + " X X " + " XXXX " + " ") + }, + { + /* [ɛ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x25b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXX " + " X X " + " XXXX " + " ") + }, + { + /* [ɲ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x272, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X " + "X ") + }, + { + /* [ʒ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x292, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " XXX " + " X " + " XXXXX ") + }, + { + /* [ʻ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2bb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " " + " " + " " + " " + " ") + }, + { + /* [ʼ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2bc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " " + " " + " " + " " + " ") + }, + { + /* [ʽ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2bd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " X " + " " + " " + " " + " " + " " + " ") + }, + { + /* [ˆ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2c6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX " + " X X " + " " + " " + " " + " " + " " + " ") + }, + { + /* [ˇ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2c7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XX " + " " + " " + " " + " " + " " + " ") + }, + { + /* [˘] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2d8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XX " + " " + " " + " " + " " + " " + " ") + }, + { + /* [˙] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2d9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [˛] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2db, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + " X " + " XXX ") + }, + { + /* [˜] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2dc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX X " + " X XX " + " " + " " + " " + " " + " " + " ") + }, + { + /* [˝] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x2dd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " XX XX " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [΄] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x384, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " " + " " + " " + " " + " " + " " + " ") + }, + { + /* [΅] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x385, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XX X " + " X X " + " " + " " + " " + " " + " ") + }, + { + /* [Ά] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x386, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [·] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x387, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X " + " " + " " + " " + " ") + }, + { + /* [Έ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x388, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [Ή] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x389, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Ί] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x38a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ό] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x38c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ύ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x38e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " " + "X X " + " X X " + " X X " + " X " + " X " + " ") + }, + { + /* [Ώ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x38f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + "XX " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XX XX " + " ") + }, + { + /* [ΐ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x390, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XX X " + " " + " XX " + " X " + " X " + " XX " + " ") + }, + { + /* [Α] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x391, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " ") + }, + { + /* [Β] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x392, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " XXXXX " + " X X " + " X X " + " XXXXX " + " ") + }, + { + /* [Γ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x393, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [Δ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x394, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X X " + " XX XX " + " X X " + "X X " + "XXXXXXX " + " ") + }, + { + /* [Ε] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x395, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XXXX " + " X " + " X " + " XXXXXX " + " ") + }, + { + /* [Ζ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x396, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [Η] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x397, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Θ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x398, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X XX X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Ι] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x399, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Κ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x39a, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X XX " + " XXX " + " XXX " + " X XX " + " X X " + " ") + }, + { + /* [Λ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x39b, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X X " + " XX XX " + " X X " + "X X " + "X X " + " ") + }, + { + /* [Μ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x39c, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XX XX " + "X X X X " + "X X X " + "X X " + "X X " + "X X " + " ") + }, + { + /* [Ν] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x39d, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XXX X " + " X XXX " + " X X " + " X X " + " ") + }, + { + /* [Ξ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x39e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " " + " XXXX " + " " + " " + " XXXXXX " + " ") + }, + { + /* [Ο] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x39f, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [Π] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X X " + " X X " + " X X " + " X X " + " X X " + " ") + }, + { + /* [Ρ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X " + " X " + " ") + }, + { + /* [Σ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XX " + " XX " + " X " + " XXXXXX " + " ") + }, + { + /* [Τ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXX " + " X " + " X " + " X " + " X " + " X " + " ") + }, + { + /* [Υ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X X " + " X X " + " XX XX " + " X " + " X " + " X " + " ") + }, + { + /* [Φ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + "X X X " + "X X X " + "X X X " + "X X X " + " XXXXX " + " ") + }, + { + /* [Χ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " X X " + " XX " + " XX " + " X X " + " X X " + " ") + }, + { + /* [Ψ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "X X X " + "X X X " + "X X X " + "X X X " + " XXXXX " + " X " + " ") + }, + { + /* [Ω] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3a9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " X X " + " X X " + " X X " + " XX XX " + " ") + }, + { + /* [Ϊ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3aa, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " XXX " + " X " + " X " + " X " + " X " + " XXX " + " ") + }, + { + /* [Ϋ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ab, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X X " + " " + "X X " + " X X " + " X X " + " X " + " X " + " ") + }, + { + /* [ά] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ac, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " XXX X " + " ") + }, + { + /* [έ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ad, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " XXXX " + " X X " + " XXXX " + " ") + }, + { + /* [ή] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ae, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XXXXXX " + " X X " + " X X " + " X X " + " X ") + }, + { + /* [ί] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3af, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " XX " + " X " + " X " + " XX " + " ") + }, + { + /* [ΰ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XX X " + " X X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [α] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXX X " + " ") + }, + { + /* [β] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X X " + " XXXXX " + " X X " + " X X " + " XXXXX " + " X ") + }, + { + /* [γ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "X X " + " X X " + " X X " + " X " + " X ") + }, + { + /* [δ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ε] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " XXXX " + " X X " + " XXXX " + " ") + }, + { + /* [ζ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " X " + " XX " + " X " + " X " + " XXXX " + " XX ") + }, + { + /* [η] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " X ") + }, + { + /* [θ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X X " + " XXXXX " + " X X " + " X X " + " XXX " + " ") + }, + { + /* [ι] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3b9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX " + " X " + " X " + " XX " + " ") + }, + { + /* [κ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ba, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X XX " + " XXXX " + " X X " + " X X " + " ") + }, + { + /* [λ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3bb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X X " + " XX XX " + " X X " + "X X " + " ") + }, + { + /* [μ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3bc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " X XX " + " XXXX X " + " X ") + }, + { + /* [ν] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3bd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " XX XX " + " X X " + " XX " + " ") + }, + { + /* [ξ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3be, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXX " + " X " + " XXXX " + " X " + " X " + " XXXX " + " XX ") + }, + { + /* [ο] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3bf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [π] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " X X " + " ") + }, + { + /* [ρ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " X ") + }, + { + /* [ς] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X " + " XXXX " + " XX ") + }, + { + /* [σ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXX " + " ") + }, + { + /* [τ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXX " + " X " + " X " + " XX " + " ") + }, + { + /* [υ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [φ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XX XXXX " + "X X X " + "X X X " + " XXXXX " + " X ") + }, + { + /* [χ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c7, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " X X " + " X X " + " XX " + " X X " + " X X ") + }, + { + /* [ψ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c8, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "X X X " + "X X X " + "X X X " + " XXXXX " + " X ") + }, + { + /* [ω] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3c9, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XX XX " + "X X X " + "X X X " + " XX XX " + " ") + }, + { + /* [ϊ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ca, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " XX " + " X " + " X " + " XX " + " ") + }, + { + /* [ϋ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3cb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X X " + " " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ό] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3cc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ύ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3cd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + " X X " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ώ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3ce, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " X " + "XX XX " + "X X X " + "X X X " + " XX XX " + " ") + }, + { + /* [ϑ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3d1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXX " + " X X " + " XXXXX " + "X X " + " X X " + " XXX " + " ") + }, + { + /* [ϕ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3d5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " X " + "XXXXXXX " + "X X X " + "X X X " + " XXXXX " + " X ") + }, + { + /* [ϰ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXX XX " + " XX " + " XX " + "X XX " + " ") + }, + { + /* [ϱ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f1, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X X " + " X X " + " XXXXX " + " XXXX ") + }, + { + /* [ϲ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXXX " + " X " + " X X " + " XXXX " + " ") + }, + { + /* [ϳ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " X " + " " + " XX " + " X " + " X " + " X X " + " XXX ") + }, + { + /* [ϴ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " X X " + " XXXXXX " + " X X " + " X X " + " XXXX " + " ") + }, + { + /* [ϵ] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX " + " XXXXX " + " X " + " XXXXX " + " ") + }, + { + /* [϶] */ + CHAFA_SYMBOL_TAG_LATIN, + 0x3f6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XXXXX " + " XXXXX " + " X " + " XXXXX " + " ") + }, diff -Nru chafa-1.2.1/chafa/internal/chafa-symbols-misc-narrow.h chafa-1.12.4/chafa/internal/chafa-symbols-misc-narrow.h --- chafa-1.2.1/chafa/internal/chafa-symbols-misc-narrow.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-symbols-misc-narrow.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,364 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +/* Miscellaneous single-cell symbols + * --------------------------------- + * + * This is meant to be #included in the symbol definition table of + * chafa-symbols.c. It's kept in a separate file due to its size. */ + + { + /* Horizontal Scan Line 1 */ + CHAFA_SYMBOL_TAG_TECHNICAL, + 0x23ba, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXXX" + " " + " " + " " + " " + " " + " ") + }, + { + /* Horizontal Scan Line 3 */ + CHAFA_SYMBOL_TAG_TECHNICAL, + 0x23bb, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + "XXXXXXXX" + " " + " " + " " + " ") + }, + { + /* Horizontal Scan Line 7 */ + CHAFA_SYMBOL_TAG_TECHNICAL, + 0x23bc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + "XXXXXXXX" + " " + " " + " ") + }, + { + /* Horizontal Scan Line 9 */ + CHAFA_SYMBOL_TAG_TECHNICAL, + 0x23bd, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " " + " " + "XXXXXXXX" + " ") + }, + /* Begin dot characters */ + { + CHAFA_SYMBOL_TAG_DOT, + 0x25ae, /* Black vertical rectangle */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " ") + }, + { + CHAFA_SYMBOL_TAG_DOT, + 0x25a0, /* Black square */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " " + " ") + }, + { + /* Has an emoji variant that may show up unbidden */ + CHAFA_SYMBOL_TAG_DOT | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25aa, /* Black small square */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + " XXXX " + " XXXX " + " XXXX " + " " + " ") + }, + { + /* Black up-pointing triangle */ + CHAFA_SYMBOL_TAG_GEOMETRIC, + 0x25b2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XXXX " + " XXXXXX " + " XXXXXX " + "XXXXXXXX" + " " + " ") + }, + { + /* Black right-pointing triangle */ + /* Has an emoji variant that may show up unbidden */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25b6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXX " + " XXXX " + " XXXXXX " + " XXXX " + " XXX " + " X " + " ") + }, + { + /* Black down-pointing triangle */ + CHAFA_SYMBOL_TAG_GEOMETRIC, + 0x25bc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + "XXXXXXXX" + " XXXXXX " + " XXXXXX " + " XXXX " + " XX " + " " + " ") + }, + { + /* Black left-pointing triangle */ + /* Has an emoji variant that may show up unbidden */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25c0, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " X " + " XXX " + " XXXX " + " XXXXXX " + " XXXX " + " XXX " + " X " + " ") + }, + { + /* Black diamond */ + /* Depending on font, may exceed cell boundaries on VTE */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25c6, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XX " + " XXXX " + " XXXXXX " + " XXXX " + " XX " + " " + " ") + }, + { + /* Black Circle */ + CHAFA_SYMBOL_TAG_GEOMETRIC, + 0x25cf, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXX " + " XXXXXX " + " XXXXXX " + " XXXXXX " + " XXXX " + " " + " ") + }, + { + /* Black Lower Right Triangle */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25e2, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XX " + " XXX " + " XXXX " + " XXXXXX " + " " + " ") + }, + { + /* Black Lower Left Triangle */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25e3, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XX " + " XXX " + " XXXX " + " XXXXXX " + " " + " ") + }, + { + /* Black Upper Left Triangle */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25e4, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXXXX " + " XXXX " + " XXX " + " XX " + " " + " ") + }, + { + /* Black Upper Right Triangle */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25e5, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXXXX " + " XXXX " + " XXX " + " XX " + " " + " ") + }, + { + /* Black Medium Square */ + /* Has en emoji variant that may show up unbidden. See: + * https://github.com/hpjansson/chafa/issues/52 */ + CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, + 0x25fc, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XXXX " + " XXXX " + " XXXX " + " XXXX " + " " + " ") + }, + { + CHAFA_SYMBOL_TAG_DOT, + 0x00b7, /* Middle dot */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX " + " XX " + " " + " " + " ") + }, + { + /* Variant */ + CHAFA_SYMBOL_TAG_DOT, + 0x00b7, /* Middle dot */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX " + " XX " + " " + " " + " ") + }, + { + /* Variant */ + CHAFA_SYMBOL_TAG_DOT, + 0x00b7, /* Middle dot */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " XX " + " XX " + " " + " " + " ") + }, + { + /* Variant */ + CHAFA_SYMBOL_TAG_DOT, + 0x00b7, /* Middle dot */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " XX " + " XX " + " " + " " + " " + " ") + }, + { + /* Variant */ + CHAFA_SYMBOL_TAG_DOT, + 0x00b7, /* Middle dot */ + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " " + " " + " " + " XX " + " XX " + " " + " ") + }, + { + /* Greek Capital Letter Xi */ + CHAFA_SYMBOL_TAG_EXTRA, + 0x039e, + CHAFA_SYMBOL_OUTLINE_8X8 ( + " " + " XXXXXX " + " " + " XXXX " + " " + " XXXXXX " + " " + " ") + }, diff -Nru chafa-1.2.1/chafa/internal/chafa-work-cell.c chafa-1.12.4/chafa/internal/chafa-work-cell.c --- chafa-1.2.1/chafa/internal/chafa-work-cell.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-work-cell.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,379 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" + +#include +#include +#include +#include "chafa.h" +#include "internal/chafa-private.h" +#include "internal/chafa-pixops.h" +#include "internal/chafa-work-cell.h" + +typedef struct +{ + ChafaPixel fg; + ChafaPixel bg; + gint error; +} +SymbolEval; + +typedef struct +{ + ChafaPixel fg; + ChafaPixel bg; + gint error [2]; +} +SymbolEval2; + +static void +accum_to_color (const ChafaColorAccum *accum, ChafaColor *color) +{ + gint i; + + for (i = 0; i < 4; i++) + color->ch [i] = accum->ch [i]; +} + +/* pixels_out must point to CHAFA_SYMBOL_N_PIXELS-element array */ +static void +fetch_canvas_pixel_block (const ChafaPixel *src_image, gint src_width, + ChafaPixel *pixels_out, gint cx, gint cy) +{ + const ChafaPixel *row_p; + const ChafaPixel *end_p; + gint i = 0; + + row_p = src_image + cy * CHAFA_SYMBOL_HEIGHT_PIXELS * src_width + cx * CHAFA_SYMBOL_WIDTH_PIXELS; + end_p = row_p + (src_width * CHAFA_SYMBOL_HEIGHT_PIXELS); + + for ( ; row_p < end_p; row_p += src_width) + { + const ChafaPixel *p0 = row_p; + const ChafaPixel *p1 = p0 + CHAFA_SYMBOL_WIDTH_PIXELS; + + for ( ; p0 < p1; p0++) + pixels_out [i++] = *p0; + } +} + +static void +calc_colors_plain (const ChafaPixel *block, ChafaColorAccum *accums, const guint8 *cov) +{ + const guint8 *in_u8 = (const guint8 *) block; + gint i; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + gint16 *out_s16 = (gint16 *) (accums + *cov++); + + *out_s16++ += *in_u8++; + *out_s16++ += *in_u8++; + *out_s16++ += *in_u8++; + *out_s16++ += *in_u8++; + } +} + +void +chafa_work_cell_get_mean_colors_for_symbol (const ChafaWorkCell *wcell, const ChafaSymbol *sym, + ChafaColorPair *color_pair_out) +{ + const guint8 *covp = (guint8 *) &sym->coverage [0]; + ChafaColorAccum accums [2] = { 0 }; + +#ifdef HAVE_MMX_INTRINSICS + if (chafa_have_mmx ()) + calc_colors_mmx (wcell->pixels, accums, covp); + else +#endif + calc_colors_plain (wcell->pixels, accums, covp); + + if (sym->fg_weight > 1) + chafa_color_accum_div_scalar (&accums [1], sym->fg_weight); + + if (sym->bg_weight > 1) + chafa_color_accum_div_scalar (&accums [0], sym->bg_weight); + + accum_to_color (&accums [0], &color_pair_out->colors [CHAFA_COLOR_PAIR_BG]); + accum_to_color (&accums [1], &color_pair_out->colors [CHAFA_COLOR_PAIR_FG]); +} + +void +chafa_work_cell_calc_mean_color (const ChafaWorkCell *wcell, ChafaColor *color_out) +{ + ChafaColorAccum accum = { 0 }; + const ChafaPixel *block = wcell->pixels; + gint i; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + chafa_color_accum_add (&accum, &block->col); + block++; + } + + chafa_color_accum_div_scalar (&accum, CHAFA_SYMBOL_N_PIXELS); + accum_to_color (&accum, color_out); +} + +/* colors must point to an array of two elements */ +guint64 +chafa_work_cell_to_bitmap (const ChafaWorkCell *wcell, const ChafaColorPair *color_pair) +{ + guint64 bitmap = 0; + const ChafaPixel *block = wcell->pixels; + gint i; + + for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) + { + gint error [2]; + + bitmap <<= 1; + + /* FIXME: What to do about alpha? */ + error [0] = chafa_color_diff_fast (&block [i].col, &color_pair->colors [0]); + error [1] = chafa_color_diff_fast (&block [i].col, &color_pair->colors [1]); + + if (error [0] > error [1]) + bitmap |= 1; + } + + return bitmap; +} + +/* Get cell's pixels sorted by a specific channel. Sorts on demand and caches + * the results. */ +static const guint8 * +work_cell_get_sorted_pixels (ChafaWorkCell *wcell, gint ch) +{ + guint8 *index; + const guint8 index_init [CHAFA_SYMBOL_N_PIXELS] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 + }; + + index = &wcell->pixels_sorted_index [ch] [0]; + + if (wcell->have_pixels_sorted_by_channel [ch]) + return index; + + memcpy (index, index_init, CHAFA_SYMBOL_N_PIXELS); + chafa_sort_pixel_index_by_channel (index, wcell->pixels, CHAFA_SYMBOL_N_PIXELS, ch); + + wcell->have_pixels_sorted_by_channel [ch] = TRUE; + return index; +} + +void +chafa_work_cell_init (ChafaWorkCell *wcell, const ChafaPixel *src_image, + gint src_width, gint cx, gint cy) +{ + memset (wcell->have_pixels_sorted_by_channel, 0, + sizeof (wcell->have_pixels_sorted_by_channel)); + fetch_canvas_pixel_block (src_image, src_width, wcell->pixels, cx, cy); + wcell->dominant_channel = -1; +} + +static gint +work_cell_get_dominant_channel (ChafaWorkCell *wcell) +{ + const guint8 *sorted_pixels [4]; + gint best_range; + gint best_ch; + gint i; + + if (wcell->dominant_channel >= 0) + return wcell->dominant_channel; + + for (i = 0; i < 4; i++) + sorted_pixels [i] = work_cell_get_sorted_pixels (wcell, i); + + best_range = wcell->pixels [sorted_pixels [0] [CHAFA_SYMBOL_N_PIXELS - 1]].col.ch [0] + - wcell->pixels [sorted_pixels [0] [0]].col.ch [0]; + best_ch = 0; + + for (i = 1; i < 4; i++) + { + gint range = wcell->pixels [sorted_pixels [i] [CHAFA_SYMBOL_N_PIXELS - 1]].col.ch [i] + - wcell->pixels [sorted_pixels [i] [0]].col.ch [i]; + + if (range > best_range) + { + best_range = range; + best_ch = i; + } + } + + wcell->dominant_channel = best_ch; + return wcell->dominant_channel; +} + +static void +work_cell_get_dominant_channels_for_symbol (ChafaWorkCell *wcell, const ChafaSymbol *sym, + gint *bg_ch_out, gint *fg_ch_out) +{ + gint16 min [2] [4] = { { G_MAXINT16, G_MAXINT16, G_MAXINT16, G_MAXINT16 }, + { G_MAXINT16, G_MAXINT16, G_MAXINT16, G_MAXINT16 } }; + gint16 max [2] [4] = { { G_MININT16, G_MININT16, G_MININT16, G_MININT16 }, + { G_MININT16, G_MININT16, G_MININT16, G_MININT16 } }; + gint16 range [2] [4]; + gint ch, best_ch [2]; + const guint8 *sorted_pixels [4]; + gint i, j; + + if (sym->popcount == 0) + { + *bg_ch_out = work_cell_get_dominant_channel (wcell); + *fg_ch_out = -1; + return; + } + else if (sym->popcount == CHAFA_SYMBOL_N_PIXELS) + { + *bg_ch_out = -1; + *fg_ch_out = work_cell_get_dominant_channel (wcell); + return; + } + + for (i = 0; i < 4; i++) + sorted_pixels [i] = work_cell_get_sorted_pixels (wcell, i); + + /* Get minimums */ + + for (j = 0; j < 4; j++) + { + gint pen_a = sym->coverage [sorted_pixels [j] [0]]; + min [pen_a] [j] = wcell->pixels [sorted_pixels [j] [0]].col.ch [j]; + + for (i = 1; ; i++) + { + gint pen_b = sym->coverage [sorted_pixels [j] [i]]; + if (pen_b != pen_a) + { + min [pen_b] [j] = wcell->pixels [sorted_pixels [j] [i]].col.ch [j]; + break; + } + } + } + + /* Get maximums */ + + for (j = 0; j < 4; j++) + { + gint pen_a = sym->coverage [sorted_pixels [j] [CHAFA_SYMBOL_N_PIXELS - 1]]; + max [pen_a] [j] = wcell->pixels [sorted_pixels [j] [CHAFA_SYMBOL_N_PIXELS - 1]].col.ch [j]; + + for (i = CHAFA_SYMBOL_N_PIXELS - 2; ; i--) + { + gint pen_b = sym->coverage [sorted_pixels [j] [i]]; + if (pen_b != pen_a) + { + max [pen_b] [j] = wcell->pixels [sorted_pixels [j] [i]].col.ch [j]; + break; + } + } + } + + /* Find channel with the greatest range */ + + for (ch = 0; ch < 4; ch++) + { + range [0] [ch] = max [0] [ch] - min [0] [ch]; + range [1] [ch] = max [1] [ch] - min [1] [ch]; + } + + best_ch [0] = 0; + best_ch [1] = 0; + for (ch = 1; ch < 4; ch++) + { + if (range [0] [ch] > range [0] [best_ch [0]]) + best_ch [0] = ch; + if (range [1] [ch] > range [1] [best_ch [1]]) + best_ch [1] = ch; + } + + *bg_ch_out = best_ch [0]; + *fg_ch_out = best_ch [1]; +} + +void +chafa_work_cell_get_contrasting_color_pair (ChafaWorkCell *wcell, ChafaColorPair *color_pair_out) +{ + const guint8 *sorted_pixels; + + sorted_pixels = work_cell_get_sorted_pixels (wcell, work_cell_get_dominant_channel (wcell)); + + /* Choose two colors by median cut */ + + color_pair_out->colors [CHAFA_COLOR_PAIR_BG] = wcell->pixels [sorted_pixels [0]].col; + color_pair_out->colors [CHAFA_COLOR_PAIR_FG] = wcell->pixels [sorted_pixels [CHAFA_SYMBOL_N_PIXELS - 1]].col; +} + +static const ChafaPixel * +work_cell_get_nth_sorted_pixel (ChafaWorkCell *wcell, const ChafaSymbol *sym, + gint channel, gint pen, gint n) +{ + const guint8 *sorted_pixels; + gint i, j; + + pen ^= 1; + sorted_pixels = work_cell_get_sorted_pixels (wcell, channel); + + for (i = 0, j = 0; ; i++) + { + j += (sym->coverage [sorted_pixels [i]] ^ pen); + if (j > n) + return &wcell->pixels [sorted_pixels [i]]; + } + + g_assert_not_reached (); + return NULL; +} + +void +chafa_work_cell_get_median_colors_for_symbol (ChafaWorkCell *wcell, const ChafaSymbol *sym, + ChafaColorPair *color_pair_out) +{ + gint bg_ch, fg_ch; + + /* This is extremely slow and makes almost no difference */ + work_cell_get_dominant_channels_for_symbol (wcell, sym, &bg_ch, &fg_ch); + + if (bg_ch < 0) + { + color_pair_out->colors [0] = color_pair_out->colors [1] + = work_cell_get_nth_sorted_pixel (wcell, sym, fg_ch, 1, sym->popcount / 2)->col; + } + else if (fg_ch < 0) + { + color_pair_out->colors [0] = color_pair_out->colors [1] + = work_cell_get_nth_sorted_pixel (wcell, sym, bg_ch, 0, + (CHAFA_SYMBOL_N_PIXELS - sym->popcount) / 2)->col; + } + else + { + color_pair_out->colors [CHAFA_COLOR_PAIR_FG] + = work_cell_get_nth_sorted_pixel (wcell, sym, fg_ch, 1, sym->popcount / 2)->col; + color_pair_out->colors [CHAFA_COLOR_PAIR_BG] + = work_cell_get_nth_sorted_pixel (wcell, sym, bg_ch, 0, + (CHAFA_SYMBOL_N_PIXELS - sym->popcount) / 2)->col; + } +} diff -Nru chafa-1.2.1/chafa/internal/chafa-work-cell.h chafa-1.12.4/chafa/internal/chafa-work-cell.h --- chafa-1.2.1/chafa/internal/chafa-work-cell.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/chafa-work-cell.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __CHAFA_WORK_CELL_H__ +#define __CHAFA_WORK_CELL_H__ + +G_BEGIN_DECLS + +typedef struct ChafaWorkCell ChafaWorkCell; + +struct ChafaWorkCell +{ + ChafaPixel pixels [CHAFA_SYMBOL_N_PIXELS]; + guint8 pixels_sorted_index [4] [CHAFA_SYMBOL_N_PIXELS]; + guint8 have_pixels_sorted_by_channel [4]; + gint dominant_channel; +}; + +/* Currently unused */ +typedef enum +{ + CHAFA_PICK_CONSIDER_INVERTED = (1 << 0), + CHAFA_PICK_HAVE_ALPHA = (1 << 1) +} +ChafaPickFlags; + +void chafa_work_cell_init (ChafaWorkCell *wcell, const ChafaPixel *src_image, + gint src_width, gint cx, gint cy); + +void chafa_work_cell_get_mean_colors_for_symbol (const ChafaWorkCell *wcell, const ChafaSymbol *sym, + ChafaColorPair *color_pair_out); +void chafa_work_cell_get_median_colors_for_symbol (ChafaWorkCell *wcell, const ChafaSymbol *sym, + ChafaColorPair *color_pair_out); +void chafa_work_cell_get_contrasting_color_pair (ChafaWorkCell *wcell, ChafaColorPair *color_pair_out); +void chafa_work_cell_calc_mean_color (const ChafaWorkCell *wcell, ChafaColor *color_out); +guint64 chafa_work_cell_to_bitmap (const ChafaWorkCell *wcell, const ChafaColorPair *color_pair); + +G_END_DECLS + +#endif /* __CHAFA_WORK_CELL_H__ */ diff -Nru chafa-1.2.1/chafa/internal/Makefile.am chafa-1.12.4/chafa/internal/Makefile.am --- chafa-1.2.1/chafa/internal/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,84 @@ +SUBDIRS = smolscale + +## --- Library --- + +noinst_LTLIBRARIES = libchafa-internal.la + +libchafa_internal_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -DCHAFA_COMPILATION +libchafa_internal_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) +libchafa_internal_la_LIBADD = $(GLIB_LIBS) smolscale/libsmolscale.la -lm + +libchafa_internal_la_SOURCES = \ + chafa-base64.c \ + chafa-base64.h \ + chafa-batch.c \ + chafa-batch.h \ + chafa-bitfield.h \ + chafa-canvas-internal.h \ + chafa-canvas-printer.c \ + chafa-canvas-printer.h \ + chafa-color.c \ + chafa-color.h \ + chafa-color-hash.c \ + chafa-color-hash.h \ + chafa-color-table.c \ + chafa-color-table.h \ + chafa-dither.c \ + chafa-dither.h \ + chafa-indexed-image.c \ + chafa-indexed-image.h \ + chafa-iterm2-canvas.c \ + chafa-iterm2-canvas.h \ + chafa-kitty-canvas.c \ + chafa-kitty-canvas.h \ + chafa-palette.c \ + chafa-palette.h \ + chafa-pca.c \ + chafa-pca.h \ + chafa-pixops.c \ + chafa-pixops.h \ + chafa-private.h \ + chafa-sixel-canvas.c \ + chafa-sixel-canvas.h \ + chafa-string-util.c \ + chafa-string-util.h \ + chafa-symbols.c \ + chafa-symbols-ascii.h \ + chafa-symbols-block.h \ + chafa-symbols-kana.h \ + chafa-symbols-latin.h \ + chafa-symbols-misc-narrow.h \ + chafa-work-cell.c \ + chafa-work-cell.h + +if HAVE_MMX_INTRINSICS +noinst_LTLIBRARIES += libchafa-mmx.la +libchafa_internal_la_LIBADD += libchafa-mmx.la +libchafa_mmx_la_SOURCES = chafa-mmx.c +libchafa_mmx_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mmmx -DCHAFA_COMPILATION +libchafa_mmx_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) +endif + +if HAVE_SSE41_INTRINSICS +noinst_LTLIBRARIES += libchafa-sse41.la +libchafa_internal_la_LIBADD += libchafa-sse41.la +libchafa_sse41_la_SOURCES = chafa-sse41.c +libchafa_sse41_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -msse4.1 -DCHAFA_COMPILATION +libchafa_sse41_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) +endif + +if HAVE_POPCNT_INTRINSICS +noinst_LTLIBRARIES += libchafa-popcnt.la +libchafa_internal_la_LIBADD += libchafa-popcnt.la +libchafa_popcnt_la_SOURCES = chafa-popcnt.c +libchafa_popcnt_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mpopcnt -DCHAFA_COMPILATION +libchafa_popcnt_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) +endif + +## --- General --- + +## Include $(top_builddir)/chafa to get generated chafaconfig.h. + +AM_CPPFLAGS = \ + -I$(top_srcdir)/chafa \ + -I$(top_builddir)/chafa diff -Nru chafa-1.2.1/chafa/internal/smolscale/COPYING chafa-1.12.4/chafa/internal/smolscale/COPYING --- chafa-1.2.1/chafa/internal/smolscale/COPYING 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/COPYING 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,14 @@ +This software is Copyright © 2019 Hans Petter Jansson. All rights reserved. + +You may use and redistribute this software under the terms and conditions laid +out in either the BSD 4-clause license (see COPYING.BSD-4) or the LGPL +version 3 (see COPYING.LGPLv3). + +The practical intent is that you should be able to use the software in +an LGPLv3-compatible project without giving credit (although it would be nice +if you did so). However, if you're using it in a closed-source project, you +must either publish it separately in an LGPLv3-licensed library and link +with that, or credit the contributors in your advertising materials. + +If you want to use it under a different license, feel free to contact me by +e-mail at . diff -Nru chafa-1.2.1/chafa/internal/smolscale/COPYING.BSD-4 chafa-1.12.4/chafa/internal/smolscale/COPYING.BSD-4 --- chafa-1.2.1/chafa/internal/smolscale/COPYING.BSD-4 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/COPYING.BSD-4 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,34 @@ +Copyright © 2019 Hans Petter Jansson. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +4. All advertising materials mentioning features or use of this + software must display the following acknowledgement: 'This + product includes software developed by Hans Petter Jansson + (https://hpjansson.org/).' + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff -Nru chafa-1.2.1/chafa/internal/smolscale/COPYING.LGPLv3 chafa-1.12.4/chafa/internal/smolscale/COPYING.LGPLv3 --- chafa-1.2.1/chafa/internal/smolscale/COPYING.LGPLv3 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/COPYING.LGPLv3 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff -Nru chafa-1.2.1/chafa/internal/smolscale/Makefile.am chafa-1.12.4/chafa/internal/smolscale/Makefile.am --- chafa-1.2.1/chafa/internal/smolscale/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,25 @@ +noinst_LTLIBRARIES = libsmolscale.la + +SMOLSCALE_CFLAGS = $(LIBCHAFA_CFLAGS) +SMOLSCALE_LDFLAGS = $(LIBCHAFA_LDFLAGS) + +if HAVE_AVX2_INTRINSICS +SMOLSCALE_CFLAGS += -DSMOL_WITH_AVX2 +endif + +libsmolscale_la_CFLAGS = $(SMOLSCALE_CFLAGS) +libsmolscale_la_LDFLAGS = $(SMOLSCALE_LDFLAGS) +libsmolscale_la_LIBADD = + +libsmolscale_la_SOURCES = \ + smolscale.c \ + smolscale.h \ + smolscale-private.h + +if HAVE_AVX2_INTRINSICS +noinst_LTLIBRARIES += libsmolscale-avx2.la +libsmolscale_la_LIBADD += libsmolscale-avx2.la +libsmolscale_avx2_la_SOURCES = smolscale-avx2.c +libsmolscale_avx2_la_CFLAGS = $(SMOLSCALE_CFLAGS) -mavx2 +libsmolscale_avx2_la_LDFLAGS = $(SMOLSCALE_LDFLAGS) +endif diff -Nru chafa-1.2.1/chafa/internal/smolscale/README chafa-1.12.4/chafa/internal/smolscale/README --- chafa-1.2.1/chafa/internal/smolscale/README 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/README 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,42 @@ +Smolscale +========= + +Smolscale is a smol piece of C code for quickly scaling images to a reasonable +level of quality using CPU resources only (no GPU). It operates on 4-channel +data with 32 bits per pixel, i.e. packed RGBA, ARGB, BGRA, etc. It supports +both premultiplied and unassociated alpha and can convert between the two. It +is host byte ordering agnostic. + +The design goals are: + +* High throughput: Optimized code, within reason. Easily parallelizable. + +* Decent quality: No "jaggies" as produced by nearest-neighbor scaling. + +* Low memory overhead: Mostly on the stack. + +* Simplicity: A modern C toolchain as the only dependency. + +* Ease of use: One-shot and row-batch APIs. + +Usage +----- + +First, read COPYING. If your project meets the requirements, you should be +able to copy the following files into it and add it to your build with +minimal fuss: + + smolscale.c + smolscale.h + smolscale-private.h + +If you want AVX2 SIMD support, optionally copy this additional file and +compile everything with -DSMOL_WITH_AVX2: + + smolscale-avx2.c + +Keep in mind that this file is mostly just a straight copy of the generic +code for the time being. Still, you will get a performance boost by building +it with -mavx2 and letting Smolscale pick the implementation at runtime. + +The API documentation lives in smolscale.h along with the public declarations. diff -Nru chafa-1.2.1/chafa/internal/smolscale/smolscale-avx2.c chafa-1.12.4/chafa/internal/smolscale/smolscale-avx2.c --- chafa-1.2.1/chafa/internal/smolscale/smolscale-avx2.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/smolscale-avx2.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,2870 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright © 2019-2022 Hans Petter Jansson. See COPYING for details. */ + +#include /* assert */ +#include /* malloc, free, alloca */ +#include /* ptrdiff_t */ +#include /* memset */ +#include +#include +#include "smolscale-private.h" + + +/* --- Linear interpolation helpers --- */ + +#define LERP_SIMD256_EPI32(a, b, f) \ + _mm256_add_epi32 ( \ + _mm256_srli_epi32 ( \ + _mm256_mullo_epi32 ( \ + _mm256_sub_epi32 ((a), (b)), factors), 8), (b)) + +#define LERP_SIMD128_EPI32(a, b, f) \ + _mm_add_epi32 ( \ + _mm_srli_epi32 ( \ + _mm_mullo_epi32 ( \ + _mm_sub_epi32 ((a), (b)), factors), 8), (b)) + +#define LERP_SIMD256_EPI32_AND_MASK(a, b, f, mask) \ + _mm256_and_si256 (LERP_SIMD256_EPI32 ((a), (b), (f)), (mask)) + +#define LERP_SIMD128_EPI32_AND_MASK(a, b, f, mask) \ + _mm_and_si128 (LERP_SIMD128_EPI32 ((a), (b), (f)), (mask)) + +/* --- Premultiplication --- */ + +#define INVERTED_DIV_SHIFT 21 +#define INVERTED_DIV_ROUNDING (1U << (INVERTED_DIV_SHIFT - 1)) +#define INVERTED_DIV_ROUNDING_128BPP \ + (((uint64_t) INVERTED_DIV_ROUNDING << 32) | INVERTED_DIV_ROUNDING) + +/* This table is used to divide by an integer [1..255] using only a lookup, + * multiplication and a shift. This is faster than plain division on most + * architectures. + * + * Each entry represents the integer 2097152 (1 << 21) divided by the index + * of the entry. Consequently, + * + * (v / i) ~= (v * inverted_div_table [i] + (1 << 20)) >> 21 + * + * (1 << 20) is added for nearest rounding. It would've been nice to keep + * this table in uint16_t, but alas, we need the extra bits for sufficient + * precision. */ +static const uint32_t inverted_div_table [256] = +{ + 0,2097152,1048576, 699051, 524288, 419430, 349525, 299593, + 262144, 233017, 209715, 190650, 174763, 161319, 149797, 139810, + 131072, 123362, 116508, 110376, 104858, 99864, 95325, 91181, + 87381, 83886, 80660, 77672, 74898, 72316, 69905, 67650, + 65536, 63550, 61681, 59919, 58254, 56680, 55188, 53773, + 52429, 51150, 49932, 48771, 47663, 46603, 45590, 44620, + 43691, 42799, 41943, 41121, 40330, 39569, 38836, 38130, + 37449, 36792, 36158, 35545, 34953, 34380, 33825, 33288, + 32768, 32264, 31775, 31301, 30840, 30394, 29959, 29537, + 29127, 28728, 28340, 27962, 27594, 27236, 26887, 26546, + 26214, 25891, 25575, 25267, 24966, 24672, 24385, 24105, + 23831, 23564, 23302, 23046, 22795, 22550, 22310, 22075, + 21845, 21620, 21400, 21183, 20972, 20764, 20560, 20361, + 20165, 19973, 19784, 19600, 19418, 19240, 19065, 18893, + 18725, 18559, 18396, 18236, 18079, 17924, 17772, 17623, + 17476, 17332, 17190, 17050, 16913, 16777, 16644, 16513, + 16384, 16257, 16132, 16009, 15888, 15768, 15650, 15534, + 15420, 15308, 15197, 15087, 14980, 14873, 14769, 14665, + 14564, 14463, 14364, 14266, 14170, 14075, 13981, 13888, + 13797, 13707, 13618, 13530, 13443, 13358, 13273, 13190, + 13107, 13026, 12945, 12866, 12788, 12710, 12633, 12558, + 12483, 12409, 12336, 12264, 12193, 12122, 12053, 11984, + 11916, 11848, 11782, 11716, 11651, 11586, 11523, 11460, + 11398, 11336, 11275, 11215, 11155, 11096, 11038, 10980, + 10923, 10866, 10810, 10755, 10700, 10645, 10592, 10538, + 10486, 10434, 10382, 10331, 10280, 10230, 10180, 10131, + 10082, 10034, 9986, 9939, 9892, 9846, 9800, 9754, + 9709, 9664, 9620, 9576, 9533, 9489, 9447, 9404, + 9362, 9321, 9279, 9239, 9198, 9158, 9118, 9079, + 9039, 9001, 8962, 8924, 8886, 8849, 8812, 8775, + 8738, 8702, 8666, 8630, 8595, 8560, 8525, 8490, + 8456, 8422, 8389, 8355, 8322, 8289, 8257, 8224, +}; + +/* Masking and shifting out the results is left to the caller. In + * and out may not overlap. */ +static SMOL_INLINE void +unpremul_i_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, + uint64_t * SMOL_RESTRICT out, + uint8_t alpha) +{ + out [0] = ((in [0] * (uint64_t) inverted_div_table [alpha] + + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); + out [1] = ((in [1] * (uint64_t) inverted_div_table [alpha] + + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); +} + +static SMOL_INLINE void +unpremul_p_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, + uint64_t * SMOL_RESTRICT out, + uint8_t alpha) +{ + out [0] = (((in [0] << 8) * (uint64_t) inverted_div_table [alpha]) + >> INVERTED_DIV_SHIFT); + out [1] = (((in [1] << 8) * (uint64_t) inverted_div_table [alpha]) + >> INVERTED_DIV_SHIFT); +} + +static SMOL_INLINE uint64_t +unpremul_p_to_u_64bpp (const uint64_t in, + uint8_t alpha) +{ + uint64_t in_128bpp [2]; + uint64_t out_128bpp [2]; + + in_128bpp [0] = (in & 0x000000ff000000ff); + in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; + + unpremul_p_to_u_128bpp (in_128bpp, out_128bpp, alpha); + + return (out_128bpp [0] & 0x000000ff000000ff) + | ((out_128bpp [1] & 0x000000ff000000ff) << 16); +} + +static SMOL_INLINE uint64_t +premul_u_to_p_64bpp (const uint64_t in, + uint8_t alpha) +{ + return ((in * ((uint16_t) alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; +} + +/* --- Packing --- */ + +/* It's nice to be able to shift by a negative amount */ +#define SHIFT_S(in, s) ((s >= 0) ? (in) << (s) : (in) >> -(s)) + +#if 0 +/* Currently unused */ + +/* This is kind of bulky (~13 x86 insns), but it's about the same as using + * unions, and we don't have to worry about endianness. */ +#define PACK_FROM_1234_64BPP(in, a, b, c, d) \ + ((SHIFT_S ((in), ((a) - 1) * 16 + 8 - 32) & 0xff000000) \ + | (SHIFT_S ((in), ((b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in), ((c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in), ((d) - 1) * 16 + 8 - 56) & 0x000000ff)) +#endif + +#define PACK_FROM_1234_128BPP(in, a, b, c, d) \ + ((SHIFT_S ((in [((a) - 1) >> 1]), (((a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ + | (SHIFT_S ((in [((b) - 1) >> 1]), (((b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in [((c) - 1) >> 1]), (((c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in [((d) - 1) >> 1]), (((d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) + +#define SWAP_2_AND_3(n) ((n) == 2 ? 3 : (n) == 3 ? 2 : n) + +#define PACK_FROM_1324_64BPP(in, a, b, c, d) \ + ((SHIFT_S ((in), (SWAP_2_AND_3 (a) - 1) * 16 + 8 - 32) & 0xff000000) \ + | (SHIFT_S ((in), (SWAP_2_AND_3 (b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in), (SWAP_2_AND_3 (c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in), (SWAP_2_AND_3 (d) - 1) * 16 + 8 - 56) & 0x000000ff)) + +#if 0 +/* Currently unused */ + +#define PACK_FROM_1324_128BPP(in, a, b, c, d) \ + ((SHIFT_S ((in [(SWAP_2_AND_3 (a) - 1) >> 1]), \ + ((SWAP_2_AND_3 (a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ + | (SHIFT_S ((in [(SWAP_2_AND_3 (b) - 1) >> 1]), \ + ((SWAP_2_AND_3 (b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in [(SWAP_2_AND_3 (c) - 1) >> 1]), \ + ((SWAP_2_AND_3 (c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in [(SWAP_2_AND_3 (d) - 1) >> 1]), \ + ((SWAP_2_AND_3 (d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) +#endif + +/* Pack p -> p */ + +static SMOL_INLINE uint32_t +pack_pixel_1324_p_to_1234_p_64bpp (uint64_t in) +{ + return in | (in >> 24); +} + +static void +pack_row_1324_p_to_1234_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); + } +} + +static void +pack_row_132a_p_to_123_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + /* FIXME: Would be faster to shift directly */ + uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_132a_p_to_321_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + /* FIXME: Would be faster to shift directly */ + uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +#define DEF_PACK_FROM_1324_P_TO_P_64BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (uint64_t in) \ +{ \ + return PACK_FROM_1324_64BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_1324_p_to_##a##b##c##d##_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + *(row_out++) = pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (*(row_in++)); \ +} + +DEF_PACK_FROM_1324_P_TO_P_64BPP (1, 4, 3, 2) +DEF_PACK_FROM_1324_P_TO_P_64BPP (2, 3, 4, 1) +DEF_PACK_FROM_1324_P_TO_P_64BPP (3, 2, 1, 4) +DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 1, 2, 3) +DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 3, 2, 1) + +static SMOL_INLINE uint32_t +pack_pixel_1234_p_to_1234_p_128bpp (const uint64_t *in) +{ + /* FIXME: Are masks needed? */ + return ((in [0] >> 8) & 0xff000000) + | ((in [0] << 16) & 0x00ff0000) + | ((in [1] >> 24) & 0x0000ff00) + | (in [1] & 0x000000ff); +} + +static void +pack_row_1234_p_to_1234_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_1234_p_to_1234_p_128bpp (row_in); + row_in += 2; + } +} + +#define DEF_PACK_FROM_1234_P_TO_P_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + return PACK_FROM_1234_128BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_1234_P_TO_P_128BPP (1, 4, 3, 2) +DEF_PACK_FROM_1234_P_TO_P_128BPP (2, 3, 4, 1) +DEF_PACK_FROM_1234_P_TO_P_128BPP (3, 2, 1, 4) +DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 1, 2, 3) +DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 3, 2, 1) + +static void +pack_row_123a_p_to_123_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = *row_in >> 32; + *(row_out++) = *(row_in++); + *(row_out++) = *(row_in++) >> 32; + } +} + +static void +pack_row_123a_p_to_321_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = row_in [1] >> 32; + *(row_out++) = row_in [0]; + *(row_out++) = row_in [0] >> 32; + row_in += 2; + } +} + +/* Pack p (alpha last) -> u */ + +static SMOL_INLINE uint32_t +pack_pixel_132a_p_to_1234_u_64bpp (uint64_t in) +{ + uint8_t alpha = in; + in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; + return in | (in >> 24); +} + +static void +pack_row_132a_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); + } +} + +static void +pack_row_132a_p_to_123_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_132a_p_to_321_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +#define DEF_PACK_FROM_132A_P_TO_U_64BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ +{ \ + uint8_t alpha = in; \ + in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; \ + return PACK_FROM_1324_64BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_132a_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + *(row_out++) = pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ +} + +DEF_PACK_FROM_132A_P_TO_U_64BPP (3, 2, 1, 4) +DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 1, 2, 3) +DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 3, 2, 1) + +#define DEF_PACK_FROM_123A_P_TO_U_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + uint64_t t [2]; \ + uint8_t alpha = in [1]; \ + unpremul_p_to_u_128bpp (in, t, alpha); \ + t [1] = (t [1] & 0xffffffff00000000) | alpha; \ + return PACK_FROM_1234_128BPP (t, a, b, c, d); \ +} \ + \ +static void \ +pack_row_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_123A_P_TO_U_128BPP (1, 2, 3, 4) +DEF_PACK_FROM_123A_P_TO_U_128BPP (3, 2, 1, 4) +DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 1, 2, 3) +DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 3, 2, 1) + +static void +pack_row_123a_p_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_123a_p_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +/* Pack p (alpha first) -> u */ + +static SMOL_INLINE uint32_t +pack_pixel_a324_p_to_1234_u_64bpp (uint64_t in) +{ + uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ + in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); + return in | (in >> 24); +} + +static void +pack_row_a324_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); + } +} + +static void +pack_row_a324_p_to_234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + *(row_out++) = p; + } +} + +static void +pack_row_a324_p_to_432_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + } +} + +#define DEF_PACK_FROM_A324_P_TO_U_64BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ +{ \ + uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ \ + in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); \ + return PACK_FROM_1324_64BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_a324_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + *(row_out++) = pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ +} + +DEF_PACK_FROM_A324_P_TO_U_64BPP (1, 4, 3, 2) +DEF_PACK_FROM_A324_P_TO_U_64BPP (2, 3, 4, 1) +DEF_PACK_FROM_A324_P_TO_U_64BPP (4, 3, 2, 1) + +#define DEF_PACK_FROM_A234_P_TO_U_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + uint64_t t [2]; \ + uint8_t alpha = in [0] >> 32; \ + unpremul_p_to_u_128bpp (in, t, alpha); \ + t [0] = (t [0] & 0x00000000ffffffff) | ((uint64_t) alpha << 32); \ + return PACK_FROM_1234_128BPP (t, a, b, c, d); \ +} \ + \ +static void \ +pack_row_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 2, 3, 4) +DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 4, 3, 2) +DEF_PACK_FROM_A234_P_TO_U_128BPP (2, 3, 4, 1) +DEF_PACK_FROM_A234_P_TO_U_128BPP (4, 3, 2, 1) + +static void +pack_row_a234_p_to_234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + *(row_out++) = p; + } +} + +static void +pack_row_a234_p_to_432_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + } +} + +/* Pack i (alpha last) to u */ + +static SMOL_INLINE uint32_t +pack_pixel_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT in) +{ + uint8_t alpha = (in [1] >> 8) & 0xff; + uint64_t t [2]; + + unpremul_i_to_u_128bpp (in, t, alpha); + + return ((t [0] >> 8) & 0xff000000) + | ((t [0] << 16) & 0x00ff0000) + | ((t [1] >> 24) & 0x0000ff00) + | alpha; +} + +static void +pack_8x_123a_i_to_xxxx_u_128bpp (const uint64_t * SMOL_RESTRICT *in, + uint32_t * SMOL_RESTRICT *out, + uint32_t * out_max, + const __m256i channel_shuf) +{ +#define ALPHA_MUL (1 << (INVERTED_DIV_SHIFT - 8)) +#define ALPHA_MASK SMOL_8X1BIT (0, 1, 0, 0, 0, 1, 0, 0) + + const __m256i ones = _mm256_set_epi32 ( + ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, + ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL); + const __m256i alpha_clean_mask = _mm256_set_epi32 ( + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff); + const __m256i rounding = _mm256_set_epi32 ( + INVERTED_DIV_ROUNDING, 0, INVERTED_DIV_ROUNDING, INVERTED_DIV_ROUNDING, + INVERTED_DIV_ROUNDING, 0, INVERTED_DIV_ROUNDING, INVERTED_DIV_ROUNDING); + __m256i m00, m01, m02, m03, m04, m05, m06, m07, m08; + const __m256i * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; + __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; + + SMOL_ASSUME_ALIGNED (my_in, __m256i * SMOL_RESTRICT); + + while ((ptrdiff_t) (my_out + 1) <= (ptrdiff_t) out_max) + { + /* Load inputs */ + + m00 = _mm256_stream_load_si256 (my_in); + my_in++; + m01 = _mm256_stream_load_si256 (my_in); + my_in++; + m02 = _mm256_stream_load_si256 (my_in); + my_in++; + m03 = _mm256_stream_load_si256 (my_in); + my_in++; + + /* Load alpha factors */ + + m04 = _mm256_slli_si256 (m00, 4); + m06 = _mm256_srli_si256 (m03, 4); + m05 = _mm256_blend_epi32 (m04, m01, ALPHA_MASK); + m07 = _mm256_blend_epi32 (m06, m02, ALPHA_MASK); + m07 = _mm256_srli_si256 (m07, 4); + + m04 = _mm256_blend_epi32 (m05, m07, SMOL_8X1BIT (0, 0, 1, 1, 0, 0, 1, 1)); + m04 = _mm256_srli_epi32 (m04, 8); + m04 = _mm256_and_si256 (m04, alpha_clean_mask); + m04 = _mm256_i32gather_epi32 ((const void *) inverted_div_table, m04, 4); + + /* 2 pixels times 4 */ + + m05 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (3, 3, 3, 3)); + m06 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (2, 2, 2, 2)); + m07 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (1, 1, 1, 1)); + m08 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (0, 0, 0, 0)); + + m05 = _mm256_blend_epi32 (m05, ones, ALPHA_MASK); + m06 = _mm256_blend_epi32 (m06, ones, ALPHA_MASK); + m07 = _mm256_blend_epi32 (m07, ones, ALPHA_MASK); + m08 = _mm256_blend_epi32 (m08, ones, ALPHA_MASK); + + m05 = _mm256_mullo_epi32 (m05, m00); + m06 = _mm256_mullo_epi32 (m06, m01); + m07 = _mm256_mullo_epi32 (m07, m02); + m08 = _mm256_mullo_epi32 (m08, m03); + + m05 = _mm256_add_epi32 (m05, rounding); + m06 = _mm256_add_epi32 (m06, rounding); + m07 = _mm256_add_epi32 (m07, rounding); + m08 = _mm256_add_epi32 (m08, rounding); + + m05 = _mm256_srli_epi32 (m05, INVERTED_DIV_SHIFT); + m06 = _mm256_srli_epi32 (m06, INVERTED_DIV_SHIFT); + m07 = _mm256_srli_epi32 (m07, INVERTED_DIV_SHIFT); + m08 = _mm256_srli_epi32 (m08, INVERTED_DIV_SHIFT); + + /* Pack and store */ + + m00 = _mm256_packus_epi32 (m05, m06); + m01 = _mm256_packus_epi32 (m07, m08); + m00 = _mm256_packus_epi16 (m00, m01); + + m00 = _mm256_shuffle_epi8 (m00, channel_shuf); + m00 = _mm256_permute4x64_epi64 (m00, SMOL_4X2BIT (3, 1, 2, 0)); + m00 = _mm256_shuffle_epi32 (m00, SMOL_4X2BIT (3, 1, 2, 0)); + + _mm256_storeu_si256 (my_out, m00); + my_out += 1; + } + + *out = (uint32_t * SMOL_RESTRICT) my_out; + *in = (const uint64_t * SMOL_RESTRICT) my_in; + +#undef ALPHA_MUL +#undef ALPHA_MASK +} + +/* PACK_SHUF_MM256_EPI8() + * + * Generates a shuffling register for packing 8bpc pixel channels in the + * provided order. The order (1, 2, 3, 4) is neutral and corresponds to + * + * _mm256_set_epi8 (13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2, + * 13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2); + */ +#define SHUF_ORDER 0x01000302U +#define SHUF_CH(n) ((char) (SHUF_ORDER >> ((4 - (n)) * 8))) +#define SHUF_QUAD_CH(q, n) (4 * (q) + SHUF_CH (n)) +#define SHUF_QUAD(q, a, b, c, d) \ + SHUF_QUAD_CH ((q), (a)), \ + SHUF_QUAD_CH ((q), (b)), \ + SHUF_QUAD_CH ((q), (c)), \ + SHUF_QUAD_CH ((q), (d)) +#define PACK_SHUF_EPI8_LANE(a, b, c, d) \ + SHUF_QUAD (3, (a), (b), (c), (d)), \ + SHUF_QUAD (2, (a), (b), (c), (d)), \ + SHUF_QUAD (1, (a), (b), (c), (d)), \ + SHUF_QUAD (0, (a), (b), (c), (d)) +#define PACK_SHUF_MM256_EPI8(a, b, c, d) _mm256_set_epi8 ( \ + PACK_SHUF_EPI8_LANE ((a), (b), (c), (d)), \ + PACK_SHUF_EPI8_LANE ((a), (b), (c), (d))) + +static void +pack_row_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + const __m256i channel_shuf = PACK_SHUF_MM256_EPI8 (1, 2, 3, 4); + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t * SMOL_RESTRICT); + + pack_8x_123a_i_to_xxxx_u_128bpp (&row_in, &row_out, row_out_max, + channel_shuf); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_123a_i_to_1234_u_128bpp (row_in); + row_in += 2; + } +} + +static void +pack_row_123a_i_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_123a_i_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +#define DEF_PACK_FROM_123A_I_TO_U_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + uint8_t alpha = (in [1] >> 8) & 0xff; \ + uint64_t t [2]; \ + unpremul_i_to_u_128bpp (in, t, alpha); \ + t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ + return PACK_FROM_1234_128BPP (t, a, b, c, d); \ +} \ + \ +static void \ +pack_row_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + const __m256i channel_shuf = PACK_SHUF_MM256_EPI8 ((a), (b), (c), (d)); \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + pack_8x_123a_i_to_xxxx_u_128bpp (&row_in, &row_out, row_out_max, \ + channel_shuf); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_123A_I_TO_U_128BPP(3, 2, 1, 4) +DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 1, 2, 3) +DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 3, 2, 1) + +/* Unpack p -> p */ + +static SMOL_INLINE uint64_t +unpack_pixel_1234_p_to_1324_p_64bpp (uint32_t p) +{ + return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); +} + +/* AVX2 has a useful instruction for this: __m256i _mm256_cvtepu8_epi16 (__m128i a); + * It results in a different channel ordering, so it'd be important to match with + * the right kind of re-pack. */ +static void +unpack_row_1234_p_to_1324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_1234_p_to_1324_p_64bpp (*(row_in++)); + } +} + +static SMOL_INLINE uint64_t +unpack_pixel_123_p_to_132a_p_64bpp (const uint8_t *p) +{ + return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) + | ((uint64_t) p [2] << 32) | 0xff; +} + +static void +unpack_row_123_p_to_132a_p_64bpp (const uint8_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_123_p_to_132a_p_64bpp (row_in); + row_in += 3; + } +} + +static SMOL_INLINE void +unpack_pixel_1234_p_to_1234_p_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = p; + out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); + out [1] = ((p64 & 0x0000ff00) << 24) | (p64 & 0x000000ff); +} + +static void +unpack_row_1234_p_to_1234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_1234_p_to_1234_p_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +static SMOL_INLINE void +unpack_pixel_123_p_to_123a_p_128bpp (const uint8_t *in, + uint64_t *out) +{ + out [0] = ((uint64_t) in [0] << 32) | in [1]; + out [1] = ((uint64_t) in [2] << 32) | 0xff; +} + +static void +unpack_row_123_p_to_123a_p_128bpp (const uint8_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_123_p_to_123a_p_128bpp (row_in, row_out); + row_in += 3; + row_out += 2; + } +} + +/* Unpack u (alpha first) -> p */ + +static SMOL_INLINE uint64_t +unpack_pixel_a234_u_to_a324_p_64bpp (uint32_t p) +{ + uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); + uint8_t alpha = p >> 24; + + return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); +} + +static void +unpack_row_a234_u_to_a324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_a234_u_to_a324_p_64bpp (*(row_in++)); + } +} + +static SMOL_INLINE void +unpack_pixel_a234_u_to_a234_p_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); + uint8_t alpha = p >> 24; + + p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); + out [0] = (p64 >> 16) & 0x000000ff000000ff; + out [1] = p64 & 0x000000ff000000ff; +} + +static void +unpack_row_a234_u_to_a234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_a234_u_to_a234_p_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* Unpack u -> i (common) */ + +static void +unpack_8x_xxxx_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT *in, + uint64_t * SMOL_RESTRICT *out, + uint64_t *out_max, + const __m256i channel_shuf) +{ + const __m256i zero = _mm256_setzero_si256 (); + const __m256i factor_shuf = _mm256_set_epi8 ( + -1, 12, -1, -1, -1, 12, -1, 12, -1, 4, -1, -1, -1, 4, -1, 4, + -1, 12, -1, -1, -1, 12, -1, 12, -1, 4, -1, -1, -1, 4, -1, 4); + const __m256i alpha_mul = _mm256_set_epi16 ( + 0, 0x100, 0, 0, 0, 0x100, 0, 0, + 0, 0x100, 0, 0, 0, 0x100, 0, 0); + const __m256i alpha_add = _mm256_set_epi16 ( + 0, 0x80, 0, 0, 0, 0x80, 0, 0, + 0, 0x80, 0, 0, 0, 0x80, 0, 0); + __m256i m0, m1, m2, m3, m4, m5, m6; + __m256i fact1, fact2; + const __m256i * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; + __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; + + SMOL_ASSUME_ALIGNED (my_out, __m256i * SMOL_RESTRICT); + + while ((ptrdiff_t) (my_out + 4) <= (ptrdiff_t) out_max) + { + m0 = _mm256_loadu_si256 (my_in); + my_in++; + + m0 = _mm256_shuffle_epi8 (m0, channel_shuf); + m0 = _mm256_permute4x64_epi64 (m0, SMOL_4X2BIT (3, 1, 2, 0)); + + m1 = _mm256_unpacklo_epi8 (m0, zero); + m2 = _mm256_unpackhi_epi8 (m0, zero); + + fact1 = _mm256_shuffle_epi8 (m1, factor_shuf); + fact2 = _mm256_shuffle_epi8 (m2, factor_shuf); + + fact1 = _mm256_or_si256 (fact1, alpha_mul); + fact2 = _mm256_or_si256 (fact2, alpha_mul); + + m1 = _mm256_mullo_epi16 (m1, fact1); + m2 = _mm256_mullo_epi16 (m2, fact2); + + m1 = _mm256_add_epi16 (m1, alpha_add); + m2 = _mm256_add_epi16 (m2, alpha_add); + + m1 = _mm256_permute4x64_epi64 (m1, SMOL_4X2BIT (3, 1, 2, 0)); + m2 = _mm256_permute4x64_epi64 (m2, SMOL_4X2BIT (3, 1, 2, 0)); + + m3 = _mm256_unpacklo_epi16 (m1, zero); + m4 = _mm256_unpackhi_epi16 (m1, zero); + m5 = _mm256_unpacklo_epi16 (m2, zero); + m6 = _mm256_unpackhi_epi16 (m2, zero); + + _mm256_store_si256 ((__m256i *) my_out, m3); + my_out++; + _mm256_store_si256 ((__m256i *) my_out, m4); + my_out++; + _mm256_store_si256 ((__m256i *) my_out, m5); + my_out++; + _mm256_store_si256 ((__m256i *) my_out, m6); + my_out++; + } + + *out = (uint64_t * SMOL_RESTRICT) my_out; + *in = (const uint32_t * SMOL_RESTRICT) my_in; +} + +/* Unpack u (alpha first) -> i */ + +static SMOL_INLINE void +unpack_pixel_a234_u_to_234a_i_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = p; + uint64_t alpha = p >> 24; + + out [0] = (((((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8)) * alpha)); + out [1] = (((((p64 & 0x000000ff) << 32) * alpha))) | (alpha << 8) | 0x80; +} + +static void +unpack_row_a234_u_to_234a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + const __m256i channel_shuf = _mm256_set_epi8 ( + 12,15,14,13, 8,11,10,9, 4,7,6,5, 0,3,2,1, + 12,15,14,13, 8,11,10,9, 4,7,6,5, 0,3,2,1); + + SMOL_ASSUME_ALIGNED (row_out, uint64_t * SMOL_RESTRICT); + + unpack_8x_xxxx_u_to_123a_i_128bpp (&row_in, &row_out, row_out_max, + channel_shuf); + + while (row_out != row_out_max) + { + unpack_pixel_a234_u_to_234a_i_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* Unpack u (alpha last) -> p */ + +static SMOL_INLINE uint64_t +unpack_pixel_123a_u_to_132a_p_64bpp (uint32_t p) +{ + uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); + uint8_t alpha = p & 0xff; + + return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); +} + +static void +unpack_row_123a_u_to_132a_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_123a_u_to_132a_p_64bpp (*(row_in++)); + } +} + +static SMOL_INLINE void +unpack_pixel_123a_u_to_123a_p_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); + uint8_t alpha = p & 0xff; + + p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); + out [0] = (p64 >> 16) & 0x000000ff000000ff; + out [1] = p64 & 0x000000ff000000ff; +} + +static void +unpack_row_123a_u_to_123a_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_123a_u_to_123a_p_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* Unpack u (alpha last) -> i */ + +static SMOL_INLINE void +unpack_pixel_123a_u_to_123a_i_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = p; + uint64_t alpha = p & 0xff; + + out [0] = (((((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16)) * alpha)); + out [1] = (((((p64 & 0x0000ff00) << 24) * alpha))) | (alpha << 8) | 0x80; +} + +static void +unpack_row_123a_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + const __m256i channel_shuf = _mm256_set_epi8 ( + 13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2, + 13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2); + + SMOL_ASSUME_ALIGNED (row_out, uint64_t * SMOL_RESTRICT); + + unpack_8x_xxxx_u_to_123a_i_128bpp (&row_in, &row_out, row_out_max, + channel_shuf); + + while (row_out != row_out_max) + { + unpack_pixel_123a_u_to_123a_i_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* --- Filter helpers --- */ + +static SMOL_INLINE const uint32_t * +inrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, + uint32_t inrow_ofs) +{ + return scale_ctx->pixels_in + scale_ctx->rowstride_in * inrow_ofs; +} + +static SMOL_INLINE uint32_t * +outrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, + uint32_t outrow_ofs) +{ + return scale_ctx->pixels_out + scale_ctx->rowstride_out * outrow_ofs; +} + +static SMOL_INLINE uint64_t +weight_pixel_64bpp (uint64_t p, + uint16_t w) +{ + return ((p * w) >> 8) & 0x00ff00ff00ff00ff; +} + +/* p and out may be the same address */ +static SMOL_INLINE void +weight_pixel_128bpp (uint64_t *p, + uint64_t *out, + uint16_t w) +{ + out [0] = ((p [0] * w) >> 8) & 0x00ffffff00ffffffULL; + out [1] = ((p [1] * w) >> 8) & 0x00ffffff00ffffffULL; +} + +static SMOL_INLINE void +sum_parts_64bpp (const uint64_t ** SMOL_RESTRICT parts_in, + uint64_t * SMOL_RESTRICT accum, + uint32_t n) +{ + const uint64_t *pp_end; + const uint64_t * SMOL_RESTRICT pp = *parts_in; + + SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t)); + + for (pp_end = pp + n; pp < pp_end; pp++) + { + *accum += *pp; + } + + *parts_in = pp; +} + +static SMOL_INLINE void +sum_parts_128bpp (const uint64_t ** SMOL_RESTRICT parts_in, + uint64_t * SMOL_RESTRICT accum, + uint32_t n) +{ + const uint64_t *pp_end; + const uint64_t * SMOL_RESTRICT pp = *parts_in; + + SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t) * 2); + + for (pp_end = pp + n * 2; pp < pp_end; ) + { + accum [0] += *(pp++); + accum [1] += *(pp++); + } + + *parts_in = pp; +} + +static SMOL_INLINE uint64_t +scale_64bpp (uint64_t accum, + uint64_t multiplier) +{ + uint64_t a, b; + + /* Average the inputs */ + a = ((accum & 0x0000ffff0000ffffULL) * multiplier + + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; + b = (((accum & 0xffff0000ffff0000ULL) >> 16) * multiplier + + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; + + /* Return pixel */ + return (a & 0x000000ff000000ffULL) | ((b & 0x000000ff000000ffULL) << 16); +} + +static SMOL_INLINE uint64_t +scale_128bpp_half (uint64_t accum, + uint64_t multiplier) +{ + uint64_t a, b; + + a = accum & 0x00000000ffffffffULL; + a = (a * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; + + b = (accum & 0xffffffff00000000ULL) >> 32; + b = (b * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; + + return (a & 0x000000000000ffffULL) + | ((b & 0x000000000000ffffULL) << 32); +} + +static SMOL_INLINE void +scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, + uint64_t multiplier, + uint64_t ** SMOL_RESTRICT row_parts_out) +{ + *(*row_parts_out)++ = scale_128bpp_half (accum [0], multiplier); + *(*row_parts_out)++ = scale_128bpp_half (accum [1], multiplier); +} + +static void +add_parts (const uint64_t * SMOL_RESTRICT parts_in, + uint64_t * SMOL_RESTRICT parts_acc_out, + uint32_t n) +{ + const uint64_t *parts_in_max = parts_in + n; + + SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); + + while (parts_in + 4 <= parts_in_max) + { + __m256i m0, m1; + + m0 = _mm256_stream_load_si256 ((const __m256i *) parts_in); + parts_in += 4; + m1 = _mm256_load_si256 ((__m256i *) parts_acc_out); + + m0 = _mm256_add_epi32 (m0, m1); + _mm256_store_si256 ((__m256i *) parts_acc_out, m0); + parts_acc_out += 4; + } + + while (parts_in < parts_in_max) + *(parts_acc_out++) += *(parts_in++); +} + +/* --- Horizontal scaling --- */ + +#define DEF_INTERP_HORIZONTAL_BILINEAR(n_halvings) \ +static void \ +interp_horizontal_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ + const uint64_t * SMOL_RESTRICT row_parts_in, \ + uint64_t * SMOL_RESTRICT row_parts_out) \ +{ \ + uint64_t p, q; \ + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ + uint64_t F; \ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; \ + int i; \ + \ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ + \ + do \ + { \ + uint64_t accum = 0; \ + \ + for (i = 0; i < (1 << (n_halvings)); i++) \ + { \ + row_parts_in += *(ofs_x++); \ + F = *(ofs_x++); \ + \ + p = *row_parts_in; \ + q = *(row_parts_in + 1); \ + \ + accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ + } \ + *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ + } \ + while (row_parts_out != row_parts_out_max); \ +} \ + \ +static void \ +interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ + const uint64_t * SMOL_RESTRICT row_parts_in, \ + uint64_t * SMOL_RESTRICT row_parts_out) \ +{ \ + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; \ + const __m128i mask128 = _mm_set_epi32 ( \ + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); \ + const __m256i zero256 = _mm256_setzero_si256 (); \ + int i; \ + \ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ + \ + while (row_parts_out != row_parts_out_max) \ + { \ + __m256i a0 = _mm256_setzero_si256 (); \ + __m128i a1; \ + \ + for (i = 0; i < (1 << ((n_halvings) - 1)); i++) \ + { \ + __m256i m0, m1; \ + __m256i factors; \ + __m128i n0, n1, n2, n3, n4, n5; \ + \ + row_parts_in += *(ofs_x++) * 2; \ + n4 = _mm_set1_epi16 (*(ofs_x++)); \ + n0 = _mm_load_si128 ((__m128i *) row_parts_in); \ + n1 = _mm_load_si128 ((__m128i *) row_parts_in + 1); \ + \ + row_parts_in += *(ofs_x++) * 2; \ + n5 = _mm_set1_epi16 (*(ofs_x++)); \ + n2 = _mm_load_si128 ((__m128i *) row_parts_in); \ + n3 = _mm_load_si128 ((__m128i *) row_parts_in + 1); \ + \ + m0 = _mm256_set_m128i (n2, n0); \ + m1 = _mm256_set_m128i (n3, n1); \ + factors = _mm256_set_m128i (n5, n4); \ + factors = _mm256_blend_epi16 (factors, zero256, 0xaa); \ + \ + m0 = LERP_SIMD256_EPI32 (m0, m1, factors); \ + a0 = _mm256_add_epi32 (a0, m0); \ + } \ + \ + a1 = _mm_add_epi32 (_mm256_extracti128_si256 (a0, 0), \ + _mm256_extracti128_si256 (a0, 1)); \ + a1 = _mm_srli_epi32 (a1, (n_halvings)); \ + a1 = _mm_and_si128 (a1, mask128); \ + _mm_store_si128 ((__m128i *) row_parts_out, a1); \ + row_parts_out += 2; \ + } \ +} + +static void +interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t p, q; + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; + uint64_t F; + uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + do + { + row_parts_in += *(ofs_x++); + F = *(ofs_x++); + + p = *row_parts_in; + q = *(row_parts_in + 1); + + *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; + } + while (row_parts_out != row_parts_out_max); +} + +static void +interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; + uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; + const __m256i mask256 = _mm256_set_epi32 ( + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); + const __m128i mask128 = _mm_set_epi32 ( + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); + const __m256i zero = _mm256_setzero_si256 (); + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + while (row_parts_out + 4 <= row_parts_out_max) + { + __m256i m0, m1; + __m256i factors; + __m128i n0, n1, n2, n3, n4, n5; + + row_parts_in += *(ofs_x++) * 2; + n4 = _mm_set1_epi16 (*(ofs_x++)); + n0 = _mm_load_si128 ((__m128i *) row_parts_in); + n1 = _mm_load_si128 ((__m128i *) row_parts_in + 1); + + row_parts_in += *(ofs_x++) * 2; + n5 = _mm_set1_epi16 (*(ofs_x++)); + n2 = _mm_load_si128 ((__m128i *) row_parts_in); + n3 = _mm_load_si128 ((__m128i *) row_parts_in + 1); + + m0 = _mm256_set_m128i (n2, n0); + m1 = _mm256_set_m128i (n3, n1); + factors = _mm256_set_m128i (n5, n4); + factors = _mm256_blend_epi16 (factors, zero, 0xaa); + + m0 = LERP_SIMD256_EPI32_AND_MASK (m0, m1, factors, mask256); + _mm256_store_si256 ((__m256i *) row_parts_out, m0); + row_parts_out += 4; + } + + /* No need for a loop here; let compiler know we're doing it at most once */ + if (row_parts_out != row_parts_out_max) + { + __m128i m0, m1; + __m128i factors; + uint32_t f; + + row_parts_in += *(ofs_x++) * 2; + f = *(ofs_x++); + + factors = _mm_set1_epi32 ((uint32_t) f); + m0 = _mm_stream_load_si128 ((__m128i *) row_parts_in); + m1 = _mm_stream_load_si128 ((__m128i *) row_parts_in + 1); + + m0 = LERP_SIMD128_EPI32_AND_MASK (m0, m1, factors, mask128); + _mm_store_si128 ((__m128i *) row_parts_out, m0); + row_parts_out += 2; + } +} + +DEF_INTERP_HORIZONTAL_BILINEAR(1) +DEF_INTERP_HORIZONTAL_BILINEAR(2) +DEF_INTERP_HORIZONTAL_BILINEAR(3) +DEF_INTERP_HORIZONTAL_BILINEAR(4) +DEF_INTERP_HORIZONTAL_BILINEAR(5) +DEF_INTERP_HORIZONTAL_BILINEAR(6) + +static void +interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t *row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + const uint64_t * SMOL_RESTRICT pp; + const uint16_t *ofs_x = scale_ctx->offsets_x; + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out - 1; + uint64_t accum = 0; + uint64_t p, q, r, s; + uint32_t n; + uint64_t F; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + pp = row_parts_in; + p = weight_pixel_64bpp (*(pp++), 256); + n = *(ofs_x++); + + while (row_parts_out != row_parts_out_max) + { + sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); + + F = *(ofs_x++); + n = *(ofs_x++); + + r = *(pp++); + s = r * F; + + q = (s >> 8) & 0x00ff00ff00ff00ffULL; + + accum += p + q; + + /* (255 * r) - (F * r) */ + p = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; + + *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); + accum = 0; + } + + /* Final box optionally features the rightmost fractional pixel */ + + sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); + + q = 0; + F = *(ofs_x); + if (F > 0) + q = weight_pixel_64bpp (*(pp), F); + + accum += p + q; + *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); +} + +static void +interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t *row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + const uint64_t * SMOL_RESTRICT pp; + const uint16_t *ofs_x = scale_ctx->offsets_x; + uint64_t *row_parts_out_max = row_parts_out + (scale_ctx->width_out - /* 2 */ 1) * 2; + uint64_t accum [2] = { 0, 0 }; + uint64_t p [2], q [2], r [2], s [2]; + uint32_t n; + uint64_t F; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + pp = row_parts_in; + + p [0] = *(pp++); + p [1] = *(pp++); + weight_pixel_128bpp (p, p, 256); + + n = *(ofs_x++); + + while (row_parts_out != row_parts_out_max) + { + sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); + + F = *(ofs_x++); + n = *(ofs_x++); + + r [0] = *(pp++); + r [1] = *(pp++); + + s [0] = r [0] * F; + s [1] = r [1] * F; + + q [0] = (s [0] >> 8) & 0x00ffffff00ffffff; + q [1] = (s [1] >> 8) & 0x00ffffff00ffffff; + + accum [0] += p [0] + q [0]; + accum [1] += p [1] + q [1]; + + p [0] = (((r [0] << 8) - r [0] - s [0]) >> 8) & 0x00ffffff00ffffff; + p [1] = (((r [1] << 8) - r [1] - s [1]) >> 8) & 0x00ffffff00ffffff; + + scale_and_store_128bpp (accum, + scale_ctx->span_mul_x, + (uint64_t ** SMOL_RESTRICT) &row_parts_out); + + accum [0] = 0; + accum [1] = 0; + } + + /* Final box optionally features the rightmost fractional pixel */ + + sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); + + q [0] = 0; + q [1] = 0; + + F = *(ofs_x); + if (F > 0) + { + q [0] = *(pp++); + q [1] = *(pp++); + weight_pixel_128bpp (q, q, F); + } + + accum [0] += p [0] + q [0]; + accum [1] += p [1] + q [1]; + + scale_and_store_128bpp (accum, + scale_ctx->span_mul_x, + (uint64_t ** SMOL_RESTRICT) &row_parts_out); +} + +static void +interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; + uint64_t part; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + part = *row_parts_in; + while (row_parts_out != row_parts_out_max) + *(row_parts_out++) = part; +} + +static void +interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + while (row_parts_out != row_parts_out_max) + { + *(row_parts_out++) = row_parts_in [0]; + *(row_parts_out++) = row_parts_in [1]; + } +} + +static void +interp_horizontal_copy_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * sizeof (uint64_t)); +} + +static void +interp_horizontal_copy_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * 2 * sizeof (uint64_t)); +} + +static void +scale_horizontal (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + const uint32_t *row_in, + uint64_t *row_parts_out) +{ + uint64_t * SMOL_RESTRICT unpacked_in; + + unpacked_in = vertical_ctx->parts_row [3]; + + /* 32-bit unpackers need 32-bit alignment */ + if ((((uintptr_t) row_in) & 3) + && scale_ctx->pixel_type_in != SMOL_PIXEL_RGB8 + && scale_ctx->pixel_type_in != SMOL_PIXEL_BGR8) + { + if (!vertical_ctx->in_aligned) + vertical_ctx->in_aligned = + smol_alloc_aligned (scale_ctx->width_in * sizeof (uint32_t), + &vertical_ctx->in_aligned_storage); + memcpy (vertical_ctx->in_aligned, row_in, scale_ctx->width_in * sizeof (uint32_t)); + row_in = vertical_ctx->in_aligned; + } + + scale_ctx->unpack_row_func (row_in, + unpacked_in, + scale_ctx->width_in); + scale_ctx->hfilter_func (scale_ctx, + unpacked_in, + row_parts_out); +} + +/* --- Vertical scaling --- */ + +static void +update_vertical_ctx_bilinear (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index) +{ + uint32_t new_in_ofs = scale_ctx->offsets_y [outrow_index * 2]; + + if (new_in_ofs == vertical_ctx->in_ofs) + return; + + if (new_in_ofs == vertical_ctx->in_ofs + 1) + { + uint64_t *t = vertical_ctx->parts_row [0]; + vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; + vertical_ctx->parts_row [1] = t; + + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), + vertical_ctx->parts_row [1]); + } + else + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, new_in_ofs), + vertical_ctx->parts_row [0]); + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), + vertical_ctx->parts_row [1]); + } + + vertical_ctx->in_ofs = new_in_ofs; +} + +static void +interp_vertical_bilinear_store_64bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t width) +{ + uint64_t *parts_out_last = parts_out + width; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + do + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; + } + while (parts_out != parts_out_last); +} + +static void +interp_vertical_bilinear_add_64bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT accum_out, + uint32_t width) +{ + uint64_t *accum_out_last = accum_out + width; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); + + do + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; + } + while (accum_out != accum_out_last); +} + +static void +interp_vertical_bilinear_store_128bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t width) +{ + uint64_t *parts_out_last = parts_out + width; + const __m256i mask = _mm256_set_epi32 ( + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); + __m256i F256; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + F256 = _mm256_set1_epi32 ((uint32_t) F); + + while (parts_out + 8 <= parts_out_last) + { + __m256i m0, m1, m2, m3; + + m0 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); + top_row_parts_in += 4; + m2 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); + top_row_parts_in += 4; + m1 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); + bottom_row_parts_in += 4; + m3 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); + bottom_row_parts_in += 4; + + m0 = _mm256_sub_epi32 (m0, m1); + m2 = _mm256_sub_epi32 (m2, m3); + m0 = _mm256_mullo_epi32 (m0, F256); + m2 = _mm256_mullo_epi32 (m2, F256); + m0 = _mm256_srli_epi32 (m0, 8); + m2 = _mm256_srli_epi32 (m2, 8); + m0 = _mm256_add_epi32 (m0, m1); + m2 = _mm256_add_epi32 (m2, m3); + m0 = _mm256_and_si256 (m0, mask); + m2 = _mm256_and_si256 (m2, mask); + + _mm256_store_si256 ((__m256i *) parts_out, m0); + parts_out += 4; + _mm256_store_si256 ((__m256i *) parts_out, m2); + parts_out += 4; + } + + while (parts_out != parts_out_last) + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; + } +} + +static void +interp_vertical_bilinear_add_128bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT accum_out, + uint32_t width) +{ + uint64_t *accum_out_last = accum_out + width; + const __m256i mask = _mm256_set_epi32 ( + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); + __m256i F256; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); + + F256 = _mm256_set1_epi32 ((uint32_t) F); + + while (accum_out + 8 <= accum_out_last) + { + __m256i m0, m1, m2, m3, o0, o1; + + m0 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); + top_row_parts_in += 4; + m2 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); + top_row_parts_in += 4; + m1 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); + bottom_row_parts_in += 4; + m3 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); + bottom_row_parts_in += 4; + o0 = _mm256_load_si256 ((const __m256i *) accum_out); + o1 = _mm256_load_si256 ((const __m256i *) accum_out + 4); + + m0 = _mm256_sub_epi32 (m0, m1); + m2 = _mm256_sub_epi32 (m2, m3); + m0 = _mm256_mullo_epi32 (m0, F256); + m2 = _mm256_mullo_epi32 (m2, F256); + m0 = _mm256_srli_epi32 (m0, 8); + m2 = _mm256_srli_epi32 (m2, 8); + m0 = _mm256_add_epi32 (m0, m1); + m2 = _mm256_add_epi32 (m2, m3); + m0 = _mm256_and_si256 (m0, mask); + m2 = _mm256_and_si256 (m2, mask); + + o0 = _mm256_add_epi32 (o0, m0); + o1 = _mm256_add_epi32 (o1, m2); + _mm256_store_si256 ((__m256i *) accum_out, o0); + accum_out += 4; + _mm256_store_si256 ((__m256i *) accum_out, o1); + accum_out += 4; + } + + while (accum_out != accum_out_last) + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; + } +} + +#define DEF_INTERP_VERTICAL_BILINEAR_FINAL(n_halvings) \ +static void \ +interp_vertical_bilinear_final_##n_halvings##h_64bpp (uint64_t F, \ + const uint64_t * SMOL_RESTRICT top_row_parts_in, \ + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ + uint64_t * SMOL_RESTRICT accum_inout, \ + uint32_t width) \ +{ \ + uint64_t *accum_inout_last = accum_inout + width; \ + \ + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ + \ + do \ + { \ + uint64_t p, q; \ + \ + p = *(top_row_parts_in++); \ + q = *(bottom_row_parts_in++); \ + \ + p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ + p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ + \ + *(accum_inout++) = p; \ + } \ + while (accum_inout != accum_inout_last); \ +} \ + \ +static void \ +interp_vertical_bilinear_final_##n_halvings##h_128bpp (uint64_t F, \ + const uint64_t * SMOL_RESTRICT top_row_parts_in, \ + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ + uint64_t * SMOL_RESTRICT accum_inout, \ + uint32_t width) \ +{ \ + uint64_t *accum_inout_last = accum_inout + width; \ + \ + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ + \ + do \ + { \ + uint64_t p, q; \ + \ + p = *(top_row_parts_in++); \ + q = *(bottom_row_parts_in++); \ + \ + p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ + p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ + \ + *(accum_inout++) = p; \ + } \ + while (accum_inout != accum_inout_last); \ +} + +#define DEF_SCALE_OUTROW_BILINEAR(n_halvings) \ +static void \ +scale_outrow_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ + SmolVerticalCtx *vertical_ctx, \ + uint32_t outrow_index, \ + uint32_t *row_out) \ +{ \ + uint32_t bilin_index = outrow_index << (n_halvings); \ + unsigned int i; \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out); \ + bilin_index++; \ + \ + for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ + { \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_add_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out); \ + bilin_index++; \ + } \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_final_##n_halvings##h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out); \ + \ + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ +} \ + \ +static void \ +scale_outrow_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ + SmolVerticalCtx *vertical_ctx, \ + uint32_t outrow_index, \ + uint32_t *row_out) \ +{ \ + uint32_t bilin_index = outrow_index << (n_halvings); \ + unsigned int i; \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out * 2); \ + bilin_index++; \ + \ + for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ + { \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_add_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out * 2); \ + bilin_index++; \ + } \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_final_##n_halvings##h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out * 2); \ + \ + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ +} + +static void +scale_outrow_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); + interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); + interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out * 2); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) + +static void +scale_outrow_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t bilin_index = outrow_index << 1; + + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + bilin_index++; + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_final_1h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t bilin_index = outrow_index << 1; + + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out * 2); + bilin_index++; + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_final_1h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out * 2); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) +DEF_SCALE_OUTROW_BILINEAR(2) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) +DEF_SCALE_OUTROW_BILINEAR(3) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) +DEF_SCALE_OUTROW_BILINEAR(4) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) +DEF_SCALE_OUTROW_BILINEAR(5) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) +DEF_SCALE_OUTROW_BILINEAR(6) + +static void +finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, + uint64_t multiplier, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t n) +{ + uint64_t *parts_out_max = parts_out + n; + + SMOL_ASSUME_ALIGNED (accums, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + while (parts_out != parts_out_max) + { + *(parts_out++) = scale_64bpp (*(accums++), multiplier); + } +} + +static void +weight_edge_row_64bpp (uint64_t *row, + uint16_t w, + uint32_t n) +{ + uint64_t *row_max = row + n; + + SMOL_ASSUME_ALIGNED (row, uint64_t *); + + while (row != row_max) + { + *row = ((*row * w) >> 8) & 0x00ff00ff00ff00ffULL; + row++; + } +} + +static void +scale_and_weight_edge_rows_box_64bpp (const uint64_t * SMOL_RESTRICT first_row, + uint64_t * SMOL_RESTRICT last_row, + uint64_t * SMOL_RESTRICT accum, + uint16_t w2, + uint32_t n) +{ + const uint64_t *first_row_max = first_row + n; + + SMOL_ASSUME_ALIGNED (first_row, const uint64_t *); + SMOL_ASSUME_ALIGNED (last_row, uint64_t *); + SMOL_ASSUME_ALIGNED (accum, uint64_t *); + + while (first_row != first_row_max) + { + uint64_t r, s, p, q; + + p = *(first_row++); + + r = *(last_row); + s = r * w2; + q = (s >> 8) & 0x00ff00ff00ff00ffULL; + /* (255 * r) - (F * r) */ + *(last_row++) = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; + + *(accum++) = p + q; + } +} + +static void +update_vertical_ctx_box_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t ofs_y, + uint32_t ofs_y_max, + uint16_t w1, + uint16_t w2) +{ + /* Old in_ofs is the previous max */ + if (ofs_y == vertical_ctx->in_ofs) + { + uint64_t *t = vertical_ctx->parts_row [0]; + vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; + vertical_ctx->parts_row [1] = t; + } + else + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [0]); + weight_edge_row_64bpp (vertical_ctx->parts_row [0], w1, scale_ctx->width_out); + } + + /* When w2 == 0, the final inrow may be out of bounds. Don't try to access it in + * that case. */ + if (w2 || ofs_y_max < scale_ctx->height_in) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y_max), + vertical_ctx->parts_row [1]); + } + else + { + memset (vertical_ctx->parts_row [1], 0, scale_ctx->width_out * sizeof (uint64_t)); + } + + vertical_ctx->in_ofs = ofs_y_max; +} + +static void +scale_outrow_box_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t ofs_y, ofs_y_max; + uint16_t w1, w2; + + /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ + + ofs_y = scale_ctx->offsets_y [outrow_index * 2]; + ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; + + /* Scale the first and last rows, weight them and store in accumulator */ + + w1 = (outrow_index == 0) ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1]; + w2 = scale_ctx->offsets_y [outrow_index * 2 + 1]; + + update_vertical_ctx_box_64bpp (scale_ctx, vertical_ctx, ofs_y, ofs_y_max, w1, w2); + + scale_and_weight_edge_rows_box_64bpp (vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + w2, + scale_ctx->width_out); + + ofs_y++; + + /* Add up whole rows */ + + while (ofs_y < ofs_y_max) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [0]); + add_parts (vertical_ctx->parts_row [0], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + + ofs_y++; + } + + finalize_vertical_64bpp (vertical_ctx->parts_row [2], + scale_ctx->span_mul_y, + vertical_ctx->parts_row [0], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, + uint64_t multiplier, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t n) +{ + uint64_t *parts_out_max = parts_out + n * 2; + + SMOL_ASSUME_ALIGNED (accums, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + while (parts_out != parts_out_max) + { + *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); + *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); + } +} + +static void +weight_row_128bpp (uint64_t *row, + uint16_t w, + uint32_t n) +{ + uint64_t *row_max = row + (n * 2); + + SMOL_ASSUME_ALIGNED (row, uint64_t *); + + while (row != row_max) + { + row [0] = ((row [0] * w) >> 8) & 0x00ffffff00ffffffULL; + row [1] = ((row [1] * w) >> 8) & 0x00ffffff00ffffffULL; + row += 2; + } +} + +static void +scale_outrow_box_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t ofs_y, ofs_y_max; + uint16_t w; + + /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ + + ofs_y = scale_ctx->offsets_y [outrow_index * 2]; + ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; + + /* Scale the first inrow and store it */ + + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [0]); + weight_row_128bpp (vertical_ctx->parts_row [0], + outrow_index == 0 ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1], + scale_ctx->width_out); + ofs_y++; + + /* Add up whole rows */ + + while (ofs_y < ofs_y_max) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [1]); + add_parts (vertical_ctx->parts_row [1], + vertical_ctx->parts_row [0], + scale_ctx->width_out * 2); + + ofs_y++; + } + + /* Final row is optional; if this is the bottommost outrow it could be out of bounds */ + + w = scale_ctx->offsets_y [outrow_index * 2 + 1]; + if (w > 0) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [1]); + weight_row_128bpp (vertical_ctx->parts_row [1], + w - 1, /* Subtract 1 to avoid overflow */ + scale_ctx->width_out); + add_parts (vertical_ctx->parts_row [1], + vertical_ctx->parts_row [0], + scale_ctx->width_out * 2); + } + + finalize_vertical_128bpp (vertical_ctx->parts_row [0], + scale_ctx->span_mul_y, + vertical_ctx->parts_row [1], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [1], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_one_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t row_index, + uint32_t *row_out) +{ + SMOL_UNUSED (row_index); + + /* Scale the row and store it */ + + if (vertical_ctx->in_ofs != 0) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, 0), + vertical_ctx->parts_row [0]); + vertical_ctx->in_ofs = 0; + } + + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_one_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t row_index, + uint32_t *row_out) +{ + SMOL_UNUSED (row_index); + + /* Scale the row and store it */ + + if (vertical_ctx->in_ofs != 0) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, 0), + vertical_ctx->parts_row [0]); + vertical_ctx->in_ofs = 0; + } + + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_copy (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t row_index, + uint32_t *row_out) +{ + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, row_index), + vertical_ctx->parts_row [0]); + + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +/* --- Conversion tables --- */ + +static const SmolConversionTable avx2_conversions = +{ +{ { + /* Conversions where accumulators must hold the sum of fewer than + * 256 pixels. This can be done in 64bpp, but 128bpp may be used + * e.g. for 16 bits per channel internally premultiplied data. */ + + /* RGBA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), + }, + /* BGRA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), + }, + /* ARGB8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), + }, + /* ABGR8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), + }, + /* RGBA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), + /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), + /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), + /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + }, + /* BGRA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), + /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), + /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), + /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + }, + /* ARGB8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), + /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), + /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), + /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + }, + /* ABGR8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), + /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), + /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), + /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + }, + /* RGB8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), + /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), + }, + /* BGR8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), + /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), + } + }, + + { + /* Conversions where accumulators must hold the sum of up to + * 65535 pixels. We need 128bpp for this. */ + + /* RGBA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), + }, + /* BGRA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), + }, + /* ARGB8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), + }, + /* ABGR8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), + }, + /* RGBA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), + /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), + /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), + /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + }, + /* BGRA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), + /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), + /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), + /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + }, + /* ARGB8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), + /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), + /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), + /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + }, + /* ABGR8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), + /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), + /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), + /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + }, + /* RGB8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), + /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), + }, + /* BGR8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), + /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), + } +} } +}; + +static const SmolImplementation avx2_implementation = +{ + { + /* Horizontal filters */ + { + /* 64bpp */ + interp_horizontal_copy_64bpp, + interp_horizontal_one_64bpp, + interp_horizontal_bilinear_0h_64bpp, + interp_horizontal_bilinear_1h_64bpp, + interp_horizontal_bilinear_2h_64bpp, + interp_horizontal_bilinear_3h_64bpp, + interp_horizontal_bilinear_4h_64bpp, + interp_horizontal_bilinear_5h_64bpp, + interp_horizontal_bilinear_6h_64bpp, + interp_horizontal_boxes_64bpp + }, + { + /* 128bpp */ + interp_horizontal_copy_128bpp, + interp_horizontal_one_128bpp, + interp_horizontal_bilinear_0h_128bpp, + interp_horizontal_bilinear_1h_128bpp, + interp_horizontal_bilinear_2h_128bpp, + interp_horizontal_bilinear_3h_128bpp, + interp_horizontal_bilinear_4h_128bpp, + interp_horizontal_bilinear_5h_128bpp, + interp_horizontal_bilinear_6h_128bpp, + interp_horizontal_boxes_128bpp + } + }, + { + /* Vertical filters */ + { + /* 64bpp */ + scale_outrow_copy, + scale_outrow_one_64bpp, + scale_outrow_bilinear_0h_64bpp, + scale_outrow_bilinear_1h_64bpp, + scale_outrow_bilinear_2h_64bpp, + scale_outrow_bilinear_3h_64bpp, + scale_outrow_bilinear_4h_64bpp, + scale_outrow_bilinear_5h_64bpp, + scale_outrow_bilinear_6h_64bpp, + scale_outrow_box_64bpp + }, + { + /* 128bpp */ + scale_outrow_copy, + scale_outrow_one_128bpp, + scale_outrow_bilinear_0h_128bpp, + scale_outrow_bilinear_1h_128bpp, + scale_outrow_bilinear_2h_128bpp, + scale_outrow_bilinear_3h_128bpp, + scale_outrow_bilinear_4h_128bpp, + scale_outrow_bilinear_5h_128bpp, + scale_outrow_bilinear_6h_128bpp, + scale_outrow_box_128bpp + } + }, + &avx2_conversions +}; + +const SmolImplementation * +_smol_get_avx2_implementation (void) +{ + return &avx2_implementation; +} diff -Nru chafa-1.2.1/chafa/internal/smolscale/smolscale.c chafa-1.12.4/chafa/internal/smolscale/smolscale.c --- chafa-1.2.1/chafa/internal/smolscale/smolscale.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/smolscale.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,3105 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright © 2019-2022 Hans Petter Jansson. See COPYING for details. */ + +#include /* assert */ +#include /* malloc, free, alloca */ +#include /* memset */ +#include +#include "smolscale-private.h" + +/* --- Premultiplication --- */ + +#define INVERTED_DIV_SHIFT 21 +#define INVERTED_DIV_ROUNDING (1U << (INVERTED_DIV_SHIFT - 1)) +#define INVERTED_DIV_ROUNDING_128BPP \ + (((uint64_t) INVERTED_DIV_ROUNDING << 32) | INVERTED_DIV_ROUNDING) + +/* This table is used to divide by an integer [1..255] using only a lookup, + * multiplication and a shift. This is faster than plain division on most + * architectures. + * + * Each entry represents the integer 2097152 (1 << 21) divided by the index + * of the entry. Consequently, + * + * (v / i) ~= (v * inverted_div_table [i] + (1 << 20)) >> 21 + * + * (1 << 20) is added for nearest rounding. It would've been nice to keep + * this table in uint16_t, but alas, we need the extra bits for sufficient + * precision. */ +static const uint32_t inverted_div_table [256] = +{ + 0,2097152,1048576, 699051, 524288, 419430, 349525, 299593, + 262144, 233017, 209715, 190650, 174763, 161319, 149797, 139810, + 131072, 123362, 116508, 110376, 104858, 99864, 95325, 91181, + 87381, 83886, 80660, 77672, 74898, 72316, 69905, 67650, + 65536, 63550, 61681, 59919, 58254, 56680, 55188, 53773, + 52429, 51150, 49932, 48771, 47663, 46603, 45590, 44620, + 43691, 42799, 41943, 41121, 40330, 39569, 38836, 38130, + 37449, 36792, 36158, 35545, 34953, 34380, 33825, 33288, + 32768, 32264, 31775, 31301, 30840, 30394, 29959, 29537, + 29127, 28728, 28340, 27962, 27594, 27236, 26887, 26546, + 26214, 25891, 25575, 25267, 24966, 24672, 24385, 24105, + 23831, 23564, 23302, 23046, 22795, 22550, 22310, 22075, + 21845, 21620, 21400, 21183, 20972, 20764, 20560, 20361, + 20165, 19973, 19784, 19600, 19418, 19240, 19065, 18893, + 18725, 18559, 18396, 18236, 18079, 17924, 17772, 17623, + 17476, 17332, 17190, 17050, 16913, 16777, 16644, 16513, + 16384, 16257, 16132, 16009, 15888, 15768, 15650, 15534, + 15420, 15308, 15197, 15087, 14980, 14873, 14769, 14665, + 14564, 14463, 14364, 14266, 14170, 14075, 13981, 13888, + 13797, 13707, 13618, 13530, 13443, 13358, 13273, 13190, + 13107, 13026, 12945, 12866, 12788, 12710, 12633, 12558, + 12483, 12409, 12336, 12264, 12193, 12122, 12053, 11984, + 11916, 11848, 11782, 11716, 11651, 11586, 11523, 11460, + 11398, 11336, 11275, 11215, 11155, 11096, 11038, 10980, + 10923, 10866, 10810, 10755, 10700, 10645, 10592, 10538, + 10486, 10434, 10382, 10331, 10280, 10230, 10180, 10131, + 10082, 10034, 9986, 9939, 9892, 9846, 9800, 9754, + 9709, 9664, 9620, 9576, 9533, 9489, 9447, 9404, + 9362, 9321, 9279, 9239, 9198, 9158, 9118, 9079, + 9039, 9001, 8962, 8924, 8886, 8849, 8812, 8775, + 8738, 8702, 8666, 8630, 8595, 8560, 8525, 8490, + 8456, 8422, 8389, 8355, 8322, 8289, 8257, 8224, +}; + +/* Masking and shifting out the results is left to the caller. In + * and out may not overlap. */ +static SMOL_INLINE void +unpremul_i_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, + uint64_t * SMOL_RESTRICT out, + uint8_t alpha) +{ + out [0] = ((in [0] * (uint64_t) inverted_div_table [alpha] + + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); + out [1] = ((in [1] * (uint64_t) inverted_div_table [alpha] + + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); +} + +static SMOL_INLINE void +unpremul_p_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, + uint64_t * SMOL_RESTRICT out, + uint8_t alpha) +{ + out [0] = (((in [0] << 8) * (uint64_t) inverted_div_table [alpha]) + >> INVERTED_DIV_SHIFT); + out [1] = (((in [1] << 8) * (uint64_t) inverted_div_table [alpha]) + >> INVERTED_DIV_SHIFT); +} + +static SMOL_INLINE uint64_t +unpremul_p_to_u_64bpp (const uint64_t in, + uint8_t alpha) +{ + uint64_t in_128bpp [2]; + uint64_t out_128bpp [2]; + + in_128bpp [0] = (in & 0x000000ff000000ff); + in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; + + unpremul_p_to_u_128bpp (in_128bpp, out_128bpp, alpha); + + return (out_128bpp [0] & 0x000000ff000000ff) + | ((out_128bpp [1] & 0x000000ff000000ff) << 16); +} + +static SMOL_INLINE uint64_t +premul_u_to_p_64bpp (const uint64_t in, + uint8_t alpha) +{ + return ((in * ((uint16_t) alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; +} + +/* --- Packing --- */ + +/* It's nice to be able to shift by a negative amount */ +#define SHIFT_S(in, s) ((s >= 0) ? (in) << (s) : (in) >> -(s)) + +#if 0 +/* Currently unused */ + +/* This is kind of bulky (~13 x86 insns), but it's about the same as using + * unions, and we don't have to worry about endianness. */ +#define PACK_FROM_1234_64BPP(in, a, b, c, d) \ + ((SHIFT_S ((in), ((a) - 1) * 16 + 8 - 32) & 0xff000000) \ + | (SHIFT_S ((in), ((b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in), ((c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in), ((d) - 1) * 16 + 8 - 56) & 0x000000ff)) +#endif + +#define PACK_FROM_1234_128BPP(in, a, b, c, d) \ + ((SHIFT_S ((in [((a) - 1) >> 1]), (((a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ + | (SHIFT_S ((in [((b) - 1) >> 1]), (((b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in [((c) - 1) >> 1]), (((c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in [((d) - 1) >> 1]), (((d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) + +#define SWAP_2_AND_3(n) ((n) == 2 ? 3 : (n) == 3 ? 2 : n) + +#define PACK_FROM_1324_64BPP(in, a, b, c, d) \ + ((SHIFT_S ((in), (SWAP_2_AND_3 (a) - 1) * 16 + 8 - 32) & 0xff000000) \ + | (SHIFT_S ((in), (SWAP_2_AND_3 (b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in), (SWAP_2_AND_3 (c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in), (SWAP_2_AND_3 (d) - 1) * 16 + 8 - 56) & 0x000000ff)) + +#if 0 +/* Currently unused */ + +#define PACK_FROM_1324_128BPP(in, a, b, c, d) \ + ((SHIFT_S ((in [(SWAP_2_AND_3 (a) - 1) >> 1]), \ + ((SWAP_2_AND_3 (a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ + | (SHIFT_S ((in [(SWAP_2_AND_3 (b) - 1) >> 1]), \ + ((SWAP_2_AND_3 (b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ + | (SHIFT_S ((in [(SWAP_2_AND_3 (c) - 1) >> 1]), \ + ((SWAP_2_AND_3 (c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ + | (SHIFT_S ((in [(SWAP_2_AND_3 (d) - 1) >> 1]), \ + ((SWAP_2_AND_3 (d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) +#endif + +/* Pack p -> p */ + +static SMOL_INLINE uint32_t +pack_pixel_1324_p_to_1234_p_64bpp (uint64_t in) +{ + return in | (in >> 24); +} + +static void +pack_row_1324_p_to_1234_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); + } +} + +static void +pack_row_132a_p_to_123_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + /* FIXME: Would be faster to shift directly */ + uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_132a_p_to_321_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + /* FIXME: Would be faster to shift directly */ + uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +#define DEF_PACK_FROM_1324_P_TO_P_64BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (uint64_t in) \ +{ \ + return PACK_FROM_1324_64BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_1324_p_to_##a##b##c##d##_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + *(row_out++) = pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (*(row_in++)); \ +} + +DEF_PACK_FROM_1324_P_TO_P_64BPP (1, 4, 3, 2) +DEF_PACK_FROM_1324_P_TO_P_64BPP (2, 3, 4, 1) +DEF_PACK_FROM_1324_P_TO_P_64BPP (3, 2, 1, 4) +DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 1, 2, 3) +DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 3, 2, 1) + +static SMOL_INLINE uint32_t +pack_pixel_1234_p_to_1234_p_128bpp (const uint64_t *in) +{ + /* FIXME: Are masks needed? */ + return ((in [0] >> 8) & 0xff000000) + | ((in [0] << 16) & 0x00ff0000) + | ((in [1] >> 24) & 0x0000ff00) + | (in [1] & 0x000000ff); +} + +static void +pack_row_1234_p_to_1234_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_1234_p_to_1234_p_128bpp (row_in); + row_in += 2; + } +} + +#define DEF_PACK_FROM_1234_P_TO_P_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + return PACK_FROM_1234_128BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_1234_P_TO_P_128BPP (1, 4, 3, 2) +DEF_PACK_FROM_1234_P_TO_P_128BPP (2, 3, 4, 1) +DEF_PACK_FROM_1234_P_TO_P_128BPP (3, 2, 1, 4) +DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 1, 2, 3) +DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 3, 2, 1) + +static void +pack_row_123a_p_to_123_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = *row_in >> 32; + *(row_out++) = *(row_in++); + *(row_out++) = *(row_in++) >> 32; + } +} + +static void +pack_row_123a_p_to_321_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = row_in [1] >> 32; + *(row_out++) = row_in [0]; + *(row_out++) = row_in [0] >> 32; + row_in += 2; + } +} + +/* Pack p (alpha last) -> u */ + +static SMOL_INLINE uint32_t +pack_pixel_132a_p_to_1234_u_64bpp (uint64_t in) +{ + uint8_t alpha = in; + in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; + return in | (in >> 24); +} + +static void +pack_row_132a_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); + } +} + +static void +pack_row_132a_p_to_123_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_132a_p_to_321_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +#define DEF_PACK_FROM_132A_P_TO_U_64BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ +{ \ + uint8_t alpha = in; \ + in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; \ + return PACK_FROM_1324_64BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_132a_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + *(row_out++) = pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ +} + +DEF_PACK_FROM_132A_P_TO_U_64BPP (3, 2, 1, 4) +DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 1, 2, 3) +DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 3, 2, 1) + +#define DEF_PACK_FROM_123A_P_TO_U_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + uint64_t t [2]; \ + uint8_t alpha = in [1]; \ + unpremul_p_to_u_128bpp (in, t, alpha); \ + t [1] = (t [1] & 0xffffffff00000000) | alpha; \ + return PACK_FROM_1234_128BPP (t, a, b, c, d); \ +} \ + \ +static void \ +pack_row_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_123A_P_TO_U_128BPP (1, 2, 3, 4) +DEF_PACK_FROM_123A_P_TO_U_128BPP (3, 2, 1, 4) +DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 1, 2, 3) +DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 3, 2, 1) + +static void +pack_row_123a_p_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_123a_p_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +/* Pack p (alpha first) -> u */ + +static SMOL_INLINE uint32_t +pack_pixel_a324_p_to_1234_u_64bpp (uint64_t in) +{ + uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ + in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); + return in | (in >> 24); +} + +static void +pack_row_a324_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); + } +} + +static void +pack_row_a324_p_to_234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + *(row_out++) = p; + } +} + +static void +pack_row_a324_p_to_432_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); + *(row_out++) = p; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + } +} + +#define DEF_PACK_FROM_A324_P_TO_U_64BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ +{ \ + uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ \ + in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); \ + return PACK_FROM_1324_64BPP (in, a, b, c, d); \ +} \ + \ +static void \ +pack_row_a324_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + *(row_out++) = pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ +} + +DEF_PACK_FROM_A324_P_TO_U_64BPP (1, 4, 3, 2) +DEF_PACK_FROM_A324_P_TO_U_64BPP (2, 3, 4, 1) +DEF_PACK_FROM_A324_P_TO_U_64BPP (4, 3, 2, 1) + +#define DEF_PACK_FROM_A234_P_TO_U_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + uint64_t t [2]; \ + uint8_t alpha = in [0] >> 32; \ + unpremul_p_to_u_128bpp (in, t, alpha); \ + t [0] = (t [0] & 0x00000000ffffffff) | ((uint64_t) alpha << 32); \ + return PACK_FROM_1234_128BPP (t, a, b, c, d); \ +} \ + \ +static void \ +pack_row_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 2, 3, 4) +DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 4, 3, 2) +DEF_PACK_FROM_A234_P_TO_U_128BPP (2, 3, 4, 1) +DEF_PACK_FROM_A234_P_TO_U_128BPP (4, 3, 2, 1) + +static void +pack_row_a234_p_to_234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + *(row_out++) = p; + } +} + +static void +pack_row_a234_p_to_432_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + } +} + +/* Pack i (alpha last) to u */ + +static SMOL_INLINE uint32_t +pack_pixel_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT in) +{ + uint8_t alpha = (in [1] >> 8) & 0xff; + uint64_t t [2]; + + unpremul_i_to_u_128bpp (in, t, alpha); + + return ((t [0] >> 8) & 0xff000000) + | ((t [0] << 16) & 0x00ff0000) + | ((t [1] >> 24) & 0x0000ff00) + | alpha; +} + +static void +pack_row_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint32_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint32_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = pack_pixel_123a_i_to_1234_u_128bpp (row_in); + row_in += 2; + } +} + +static void +pack_row_123a_i_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 24; + *(row_out++) = p >> 16; + *(row_out++) = p >> 8; + } +} + +static void +pack_row_123a_i_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, + uint8_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint8_t *row_out_max = row_out + n_pixels * 3; + + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); + + while (row_out != row_out_max) + { + uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); + row_in += 2; + *(row_out++) = p >> 8; + *(row_out++) = p >> 16; + *(row_out++) = p >> 24; + } +} + +#define DEF_PACK_FROM_123A_I_TO_U_128BPP(a, b, c, d) \ +static SMOL_INLINE uint32_t \ +pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ +{ \ + uint8_t alpha = (in [1] >> 8) & 0xff; \ + uint64_t t [2]; \ + unpremul_i_to_u_128bpp (in, t, alpha); \ + t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ + return PACK_FROM_1234_128BPP (t, a, b, c, d); \ +} \ + \ +static void \ +pack_row_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ + uint32_t * SMOL_RESTRICT row_out, \ + uint32_t n_pixels) \ +{ \ + uint32_t *row_out_max = row_out + n_pixels; \ + SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ + while (row_out != row_out_max) \ + { \ + *(row_out++) = pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (row_in); \ + row_in += 2; \ + } \ +} + +DEF_PACK_FROM_123A_I_TO_U_128BPP(3, 2, 1, 4) +DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 1, 2, 3) +DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 3, 2, 1) + +/* Unpack p -> p */ + +static SMOL_INLINE uint64_t +unpack_pixel_1234_p_to_1324_p_64bpp (uint32_t p) +{ + return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); +} + +/* AVX2 has a useful instruction for this: __m256i _mm256_cvtepu8_epi16 (__m128i a); + * It results in a different channel ordering, so it'd be important to match with + * the right kind of re-pack. */ +static void +unpack_row_1234_p_to_1324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_1234_p_to_1324_p_64bpp (*(row_in++)); + } +} + +static SMOL_INLINE uint64_t +unpack_pixel_123_p_to_132a_p_64bpp (const uint8_t *p) +{ + return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) + | ((uint64_t) p [2] << 32) | 0xff; +} + +static void +unpack_row_123_p_to_132a_p_64bpp (const uint8_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_123_p_to_132a_p_64bpp (row_in); + row_in += 3; + } +} + +static SMOL_INLINE void +unpack_pixel_1234_p_to_1234_p_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = p; + out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); + out [1] = ((p64 & 0x0000ff00) << 24) | (p64 & 0x000000ff); +} + +static void +unpack_row_1234_p_to_1234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_1234_p_to_1234_p_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +static SMOL_INLINE void +unpack_pixel_123_p_to_123a_p_128bpp (const uint8_t *in, + uint64_t *out) +{ + out [0] = ((uint64_t) in [0] << 32) | in [1]; + out [1] = ((uint64_t) in [2] << 32) | 0xff; +} + +static void +unpack_row_123_p_to_123a_p_128bpp (const uint8_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_123_p_to_123a_p_128bpp (row_in, row_out); + row_in += 3; + row_out += 2; + } +} + +/* Unpack u (alpha first) -> p */ + +static SMOL_INLINE uint64_t +unpack_pixel_a234_u_to_a324_p_64bpp (uint32_t p) +{ + uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); + uint8_t alpha = p >> 24; + + return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); +} + +static void +unpack_row_a234_u_to_a324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_a234_u_to_a324_p_64bpp (*(row_in++)); + } +} + +static SMOL_INLINE void +unpack_pixel_a234_u_to_a234_p_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); + uint8_t alpha = p >> 24; + + p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); + out [0] = (p64 >> 16) & 0x000000ff000000ff; + out [1] = p64 & 0x000000ff000000ff; +} + +static void +unpack_row_a234_u_to_a234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_a234_u_to_a234_p_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* Unpack u (alpha first) -> i */ + +static SMOL_INLINE void +unpack_pixel_a234_u_to_234a_i_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = p; + uint64_t alpha = p >> 24; + + out [0] = (((((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8)) * alpha)); + out [1] = (((((p64 & 0x000000ff) << 32) * alpha))) | (alpha << 8) | 0x80; +} + +static void +unpack_row_a234_u_to_234a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_a234_u_to_234a_i_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* Unpack u (alpha last) -> p */ + +static SMOL_INLINE uint64_t +unpack_pixel_123a_u_to_132a_p_64bpp (uint32_t p) +{ + uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); + uint8_t alpha = p & 0xff; + + return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); +} + +static void +unpack_row_123a_u_to_132a_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + *(row_out++) = unpack_pixel_123a_u_to_132a_p_64bpp (*(row_in++)); + } +} + +static SMOL_INLINE void +unpack_pixel_123a_u_to_123a_p_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); + uint8_t alpha = p & 0xff; + + p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); + out [0] = (p64 >> 16) & 0x000000ff000000ff; + out [1] = p64 & 0x000000ff000000ff; +} + +static void +unpack_row_123a_u_to_123a_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_123a_u_to_123a_p_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* Unpack u (alpha last) -> i */ + +static SMOL_INLINE void +unpack_pixel_123a_u_to_123a_i_128bpp (uint32_t p, + uint64_t *out) +{ + uint64_t p64 = p; + uint64_t alpha = p & 0xff; + + out [0] = (((((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16)) * alpha)); + out [1] = (((((p64 & 0x0000ff00) << 24) * alpha))) | (alpha << 8) | 0x80; +} + +static void +unpack_row_123a_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, + uint64_t * SMOL_RESTRICT row_out, + uint32_t n_pixels) +{ + uint64_t *row_out_max = row_out + n_pixels * 2; + + SMOL_ASSUME_ALIGNED (row_out, uint64_t *); + + while (row_out != row_out_max) + { + unpack_pixel_123a_u_to_123a_i_128bpp (*(row_in++), row_out); + row_out += 2; + } +} + +/* --- Filter helpers --- */ + +static SMOL_INLINE const uint32_t * +inrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, + uint32_t inrow_ofs) +{ + return scale_ctx->pixels_in + scale_ctx->rowstride_in * inrow_ofs; +} + +static SMOL_INLINE uint32_t * +outrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, + uint32_t outrow_ofs) +{ + return scale_ctx->pixels_out + scale_ctx->rowstride_out * outrow_ofs; +} + +static SMOL_INLINE uint64_t +weight_pixel_64bpp (uint64_t p, + uint16_t w) +{ + return ((p * w) >> 8) & 0x00ff00ff00ff00ff; +} + +/* p and out may be the same address */ +static SMOL_INLINE void +weight_pixel_128bpp (uint64_t *p, + uint64_t *out, + uint16_t w) +{ + out [0] = ((p [0] * w) >> 8) & 0x00ffffff00ffffffULL; + out [1] = ((p [1] * w) >> 8) & 0x00ffffff00ffffffULL; +} + +static SMOL_INLINE void +sum_parts_64bpp (const uint64_t ** SMOL_RESTRICT parts_in, + uint64_t * SMOL_RESTRICT accum, + uint32_t n) +{ + const uint64_t *pp_end; + const uint64_t * SMOL_RESTRICT pp = *parts_in; + + SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t)); + + for (pp_end = pp + n; pp < pp_end; pp++) + { + *accum += *pp; + } + + *parts_in = pp; +} + +static SMOL_INLINE void +sum_parts_128bpp (const uint64_t ** SMOL_RESTRICT parts_in, + uint64_t * SMOL_RESTRICT accum, + uint32_t n) +{ + const uint64_t *pp_end; + const uint64_t * SMOL_RESTRICT pp = *parts_in; + + SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t) * 2); + + for (pp_end = pp + n * 2; pp < pp_end; ) + { + accum [0] += *(pp++); + accum [1] += *(pp++); + } + + *parts_in = pp; +} + +static SMOL_INLINE uint64_t +scale_64bpp (uint64_t accum, + uint64_t multiplier) +{ + uint64_t a, b; + + /* Average the inputs */ + a = ((accum & 0x0000ffff0000ffffULL) * multiplier + + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; + b = (((accum & 0xffff0000ffff0000ULL) >> 16) * multiplier + + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; + + /* Return pixel */ + return (a & 0x000000ff000000ffULL) | ((b & 0x000000ff000000ffULL) << 16); +} + +static SMOL_INLINE uint64_t +scale_128bpp_half (uint64_t accum, + uint64_t multiplier) +{ + uint64_t a, b; + + a = accum & 0x00000000ffffffffULL; + a = (a * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; + + b = (accum & 0xffffffff00000000ULL) >> 32; + b = (b * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; + + return (a & 0x000000000000ffffULL) + | ((b & 0x000000000000ffffULL) << 32); +} + +static SMOL_INLINE void +scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, + uint64_t multiplier, + uint64_t ** SMOL_RESTRICT row_parts_out) +{ + *(*row_parts_out)++ = scale_128bpp_half (accum [0], multiplier); + *(*row_parts_out)++ = scale_128bpp_half (accum [1], multiplier); +} + +static void +add_parts (const uint64_t * SMOL_RESTRICT parts_in, + uint64_t * SMOL_RESTRICT parts_acc_out, + uint32_t n) +{ + const uint64_t *parts_in_max = parts_in + n; + + SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); + + while (parts_in < parts_in_max) + *(parts_acc_out++) += *(parts_in++); +} + +/* --- Precalculation --- */ + +static void +pick_filter_params (uint32_t dim_in, + uint32_t dim_out, + uint32_t *halvings_out, + uint32_t *dim_bilin_out, + SmolFilterType *filter_out, + SmolStorageType *storage_out) +{ + *dim_bilin_out = dim_out; + *storage_out = SMOL_STORAGE_64BPP; + + /* The box algorithms are only sufficiently precise when + * dim_in > dim_out * 5. box_64bpp typically starts outperforming + * bilinear+halving at dim_in > dim_out * 8. */ + + if (dim_in > dim_out * 255) + { + *filter_out = SMOL_FILTER_BOX; + *storage_out = SMOL_STORAGE_128BPP; + } + else if (dim_in > dim_out * 8) + { + *filter_out = SMOL_FILTER_BOX; + } + else if (dim_in == 1) + { + *filter_out = SMOL_FILTER_ONE; + } + else if (dim_in == dim_out) + { + *filter_out = SMOL_FILTER_COPY; + } + else + { + uint32_t n_halvings = 0; + uint32_t d = dim_out; + + for (;;) + { + d *= 2; + if (d >= dim_in) + break; + n_halvings++; + } + + dim_out <<= n_halvings; + *dim_bilin_out = dim_out; + *filter_out = SMOL_FILTER_BILINEAR_0H + n_halvings; + *halvings_out = n_halvings; + } +} + +static void +precalc_bilinear_array (uint16_t *array, + uint32_t dim_in, + uint32_t dim_out, + unsigned int make_absolute_offsets) +{ + uint64_t ofs_stepF, fracF, frac_stepF; + uint16_t *pu16 = array; + uint16_t last_ofs = 0; + + if (dim_in > dim_out) + { + /* Minification */ + frac_stepF = ofs_stepF = (dim_in * SMOL_BILIN_MULTIPLIER) / dim_out; + fracF = (frac_stepF - SMOL_BILIN_MULTIPLIER) / 2; + } + else + { + /* Magnification */ + frac_stepF = ofs_stepF = ((dim_in - 1) * SMOL_BILIN_MULTIPLIER) / (dim_out > 1 ? (dim_out - 1) : 1); + fracF = 0; + } + + do + { + uint16_t ofs = fracF / SMOL_BILIN_MULTIPLIER; + + /* We sample ofs and its neighbor -- prevent out of bounds access + * for the latter. */ + if (ofs >= dim_in - 1) + break; + + *(pu16++) = make_absolute_offsets ? ofs : ofs - last_ofs; + *(pu16++) = SMOL_SMALL_MUL - ((fracF / (SMOL_BILIN_MULTIPLIER / SMOL_SMALL_MUL)) % SMOL_SMALL_MUL); + fracF += frac_stepF; + + last_ofs = ofs; + } + while (--dim_out); + + /* Instead of going out of bounds, sample the final pair of pixels with a 100% + * bias towards the last pixel */ + while (dim_out) + { + *(pu16++) = make_absolute_offsets ? dim_in - 2 : (dim_in - 2) - last_ofs; + *(pu16++) = 0; + dim_out--; + + last_ofs = dim_in - 2; + } +} + +static void +precalc_boxes_array (uint16_t *array, + uint32_t *span_mul, + uint32_t dim_in, + uint32_t dim_out, + unsigned int make_absolute_offsets) +{ + uint64_t fracF, frac_stepF; + uint16_t *pu16 = array; + uint16_t ofs, next_ofs; + uint64_t f; + uint64_t stride; + uint64_t a, b; + + frac_stepF = ((uint64_t) dim_in * SMOL_BIG_MUL) / (uint64_t) dim_out; + fracF = 0; + ofs = 0; + + stride = frac_stepF / (uint64_t) SMOL_BIG_MUL; + f = (frac_stepF / SMOL_SMALL_MUL) % SMOL_SMALL_MUL; + + a = (SMOL_BOXES_MULTIPLIER * 255); + b = ((stride * 255) + ((f * 255) / 256)); + *span_mul = (a + (b / 2)) / b; + + do + { + fracF += frac_stepF; + next_ofs = (uint64_t) fracF / ((uint64_t) SMOL_BIG_MUL); + + /* Prevent out of bounds access */ + if (ofs >= dim_in - 1) + break; + + if (next_ofs > dim_in) + { + next_ofs = dim_in; + if (next_ofs <= ofs) + break; + } + + stride = next_ofs - ofs - 1; + f = (fracF / SMOL_SMALL_MUL) % SMOL_SMALL_MUL; + + /* Fraction is the other way around, since left pixel of each span + * comes first, and it's on the right side of the fractional sample. */ + *(pu16++) = make_absolute_offsets ? ofs : stride; + *(pu16++) = f; + + ofs = next_ofs; + } + while (--dim_out); + + /* Instead of going out of bounds, sample the final pair of pixels with a 100% + * bias towards the last pixel */ + while (dim_out) + { + *(pu16++) = make_absolute_offsets ? ofs : 0; + *(pu16++) = 0; + dim_out--; + } + + *(pu16++) = make_absolute_offsets ? ofs : 0; + *(pu16++) = 0; +} + +/* --- Horizontal scaling --- */ + +#define DEF_INTERP_HORIZONTAL_BILINEAR(n_halvings) \ +static void \ +interp_horizontal_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ + const uint64_t * SMOL_RESTRICT row_parts_in, \ + uint64_t * SMOL_RESTRICT row_parts_out) \ +{ \ + uint64_t p, q; \ + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ + uint64_t F; \ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; \ + int i; \ + \ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ + \ + do \ + { \ + uint64_t accum = 0; \ + \ + for (i = 0; i < (1 << (n_halvings)); i++) \ + { \ + row_parts_in += *(ofs_x++); \ + F = *(ofs_x++); \ + \ + p = *row_parts_in; \ + q = *(row_parts_in + 1); \ + \ + accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ + } \ + *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ + } \ + while (row_parts_out != row_parts_out_max); \ +} \ + \ +static void \ +interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ + const uint64_t * SMOL_RESTRICT row_parts_in, \ + uint64_t * SMOL_RESTRICT row_parts_out) \ +{ \ + uint64_t p, q; \ + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ + uint64_t F; \ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; \ + int i; \ + \ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ + \ + do \ + { \ + uint64_t accum [2] = { 0 }; \ + \ + for (i = 0; i < (1 << (n_halvings)); i++) \ + { \ + row_parts_in += *(ofs_x++) * 2; \ + F = *(ofs_x++); \ + \ + p = row_parts_in [0]; \ + q = row_parts_in [2]; \ + \ + accum [0] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ + \ + p = row_parts_in [1]; \ + q = row_parts_in [3]; \ + \ + accum [1] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ + } \ + *(row_parts_out++) = ((accum [0]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ + *(row_parts_out++) = ((accum [1]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ + } \ + while (row_parts_out != row_parts_out_max); \ +} + +static void +interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t p, q; + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; + uint64_t F; + uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + do + { + row_parts_in += *(ofs_x++); + F = *(ofs_x++); + + p = *row_parts_in; + q = *(row_parts_in + 1); + + *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; + } + while (row_parts_out != row_parts_out_max); +} + +static void +interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t p, q; + const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; + uint64_t F; + uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + do + { + row_parts_in += *(ofs_x++) * 2; + F = *(ofs_x++); + + p = row_parts_in [0]; + q = row_parts_in [2]; + + *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; + + p = row_parts_in [1]; + q = row_parts_in [3]; + + *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; + } + while (row_parts_out != row_parts_out_max); +} + +DEF_INTERP_HORIZONTAL_BILINEAR(1) +DEF_INTERP_HORIZONTAL_BILINEAR(2) +DEF_INTERP_HORIZONTAL_BILINEAR(3) +DEF_INTERP_HORIZONTAL_BILINEAR(4) +DEF_INTERP_HORIZONTAL_BILINEAR(5) +DEF_INTERP_HORIZONTAL_BILINEAR(6) + +static void +interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t *row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + const uint64_t * SMOL_RESTRICT pp; + const uint16_t *ofs_x = scale_ctx->offsets_x; + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out - 1; + uint64_t accum = 0; + uint64_t p, q, r, s; + uint32_t n; + uint64_t F; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + pp = row_parts_in; + p = weight_pixel_64bpp (*(pp++), 256); + n = *(ofs_x++); + + while (row_parts_out != row_parts_out_max) + { + sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); + + F = *(ofs_x++); + n = *(ofs_x++); + + r = *(pp++); + s = r * F; + + q = (s >> 8) & 0x00ff00ff00ff00ffULL; + + accum += p + q; + + /* (255 * r) - (F * r) */ + p = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; + + *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); + accum = 0; + } + + /* Final box optionally features the rightmost fractional pixel */ + + sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); + + q = 0; + F = *(ofs_x); + if (F > 0) + q = weight_pixel_64bpp (*(pp), F); + + accum += p + q; + *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); +} + +static void +interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t *row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + const uint64_t * SMOL_RESTRICT pp; + const uint16_t *ofs_x = scale_ctx->offsets_x; + uint64_t *row_parts_out_max = row_parts_out + (scale_ctx->width_out - /* 2 */ 1) * 2; + uint64_t accum [2] = { 0, 0 }; + uint64_t p [2], q [2], r [2], s [2]; + uint32_t n; + uint64_t F; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + pp = row_parts_in; + + p [0] = *(pp++); + p [1] = *(pp++); + weight_pixel_128bpp (p, p, 256); + + n = *(ofs_x++); + + while (row_parts_out != row_parts_out_max) + { + sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); + + F = *(ofs_x++); + n = *(ofs_x++); + + r [0] = *(pp++); + r [1] = *(pp++); + + s [0] = r [0] * F; + s [1] = r [1] * F; + + q [0] = (s [0] >> 8) & 0x00ffffff00ffffff; + q [1] = (s [1] >> 8) & 0x00ffffff00ffffff; + + accum [0] += p [0] + q [0]; + accum [1] += p [1] + q [1]; + + p [0] = (((r [0] << 8) - r [0] - s [0]) >> 8) & 0x00ffffff00ffffff; + p [1] = (((r [1] << 8) - r [1] - s [1]) >> 8) & 0x00ffffff00ffffff; + + scale_and_store_128bpp (accum, + scale_ctx->span_mul_x, + (uint64_t ** SMOL_RESTRICT) &row_parts_out); + + accum [0] = 0; + accum [1] = 0; + } + + /* Final box optionally features the rightmost fractional pixel */ + + sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); + + q [0] = 0; + q [1] = 0; + + F = *(ofs_x); + if (F > 0) + { + q [0] = *(pp++); + q [1] = *(pp++); + weight_pixel_128bpp (q, q, F); + } + + accum [0] += p [0] + q [0]; + accum [1] += p [1] + q [1]; + + scale_and_store_128bpp (accum, + scale_ctx->span_mul_x, + (uint64_t ** SMOL_RESTRICT) &row_parts_out); +} + +static void +interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; + uint64_t part; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + part = *row_parts_in; + while (row_parts_out != row_parts_out_max) + *(row_parts_out++) = part; +} + +static void +interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; + + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + while (row_parts_out != row_parts_out_max) + { + *(row_parts_out++) = row_parts_in [0]; + *(row_parts_out++) = row_parts_in [1]; + } +} + +static void +interp_horizontal_copy_64bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * sizeof (uint64_t)); +} + +static void +interp_horizontal_copy_128bpp (const SmolScaleCtx *scale_ctx, + const uint64_t * SMOL_RESTRICT row_parts_in, + uint64_t * SMOL_RESTRICT row_parts_out) +{ + SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); + + memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * 2 * sizeof (uint64_t)); +} + +static void +scale_horizontal (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + const uint32_t *row_in, + uint64_t *row_parts_out) +{ + uint64_t * SMOL_RESTRICT unpacked_in; + + unpacked_in = vertical_ctx->parts_row [3]; + + /* 32-bit unpackers need 32-bit alignment */ + if ((((uintptr_t) row_in) & 3) + && scale_ctx->pixel_type_in != SMOL_PIXEL_RGB8 + && scale_ctx->pixel_type_in != SMOL_PIXEL_BGR8) + { + if (!vertical_ctx->in_aligned) + vertical_ctx->in_aligned = + smol_alloc_aligned (scale_ctx->width_in * sizeof (uint32_t), + &vertical_ctx->in_aligned_storage); + memcpy (vertical_ctx->in_aligned, row_in, scale_ctx->width_in * sizeof (uint32_t)); + row_in = vertical_ctx->in_aligned; + } + + scale_ctx->unpack_row_func (row_in, + unpacked_in, + scale_ctx->width_in); + scale_ctx->hfilter_func (scale_ctx, + unpacked_in, + row_parts_out); +} + +/* --- Vertical scaling --- */ + +static void +update_vertical_ctx_bilinear (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index) +{ + uint32_t new_in_ofs = scale_ctx->offsets_y [outrow_index * 2]; + + if (new_in_ofs == vertical_ctx->in_ofs) + return; + + if (new_in_ofs == vertical_ctx->in_ofs + 1) + { + uint64_t *t = vertical_ctx->parts_row [0]; + vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; + vertical_ctx->parts_row [1] = t; + + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), + vertical_ctx->parts_row [1]); + } + else + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, new_in_ofs), + vertical_ctx->parts_row [0]); + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), + vertical_ctx->parts_row [1]); + } + + vertical_ctx->in_ofs = new_in_ofs; +} + +static void +interp_vertical_bilinear_store_64bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t width) +{ + uint64_t *parts_out_last = parts_out + width; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + do + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; + } + while (parts_out != parts_out_last); +} + +static void +interp_vertical_bilinear_add_64bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT accum_out, + uint32_t width) +{ + uint64_t *accum_out_last = accum_out + width; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); + + do + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; + } + while (accum_out != accum_out_last); +} + +static void +interp_vertical_bilinear_store_128bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t width) +{ + uint64_t *parts_out_last = parts_out + width; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + do + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; + } + while (parts_out != parts_out_last); +} + +static void +interp_vertical_bilinear_add_128bpp (uint64_t F, + const uint64_t * SMOL_RESTRICT top_row_parts_in, + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, + uint64_t * SMOL_RESTRICT accum_out, + uint32_t width) +{ + uint64_t *accum_out_last = accum_out + width; + + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); + SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); + + do + { + uint64_t p, q; + + p = *(top_row_parts_in++); + q = *(bottom_row_parts_in++); + + *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; + } + while (accum_out != accum_out_last); +} + +#define DEF_INTERP_VERTICAL_BILINEAR_FINAL(n_halvings) \ +static void \ +interp_vertical_bilinear_final_##n_halvings##h_64bpp (uint64_t F, \ + const uint64_t * SMOL_RESTRICT top_row_parts_in, \ + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ + uint64_t * SMOL_RESTRICT accum_inout, \ + uint32_t width) \ +{ \ + uint64_t *accum_inout_last = accum_inout + width; \ + \ + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ + \ + do \ + { \ + uint64_t p, q; \ + \ + p = *(top_row_parts_in++); \ + q = *(bottom_row_parts_in++); \ + \ + p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ + p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ + \ + *(accum_inout++) = p; \ + } \ + while (accum_inout != accum_inout_last); \ +} \ + \ +static void \ +interp_vertical_bilinear_final_##n_halvings##h_128bpp (uint64_t F, \ + const uint64_t * SMOL_RESTRICT top_row_parts_in, \ + const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ + uint64_t * SMOL_RESTRICT accum_inout, \ + uint32_t width) \ +{ \ + uint64_t *accum_inout_last = accum_inout + width; \ + \ + SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ + SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ + \ + do \ + { \ + uint64_t p, q; \ + \ + p = *(top_row_parts_in++); \ + q = *(bottom_row_parts_in++); \ + \ + p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ + p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ + \ + *(accum_inout++) = p; \ + } \ + while (accum_inout != accum_inout_last); \ +} + +#define DEF_SCALE_OUTROW_BILINEAR(n_halvings) \ +static void \ +scale_outrow_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ + SmolVerticalCtx *vertical_ctx, \ + uint32_t outrow_index, \ + uint32_t *row_out) \ +{ \ + uint32_t bilin_index = outrow_index << (n_halvings); \ + unsigned int i; \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out); \ + bilin_index++; \ + \ + for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ + { \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_add_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out); \ + bilin_index++; \ + } \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_final_##n_halvings##h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out); \ + \ + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ +} \ + \ +static void \ +scale_outrow_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ + SmolVerticalCtx *vertical_ctx, \ + uint32_t outrow_index, \ + uint32_t *row_out) \ +{ \ + uint32_t bilin_index = outrow_index << (n_halvings); \ + unsigned int i; \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out * 2); \ + bilin_index++; \ + \ + for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ + { \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_add_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out * 2); \ + bilin_index++; \ + } \ + \ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ + interp_vertical_bilinear_final_##n_halvings##h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ + vertical_ctx->parts_row [0], \ + vertical_ctx->parts_row [1], \ + vertical_ctx->parts_row [2], \ + scale_ctx->width_out * 2); \ + \ + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ +} + +static void +scale_outrow_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); + interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); + interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out * 2); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) + +static void +scale_outrow_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t bilin_index = outrow_index << 1; + + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + bilin_index++; + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_final_1h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t bilin_index = outrow_index << 1; + + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out * 2); + bilin_index++; + update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); + interp_vertical_bilinear_final_1h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], + vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + scale_ctx->width_out * 2); + scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); +} + +DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) +DEF_SCALE_OUTROW_BILINEAR(2) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) +DEF_SCALE_OUTROW_BILINEAR(3) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) +DEF_SCALE_OUTROW_BILINEAR(4) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) +DEF_SCALE_OUTROW_BILINEAR(5) +DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) +DEF_SCALE_OUTROW_BILINEAR(6) + +static void +finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, + uint64_t multiplier, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t n) +{ + uint64_t *parts_out_max = parts_out + n; + + SMOL_ASSUME_ALIGNED (accums, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + while (parts_out != parts_out_max) + { + *(parts_out++) = scale_64bpp (*(accums++), multiplier); + } +} + +static void +weight_edge_row_64bpp (uint64_t *row, + uint16_t w, + uint32_t n) +{ + uint64_t *row_max = row + n; + + SMOL_ASSUME_ALIGNED (row, uint64_t *); + + while (row != row_max) + { + *row = ((*row * w) >> 8) & 0x00ff00ff00ff00ffULL; + row++; + } +} + +static void +scale_and_weight_edge_rows_box_64bpp (const uint64_t * SMOL_RESTRICT first_row, + uint64_t * SMOL_RESTRICT last_row, + uint64_t * SMOL_RESTRICT accum, + uint16_t w2, + uint32_t n) +{ + const uint64_t *first_row_max = first_row + n; + + SMOL_ASSUME_ALIGNED (first_row, const uint64_t *); + SMOL_ASSUME_ALIGNED (last_row, uint64_t *); + SMOL_ASSUME_ALIGNED (accum, uint64_t *); + + while (first_row != first_row_max) + { + uint64_t r, s, p, q; + + p = *(first_row++); + + r = *(last_row); + s = r * w2; + q = (s >> 8) & 0x00ff00ff00ff00ffULL; + /* (255 * r) - (F * r) */ + *(last_row++) = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; + + *(accum++) = p + q; + } +} + +static void +update_vertical_ctx_box_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t ofs_y, + uint32_t ofs_y_max, + uint16_t w1, + uint16_t w2) +{ + /* Old in_ofs is the previous max */ + if (ofs_y == vertical_ctx->in_ofs) + { + uint64_t *t = vertical_ctx->parts_row [0]; + vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; + vertical_ctx->parts_row [1] = t; + } + else + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [0]); + weight_edge_row_64bpp (vertical_ctx->parts_row [0], w1, scale_ctx->width_out); + } + + /* When w2 == 0, the final inrow may be out of bounds. Don't try to access it in + * that case. */ + if (w2 || ofs_y_max < scale_ctx->height_in) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y_max), + vertical_ctx->parts_row [1]); + } + else + { + memset (vertical_ctx->parts_row [1], 0, scale_ctx->width_out * sizeof (uint64_t)); + } + + vertical_ctx->in_ofs = ofs_y_max; +} + +static void +scale_outrow_box_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t ofs_y, ofs_y_max; + uint16_t w1, w2; + + /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ + + ofs_y = scale_ctx->offsets_y [outrow_index * 2]; + ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; + + /* Scale the first and last rows, weight them and store in accumulator */ + + w1 = (outrow_index == 0) ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1]; + w2 = scale_ctx->offsets_y [outrow_index * 2 + 1]; + + update_vertical_ctx_box_64bpp (scale_ctx, vertical_ctx, ofs_y, ofs_y_max, w1, w2); + + scale_and_weight_edge_rows_box_64bpp (vertical_ctx->parts_row [0], + vertical_ctx->parts_row [1], + vertical_ctx->parts_row [2], + w2, + scale_ctx->width_out); + + ofs_y++; + + /* Add up whole rows */ + + while (ofs_y < ofs_y_max) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [0]); + add_parts (vertical_ctx->parts_row [0], + vertical_ctx->parts_row [2], + scale_ctx->width_out); + + ofs_y++; + } + + finalize_vertical_64bpp (vertical_ctx->parts_row [2], + scale_ctx->span_mul_y, + vertical_ctx->parts_row [0], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, + uint64_t multiplier, + uint64_t * SMOL_RESTRICT parts_out, + uint32_t n) +{ + uint64_t *parts_out_max = parts_out + n * 2; + + SMOL_ASSUME_ALIGNED (accums, const uint64_t *); + SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); + + while (parts_out != parts_out_max) + { + *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); + *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); + } +} + +static void +weight_row_128bpp (uint64_t *row, + uint16_t w, + uint32_t n) +{ + uint64_t *row_max = row + (n * 2); + + SMOL_ASSUME_ALIGNED (row, uint64_t *); + + while (row != row_max) + { + row [0] = ((row [0] * w) >> 8) & 0x00ffffff00ffffffULL; + row [1] = ((row [1] * w) >> 8) & 0x00ffffff00ffffffULL; + row += 2; + } +} + +static void +scale_outrow_box_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + uint32_t ofs_y, ofs_y_max; + uint16_t w; + + /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ + + ofs_y = scale_ctx->offsets_y [outrow_index * 2]; + ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; + + /* Scale the first inrow and store it */ + + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [0]); + weight_row_128bpp (vertical_ctx->parts_row [0], + outrow_index == 0 ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1], + scale_ctx->width_out); + ofs_y++; + + /* Add up whole rows */ + + while (ofs_y < ofs_y_max) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [1]); + add_parts (vertical_ctx->parts_row [1], + vertical_ctx->parts_row [0], + scale_ctx->width_out * 2); + + ofs_y++; + } + + /* Final row is optional; if this is the bottommost outrow it could be out of bounds */ + + w = scale_ctx->offsets_y [outrow_index * 2 + 1]; + if (w > 0) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, ofs_y), + vertical_ctx->parts_row [1]); + weight_row_128bpp (vertical_ctx->parts_row [1], + w - 1, /* Subtract 1 to avoid overflow */ + scale_ctx->width_out); + add_parts (vertical_ctx->parts_row [1], + vertical_ctx->parts_row [0], + scale_ctx->width_out * 2); + } + + finalize_vertical_128bpp (vertical_ctx->parts_row [0], + scale_ctx->span_mul_y, + vertical_ctx->parts_row [1], + scale_ctx->width_out); + scale_ctx->pack_row_func (vertical_ctx->parts_row [1], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_one_64bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t row_index, + uint32_t *row_out) +{ + SMOL_UNUSED (row_index); + + /* Scale the row and store it */ + + if (vertical_ctx->in_ofs != 0) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, 0), + vertical_ctx->parts_row [0]); + vertical_ctx->in_ofs = 0; + } + + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_one_128bpp (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t row_index, + uint32_t *row_out) +{ + SMOL_UNUSED (row_index); + + /* Scale the row and store it */ + + if (vertical_ctx->in_ofs != 0) + { + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, 0), + vertical_ctx->parts_row [0]); + vertical_ctx->in_ofs = 0; + } + + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +scale_outrow_copy (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t row_index, + uint32_t *row_out) +{ + scale_horizontal (scale_ctx, + vertical_ctx, + inrow_ofs_to_pointer (scale_ctx, row_index), + vertical_ctx->parts_row [0]); + + scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); +} + +static void +scale_outrow (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out) +{ + scale_ctx->vfilter_func (scale_ctx, + vertical_ctx, + outrow_index, + row_out); + + if (scale_ctx->post_row_func) + scale_ctx->post_row_func (row_out, scale_ctx->width_out, scale_ctx->user_data); +} + +static void +do_rows (const SmolScaleCtx *scale_ctx, + void *outrows_dest, + uint32_t row_out_index, + uint32_t n_rows) +{ + SmolVerticalCtx vertical_ctx = { 0 }; + uint32_t n_parts_per_pixel = 1; + uint32_t n_stored_rows = 4; + uint32_t i; + + if (scale_ctx->storage_type == SMOL_STORAGE_128BPP) + n_parts_per_pixel = 2; + + /* Must be one less, or this test in update_vertical_ctx() will wrap around: + * if (new_in_ofs == vertical_ctx->in_ofs + 1) { ... } */ + vertical_ctx.in_ofs = UINT_MAX - 1; + + for (i = 0; i < n_stored_rows; i++) + { + vertical_ctx.parts_row [i] = + smol_alloc_aligned (MAX (scale_ctx->width_in, scale_ctx->width_out) + * n_parts_per_pixel * sizeof (uint64_t), + &vertical_ctx.row_storage [i]); + } + + for (i = row_out_index; i < row_out_index + n_rows; i++) + { + scale_outrow (scale_ctx, &vertical_ctx, i, outrows_dest); + outrows_dest = (uint32_t *) outrows_dest + scale_ctx->rowstride_out; + } + + for (i = 0; i < n_stored_rows; i++) + { + smol_free (vertical_ctx.row_storage [i]); + } + + /* Used to align row data if needed. May be allocated in scale_horizontal(). */ + if (vertical_ctx.in_aligned) + smol_free (vertical_ctx.in_aligned_storage); +} + +/* --- Conversion tables --- */ + +static const SmolConversionTable generic_conversions = +{ +{ { + /* Conversions where accumulators must hold the sum of fewer than + * 256 pixels. This can be done in 64bpp, but 128bpp may be used + * e.g. for 16 bits per channel internally premultiplied data. */ + + /* RGBA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), + }, + /* BGRA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), + }, + /* ARGB8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), + }, + /* ABGR8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), + /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), + /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), + /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), + /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), + /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), + /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), + }, + /* RGBA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), + /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), + /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), + /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + }, + /* BGRA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), + /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), + /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), + /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + }, + /* ARGB8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), + /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), + /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), + /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + }, + /* ABGR8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), + /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), + /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), + /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + }, + /* RGB8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), + /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), + }, + /* BGR8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), + /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), + /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), + /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), + /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), + /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), + } + }, + + { + /* Conversions where accumulators must hold the sum of up to + * 65535 pixels. We need 128bpp for this. */ + + /* RGBA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), + }, + /* BGRA8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), + }, + /* ARGB8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), + }, + /* ABGR8 pre -> */ + { + /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), + /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), + /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), + /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), + /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), + /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), + /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), + /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), + /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), + /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), + }, + /* RGBA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), + /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), + /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), + /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + }, + /* BGRA8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), + /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), + /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), + /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), + /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), + }, + /* ARGB8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), + /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), + /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), + /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + }, + /* ABGR8 un -> */ + { + /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), + /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), + /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), + /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), + /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), + /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), + /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), + /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), + /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), + /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), + }, + /* RGB8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), + /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), + }, + /* BGR8 -> */ + { + /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), + /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), + /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), + /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), + /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), + /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), + } +} } +}; + +static const SmolImplementation generic_implementation = +{ + { + /* Horizontal filters */ + { + /* 64bpp */ + interp_horizontal_copy_64bpp, + interp_horizontal_one_64bpp, + interp_horizontal_bilinear_0h_64bpp, + interp_horizontal_bilinear_1h_64bpp, + interp_horizontal_bilinear_2h_64bpp, + interp_horizontal_bilinear_3h_64bpp, + interp_horizontal_bilinear_4h_64bpp, + interp_horizontal_bilinear_5h_64bpp, + interp_horizontal_bilinear_6h_64bpp, + interp_horizontal_boxes_64bpp + }, + { + /* 128bpp */ + interp_horizontal_copy_128bpp, + interp_horizontal_one_128bpp, + interp_horizontal_bilinear_0h_128bpp, + interp_horizontal_bilinear_1h_128bpp, + interp_horizontal_bilinear_2h_128bpp, + interp_horizontal_bilinear_3h_128bpp, + interp_horizontal_bilinear_4h_128bpp, + interp_horizontal_bilinear_5h_128bpp, + interp_horizontal_bilinear_6h_128bpp, + interp_horizontal_boxes_128bpp + } + }, + { + /* Vertical filters */ + { + /* 64bpp */ + scale_outrow_copy, + scale_outrow_one_64bpp, + scale_outrow_bilinear_0h_64bpp, + scale_outrow_bilinear_1h_64bpp, + scale_outrow_bilinear_2h_64bpp, + scale_outrow_bilinear_3h_64bpp, + scale_outrow_bilinear_4h_64bpp, + scale_outrow_bilinear_5h_64bpp, + scale_outrow_bilinear_6h_64bpp, + scale_outrow_box_64bpp + }, + { + /* 128bpp */ + scale_outrow_copy, + scale_outrow_one_128bpp, + scale_outrow_bilinear_0h_128bpp, + scale_outrow_bilinear_1h_128bpp, + scale_outrow_bilinear_2h_128bpp, + scale_outrow_bilinear_3h_128bpp, + scale_outrow_bilinear_4h_128bpp, + scale_outrow_bilinear_5h_128bpp, + scale_outrow_bilinear_6h_128bpp, + scale_outrow_box_128bpp + } + }, + &generic_conversions +}; + +/* In the absence of a proper build system, runtime detection is more + portable than compiler macros. WFM. */ +static SmolBool +host_is_little_endian (void) +{ + static const union + { + uint8_t u8 [4]; + uint32_t u32; + } + host_bytes = { { 0, 1, 2, 3 } }; + + if (host_bytes.u32 == 0x03020100UL) + return TRUE; + + return FALSE; +} + +/* The generic unpack/pack functions fetch and store pixels as u32. + * This means the byte order will be reversed on little endian, with + * consequences for the alpha channel and reordering logic. We deal + * with this by using the apparent byte order internally. */ +static SmolPixelType +get_host_pixel_type (SmolPixelType pixel_type) +{ + SmolPixelType host_pixel_type = SMOL_PIXEL_MAX; + + if (!host_is_little_endian ()) + return pixel_type; + + /* We use a switch for this so the compiler can remind us + * to keep it in sync with the SmolPixelType enum. */ + switch (pixel_type) + { + case SMOL_PIXEL_RGBA8_PREMULTIPLIED: + host_pixel_type = SMOL_PIXEL_ABGR8_PREMULTIPLIED; break; + case SMOL_PIXEL_BGRA8_PREMULTIPLIED: + host_pixel_type = SMOL_PIXEL_ARGB8_PREMULTIPLIED; break; + case SMOL_PIXEL_ARGB8_PREMULTIPLIED: + host_pixel_type = SMOL_PIXEL_BGRA8_PREMULTIPLIED; break; + case SMOL_PIXEL_ABGR8_PREMULTIPLIED: + host_pixel_type = SMOL_PIXEL_RGBA8_PREMULTIPLIED; break; + case SMOL_PIXEL_RGBA8_UNASSOCIATED: + host_pixel_type = SMOL_PIXEL_ABGR8_UNASSOCIATED; break; + case SMOL_PIXEL_BGRA8_UNASSOCIATED: + host_pixel_type = SMOL_PIXEL_ARGB8_UNASSOCIATED; break; + case SMOL_PIXEL_ARGB8_UNASSOCIATED: + host_pixel_type = SMOL_PIXEL_BGRA8_UNASSOCIATED; break; + case SMOL_PIXEL_ABGR8_UNASSOCIATED: + host_pixel_type = SMOL_PIXEL_RGBA8_UNASSOCIATED; break; + case SMOL_PIXEL_RGB8: + host_pixel_type = SMOL_PIXEL_RGB8; break; + case SMOL_PIXEL_BGR8: + host_pixel_type = SMOL_PIXEL_BGR8; break; + case SMOL_PIXEL_MAX: + host_pixel_type = SMOL_PIXEL_MAX; break; + } + + return host_pixel_type; +} + +#ifdef SMOL_WITH_AVX2 + +static SmolBool +have_avx2 (void) +{ +#ifdef HAVE_GCC_X86_FEATURE_BUILTINS + __builtin_cpu_init (); + + if (__builtin_cpu_supports ("avx2")) + return TRUE; +#endif + + return FALSE; +} + +#endif + +static void +try_override_conversion (SmolScaleCtx *scale_ctx, + const SmolImplementation *impl, + SmolPixelType ptype_in, + SmolPixelType ptype_out, + uint8_t *n_bytes_per_pixel) +{ + const SmolConversion *conv; + + conv = &impl->ctab->conversions + [scale_ctx->storage_type] [ptype_in] [ptype_out]; + + if (conv->unpack_row_func && conv->pack_row_func) + { + *n_bytes_per_pixel = conv->n_bytes_per_pixel; + scale_ctx->unpack_row_func = conv->unpack_row_func; + scale_ctx->pack_row_func = conv->pack_row_func; + } +} + +static void +try_override_filters (SmolScaleCtx *scale_ctx, + const SmolImplementation *impl) +{ + SmolHFilterFunc *hfilter_func; + SmolVFilterFunc *vfilter_func; + + hfilter_func = impl->hfilter_funcs + [scale_ctx->storage_type] [scale_ctx->filter_h]; + vfilter_func = impl->vfilter_funcs + [scale_ctx->storage_type] [scale_ctx->filter_v]; + + if (hfilter_func) + scale_ctx->hfilter_func = hfilter_func; + if (vfilter_func) + scale_ctx->vfilter_func = vfilter_func; +} + +static void +get_implementations (SmolScaleCtx *scale_ctx) +{ + const SmolConversion *conv; + SmolPixelType ptype_in, ptype_out; + uint8_t n_bytes_per_pixel; + const SmolImplementation *avx2_impl = NULL; + +#ifdef SMOL_WITH_AVX2 + if (have_avx2 ()) + avx2_impl = _smol_get_avx2_implementation (); +#endif + + ptype_in = get_host_pixel_type (scale_ctx->pixel_type_in); + ptype_out = get_host_pixel_type (scale_ctx->pixel_type_out); + + /* Install generic unpack()/pack() */ + + conv = &generic_implementation.ctab->conversions + [scale_ctx->storage_type] [ptype_in] [ptype_out]; + + n_bytes_per_pixel = conv->n_bytes_per_pixel; + scale_ctx->unpack_row_func = conv->unpack_row_func; + scale_ctx->pack_row_func = conv->pack_row_func; + + /* Try to override with better unpack()/pack() implementations */ + + if (avx2_impl) + try_override_conversion (scale_ctx, avx2_impl, + ptype_in, ptype_out, + &n_bytes_per_pixel); + + /* Some conversions require extra precision. This can only ever + * upgrade the storage from 64bpp to 128bpp, but we handle both + * cases here for clarity. */ + if (n_bytes_per_pixel == 8) + scale_ctx->storage_type = SMOL_STORAGE_64BPP; + else if (n_bytes_per_pixel == 16) + scale_ctx->storage_type = SMOL_STORAGE_128BPP; + else + { + assert (n_bytes_per_pixel == 8 || n_bytes_per_pixel == 16); + } + + /* Install generic filters */ + + scale_ctx->hfilter_func = generic_implementation.hfilter_funcs + [scale_ctx->storage_type] [scale_ctx->filter_h]; + scale_ctx->vfilter_func = generic_implementation.vfilter_funcs + [scale_ctx->storage_type] [scale_ctx->filter_v]; + + /* Try to override with better filter implementations */ + + if (avx2_impl) + try_override_filters (scale_ctx, avx2_impl); +} + +static void +smol_scale_init (SmolScaleCtx *scale_ctx, + SmolPixelType pixel_type_in, + const void *pixels_in, + uint32_t width_in, + uint32_t height_in, + uint32_t rowstride_in, + SmolPixelType pixel_type_out, + void *pixels_out, + uint32_t width_out, + uint32_t height_out, + uint32_t rowstride_out, + SmolPostRowFunc post_row_func, + void *user_data) +{ + SmolStorageType storage_type [2]; + + scale_ctx->pixel_type_in = pixel_type_in; + scale_ctx->pixels_in = pixels_in; + scale_ctx->width_in = width_in; + scale_ctx->height_in = height_in; + scale_ctx->rowstride_in = rowstride_in / sizeof (uint32_t); + scale_ctx->pixel_type_out = pixel_type_out; + scale_ctx->pixels_out = pixels_out; + scale_ctx->width_out = width_out; + scale_ctx->height_out = height_out; + scale_ctx->rowstride_out = rowstride_out / sizeof (uint32_t); + + scale_ctx->post_row_func = post_row_func; + scale_ctx->user_data = user_data; + + pick_filter_params (width_in, width_out, + &scale_ctx->width_halvings, + &scale_ctx->width_bilin_out, + &scale_ctx->filter_h, + &storage_type [0]); + pick_filter_params (height_in, height_out, + &scale_ctx->height_halvings, + &scale_ctx->height_bilin_out, + &scale_ctx->filter_v, + &storage_type [1]); + + scale_ctx->storage_type = MAX (storage_type [0], storage_type [1]); + + scale_ctx->offsets_x = malloc (((scale_ctx->width_bilin_out + 1) * 2 + + (scale_ctx->height_bilin_out + 1) * 2) * sizeof (uint16_t)); + scale_ctx->offsets_y = scale_ctx->offsets_x + (scale_ctx->width_bilin_out + 1) * 2; + + if (scale_ctx->filter_h == SMOL_FILTER_ONE) + { + } + else if (scale_ctx->filter_h == SMOL_FILTER_BOX) + { + precalc_boxes_array (scale_ctx->offsets_x, &scale_ctx->span_mul_x, + width_in, scale_ctx->width_out, FALSE); + } + else /* SMOL_FILTER_BILINEAR_?H */ + { + precalc_bilinear_array (scale_ctx->offsets_x, + width_in, scale_ctx->width_bilin_out, FALSE); + } + + if (scale_ctx->filter_v == SMOL_FILTER_ONE) + { + } + else if (scale_ctx->filter_v == SMOL_FILTER_BOX) + { + precalc_boxes_array (scale_ctx->offsets_y, &scale_ctx->span_mul_y, + height_in, scale_ctx->height_out, TRUE); + } + else /* SMOL_FILTER_BILINEAR_?H */ + { + precalc_bilinear_array (scale_ctx->offsets_y, + height_in, scale_ctx->height_bilin_out, TRUE); + } + + get_implementations (scale_ctx); +} + +static void +smol_scale_finalize (SmolScaleCtx *scale_ctx) +{ + free (scale_ctx->offsets_x); +} + +/* --- Public API --- */ + +SmolScaleCtx * +smol_scale_new (SmolPixelType pixel_type_in, + const void *pixels_in, + uint32_t width_in, + uint32_t height_in, + uint32_t rowstride_in, + SmolPixelType pixel_type_out, + void *pixels_out, + uint32_t width_out, + uint32_t height_out, + uint32_t rowstride_out) +{ + SmolScaleCtx *scale_ctx; + + scale_ctx = calloc (sizeof (SmolScaleCtx), 1); + smol_scale_init (scale_ctx, + pixel_type_in, + pixels_in, + width_in, + height_in, + rowstride_in, + pixel_type_out, + pixels_out, + width_out, + height_out, + rowstride_out, + NULL, + NULL); + return scale_ctx; +} + +SmolScaleCtx * +smol_scale_new_full (SmolPixelType pixel_type_in, + const void *pixels_in, + uint32_t width_in, + uint32_t height_in, + uint32_t rowstride_in, + SmolPixelType pixel_type_out, + void *pixels_out, + uint32_t width_out, + uint32_t height_out, + uint32_t rowstride_out, + SmolPostRowFunc post_row_func, + void *user_data) +{ + SmolScaleCtx *scale_ctx; + + scale_ctx = calloc (sizeof (SmolScaleCtx), 1); + smol_scale_init (scale_ctx, + pixel_type_in, + pixels_in, + width_in, + height_in, + rowstride_in, + pixel_type_out, + pixels_out, + width_out, + height_out, + rowstride_out, + post_row_func, + user_data); + return scale_ctx; +} + +void +smol_scale_destroy (SmolScaleCtx *scale_ctx) +{ + smol_scale_finalize (scale_ctx); + free (scale_ctx); +} + +void +smol_scale_simple (SmolPixelType pixel_type_in, + const void *pixels_in, + uint32_t width_in, + uint32_t height_in, + uint32_t rowstride_in, + SmolPixelType pixel_type_out, + void *pixels_out, + uint32_t width_out, + uint32_t height_out, + uint32_t rowstride_out) +{ + SmolScaleCtx scale_ctx; + + smol_scale_init (&scale_ctx, + pixel_type_in, pixels_in, + width_in, height_in, rowstride_in, + pixel_type_out, pixels_out, + width_out, height_out, rowstride_out, + NULL, NULL); + do_rows (&scale_ctx, + outrow_ofs_to_pointer (&scale_ctx, 0), + 0, + scale_ctx.height_out); + smol_scale_finalize (&scale_ctx); +} + +void +smol_scale_batch (const SmolScaleCtx *scale_ctx, + uint32_t first_out_row, + uint32_t n_out_rows) +{ + do_rows (scale_ctx, + outrow_ofs_to_pointer (scale_ctx, first_out_row), + first_out_row, + n_out_rows); +} + +void +smol_scale_batch_full (const SmolScaleCtx *scale_ctx, + void *outrows_dest, + uint32_t first_out_row, + uint32_t n_out_rows) +{ + do_rows (scale_ctx, + outrows_dest, + first_out_row, + n_out_rows); +} diff -Nru chafa-1.2.1/chafa/internal/smolscale/smolscale.h chafa-1.12.4/chafa/internal/smolscale/smolscale.h --- chafa-1.2.1/chafa/internal/smolscale/smolscale.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/smolscale.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright © 2019-2022 Hans Petter Jansson. See COPYING for details. */ + +#include + +#ifndef _SMOLSCALE_H_ +#define _SMOLSCALE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + /* 32 bits per pixel */ + + SMOL_PIXEL_RGBA8_PREMULTIPLIED, + SMOL_PIXEL_BGRA8_PREMULTIPLIED, + SMOL_PIXEL_ARGB8_PREMULTIPLIED, + SMOL_PIXEL_ABGR8_PREMULTIPLIED, + + SMOL_PIXEL_RGBA8_UNASSOCIATED, + SMOL_PIXEL_BGRA8_UNASSOCIATED, + SMOL_PIXEL_ARGB8_UNASSOCIATED, + SMOL_PIXEL_ABGR8_UNASSOCIATED, + + /* 24 bits per pixel */ + + SMOL_PIXEL_RGB8, + SMOL_PIXEL_BGR8, + + SMOL_PIXEL_MAX +} +SmolPixelType; + +typedef void (SmolPostRowFunc) (uint32_t *row_inout, + int width, + void *user_data); + +typedef struct SmolScaleCtx SmolScaleCtx; + +/* Simple API: Scales an entire image in one shot. You must provide pointers to + * the source memory and an existing allocation to receive the output data. + * This interface can only be used from a single thread. */ + +void smol_scale_simple (SmolPixelType pixel_type_in, const void *pixels_in, + uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, + SmolPixelType pixel_type_out, void *pixels_out, + uint32_t width_out, uint32_t height_out, uint32_t rowstride_out); + +/* Batch API: Allows scaling a few rows at a time. Suitable for multithreading. */ + +SmolScaleCtx *smol_scale_new (SmolPixelType pixel_type_in, const void *pixels_in, + uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, + SmolPixelType pixel_type_out, void *pixels_out, + uint32_t width_out, uint32_t height_out, uint32_t rowstride_out); + +SmolScaleCtx *smol_scale_new_full (SmolPixelType pixel_type_in, const void *pixels_in, + uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, + SmolPixelType pixel_type_out, void *pixels_out, + uint32_t width_out, uint32_t height_out, uint32_t rowstride_out, + SmolPostRowFunc post_row_func, void *user_data); + +void smol_scale_destroy (SmolScaleCtx *scale_ctx); + +/* It's ok to call smol_scale_batch() without locking from multiple concurrent + * threads, as long as the outrows do not overlap. Make sure all workers are + * finished before you call smol_scale_destroy(). */ + +void smol_scale_batch (const SmolScaleCtx *scale_ctx, uint32_t first_outrow, uint32_t n_outrows); + +/* Like smol_scale_batch(), but will write the output rows to outrows_dest + * instead of relative to pixels_out address handed to smol_scale_new(). The + * other parameters from init (size, rowstride, etc) will still be used. */ + +void smol_scale_batch_full (const SmolScaleCtx *scale_ctx, + void *outrows_dest, + uint32_t first_outrow, uint32_t n_outrows); + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru chafa-1.2.1/chafa/internal/smolscale/smolscale-private.h chafa-1.12.4/chafa/internal/smolscale/smolscale-private.h --- chafa-1.2.1/chafa/internal/smolscale/smolscale-private.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/chafa/internal/smolscale/smolscale-private.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,201 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright © 2019-2022 Hans Petter Jansson. See COPYING for details. */ + +#include +#include "smolscale.h" + +#ifndef _SMOLSCALE_PRIVATE_H_ +#define _SMOLSCALE_PRIVATE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "config.h" + +#ifdef SMOL_USE_ALLOCA +# define _SMOL_ALLOC(n) alloca (n) +# define _SMOL_FREE(p) +#else +# define _SMOL_ALLOC(n) malloc (n) +# define _SMOL_FREE(p) free (p) +#endif + +/* Enum switches must handle every value */ +#ifdef __GNUC__ +# pragma GCC diagnostic error "-Wswitch" +#endif + +/* Compensate for GCC missing intrinsics */ +#ifdef __GNUC__ +# if __GNUC__ < 8 +# define _mm256_set_m128i(h, l) \ + _mm256_insertf128_si256 (_mm256_castsi128_si256 (l), (h), 1) +# endif +#endif + +#ifndef FALSE +# define FALSE (0) +#endif +#ifndef TRUE +# define TRUE (!FALSE) +#endif +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +typedef unsigned int SmolBool; + +#define SMOL_4X2BIT(a, b, c, d) \ + (((a) << 6) | ((b) << 4) | ((c) << 2) | (d)) + +#define SMOL_8X1BIT(a,b,c,d,e,f,g,h) \ + (((a) << 7) | ((b) << 6) | ((c) << 5) | ((d) << 4) \ + | ((e) << 3) | ((f) << 2) | ((g) << 1) | ((h) << 0)) + +#define SMOL_UNUSED(x) (void) ((x)=(x)) +#define SMOL_RESTRICT __restrict +#define SMOL_INLINE __attribute__((always_inline)) inline +#define SMOL_CONST __attribute__((const)) +#define SMOL_PURE __attribute__((pure)) + +#define SMOL_SMALL_MUL 256U +#define SMOL_BIG_MUL 65536U +#define SMOL_BOXES_MULTIPLIER ((uint64_t) SMOL_BIG_MUL * SMOL_SMALL_MUL) +#define SMOL_BILIN_MULTIPLIER ((uint64_t) SMOL_BIG_MUL * SMOL_BIG_MUL) + +#define SMOL_ALIGNMENT 64 + +#define SMOL_ASSUME_ALIGNED_TO(x, t, n) (x) = (t) __builtin_assume_aligned ((x), (n)) +#define SMOL_ASSUME_ALIGNED(x, t) SMOL_ASSUME_ALIGNED_TO ((x), t, SMOL_ALIGNMENT) + +/* Pointer to beginning of storage is stored in *r. This must be passed to smol_free() later. */ +#define smol_alloc_aligned_to(s, a, r) \ + ({ void *p; *(r) = _SMOL_ALLOC ((s) + (a)); p = (void *) (((uintptr_t) (*(r)) + (a)) & ~((a) - 1)); (p); }) +#define smol_alloc_aligned(s, r) smol_alloc_aligned_to ((s), SMOL_ALIGNMENT, (r)) +#define smol_free(p) _SMOL_FREE(p) + +typedef enum +{ + SMOL_STORAGE_64BPP, + SMOL_STORAGE_128BPP, + SMOL_STORAGE_MAX +} +SmolStorageType; + +typedef enum +{ + SMOL_FILTER_COPY, + SMOL_FILTER_ONE, + SMOL_FILTER_BILINEAR_0H, + SMOL_FILTER_BILINEAR_1H, + SMOL_FILTER_BILINEAR_2H, + SMOL_FILTER_BILINEAR_3H, + SMOL_FILTER_BILINEAR_4H, + SMOL_FILTER_BILINEAR_5H, + SMOL_FILTER_BILINEAR_6H, + SMOL_FILTER_BOX, + + SMOL_FILTER_MAX +} +SmolFilterType; + +/* For reusing rows that have already undergone horizontal scaling */ +typedef struct +{ + uint32_t in_ofs; + uint64_t *parts_row [4]; + uint64_t *row_storage [4]; + uint32_t *in_aligned; + uint32_t *in_aligned_storage; +} +SmolVerticalCtx; + +typedef void (SmolUnpackRowFunc) (const uint32_t *row_in, + uint64_t *row_out, + uint32_t n_pixels); +typedef void (SmolPackRowFunc) (const uint64_t *row_in, + uint32_t *row_out, + uint32_t n_pixels); +typedef void (SmolHFilterFunc) (const SmolScaleCtx *scale_ctx, + const uint64_t *row_limbs_in, + uint64_t *row_limbs_out); +typedef void (SmolVFilterFunc) (const SmolScaleCtx *scale_ctx, + SmolVerticalCtx *vertical_ctx, + uint32_t outrow_index, + uint32_t *row_out); + +#define SMOL_CONV_UNDEFINED { 0, NULL, NULL } +#define SMOL_CONV(un_from_order, un_from_type, un_to_order, un_to_type, pk_from_order, pk_from_type, pk_to_order, pk_to_type, storage_bits) \ +{ storage_bits / 8, (SmolUnpackRowFunc *) unpack_row_##un_from_order##_##un_from_type##_to_##un_to_order##_##un_to_type##_##storage_bits##bpp, \ +(SmolPackRowFunc *) pack_row_##pk_from_order##_##pk_from_type##_to_##pk_to_order##_##pk_to_type##_##storage_bits##bpp } + +typedef struct +{ + uint8_t n_bytes_per_pixel; + SmolUnpackRowFunc *unpack_row_func; + SmolPackRowFunc *pack_row_func; +} +SmolConversion; + +typedef struct +{ + SmolConversion conversions [SMOL_STORAGE_MAX] [SMOL_PIXEL_MAX] [SMOL_PIXEL_MAX]; +} +SmolConversionTable; + +typedef struct +{ + SmolHFilterFunc *hfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; + SmolVFilterFunc *vfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; + + /* Can be a NULL pointer if the implementation does not override any + * conversions. */ + const SmolConversionTable *ctab; +} +SmolImplementation; + +struct SmolScaleCtx +{ + /* */ + + const uint32_t *pixels_in; + uint32_t *pixels_out; + uint32_t width_in, height_in, rowstride_in; + uint32_t width_out, height_out, rowstride_out; + + SmolPixelType pixel_type_in, pixel_type_out; + SmolFilterType filter_h, filter_v; + SmolStorageType storage_type; + + SmolUnpackRowFunc *unpack_row_func; + SmolPackRowFunc *pack_row_func; + SmolHFilterFunc *hfilter_func; + SmolVFilterFunc *vfilter_func; + + /* User specified, can be NULL */ + SmolPostRowFunc *post_row_func; + void *user_data; + + /* Each offset is split in two uint16s: { pixel index, fraction }. These + * are relative to the image after halvings have taken place. */ + uint16_t *offsets_x, *offsets_y; + uint32_t span_mul_x, span_mul_y; /* For box filter */ + + uint32_t width_bilin_out, height_bilin_out; + unsigned int width_halvings, height_halvings; +}; + +#ifdef SMOL_WITH_AVX2 +const SmolImplementation *_smol_get_avx2_implementation (void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru chafa-1.2.1/chafa/Makefile.am chafa-1.12.4/chafa/Makefile.am --- chafa-1.2.1/chafa/Makefile.am 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -1,23 +1,24 @@ -SUBDIRS = smolscale +SUBDIRS = internal +DISTCLEANFILES = +BUILT_SOURCES = ## --- Library --- lib_LTLIBRARIES = libchafa.la noinst_LTLIBRARIES = +noinst_HEADERS = -libchafa_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -libchafa_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) -version-info 1:1:1 -libchafa_la_LIBADD = $(GLIB_LIBS) smolscale/libsmolscale.la -lm +libchafa_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -DCHAFA_COMPILATION +libchafa_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) -no-undefined -version-info 8:4:8 +libchafa_la_LIBADD = $(GLIB_LIBS) internal/libchafa-internal.la -lm libchafa_la_SOURCES = \ chafa-canvas.c \ chafa-canvas-config.c \ - chafa-colors.c \ chafa-features.c \ - chafa-private.h \ chafa-symbol-map.c \ - chafa-symbols.c \ - chafa-symbols-ascii.h \ + chafa-term-db.c \ + chafa-term-info.c \ chafa-util.c chafaincludedir=$(includedir)/chafa @@ -25,36 +26,43 @@ chafa.h \ chafa-canvas.h \ chafa-canvas-config.h \ + chafa-common.h \ chafa-features.h \ chafa-symbol-map.h \ + chafa-term-db.h \ + chafa-term-info.h \ + chafa-term-seq-def.h \ chafa-util.h \ chafa-version-macros.h -if HAVE_MMX_INTRINSICS -noinst_LTLIBRARIES += libchafa-mmx.la -libchafa_la_LIBADD += libchafa-mmx.la -libchafa_mmx_la_SOURCES = chafa-mmx.c -libchafa_mmx_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mmmx -libchafa_mmx_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) -endif - -if HAVE_SSE41_INTRINSICS -noinst_LTLIBRARIES += libchafa-sse41.la -libchafa_la_LIBADD += libchafa-sse41.la -libchafa_sse41_la_SOURCES = chafa-sse41.c -libchafa_sse41_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -msse4.1 -libchafa_sse41_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) -endif - -if HAVE_POPCNT_INTRINSICS -noinst_LTLIBRARIES += libchafa-popcnt.la -libchafa_la_LIBADD += libchafa-popcnt.la -libchafa_popcnt_la_SOURCES = chafa-popcnt.c -libchafa_popcnt_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mpopcnt -libchafa_popcnt_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) -endif +# Generate header prototypes with docstrings (-CC to pass through comments) +# for terminal sequence accessors. These will be processed by gtk-doc to +# produce documentation. + +noinst_HEADERS += chafa-term-seq-doc.h chafa-term-seq-doc-in.h +DISTCLEANFILES += chafa-term-seq-doc.h +BUILT_SOURCES += chafa-term-seq-doc.h + +chafa-term-seq-doc.h: $(srcdir)/chafa-term-seq-doc-in.h + $(CPP) -CC $< -o $@ + +# Generate chafaconfig.h +# +# The timestamp of the stamp file is used to indicate if chafaconfig.h is +# up to date with respect to config.status. In the usual case, changes +# to config.status will not result in changes to chafaconfig.h, so we +# avoid touching its timestamp (so we don't rebuild the whole tree). + +DISTCLEANFILES += chafaconfig-stamp chafaconfig.h +BUILT_SOURCES += chafaconfig-stamp +configexecincludedir = $(libdir)/chafa/include +nodist_configexecinclude_HEADERS = chafaconfig.h +chafaconfig-stamp: ../config.status + $(AM_V_GEN) cd $(top_builddir) && \ + $(SHELL) ./config.status chafa/chafaconfig.h + @touch chafaconfig-stamp -## -- General --- +## --- General --- AM_CPPFLAGS = \ -I$(top_srcdir) diff -Nru chafa-1.2.1/chafa/smolscale/COPYING chafa-1.12.4/chafa/smolscale/COPYING --- chafa-1.2.1/chafa/smolscale/COPYING 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/COPYING 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -This software is Copyright © 2019 Hans Petter Jansson. All rights reserved. - -You may use and redistribute this software under the terms and conditions laid -out in either the BSD 4-clause license (see COPYING.BSD-4) or the LGPL -version 3 (see COPYING.LGPLv3). - -The practical intent is that you should be able to use the software in -an LGPLv3-compatible project without giving credit (although it would be nice -if you did so). However, if you're using it in a closed-source project, you -must either publish it separately in an LGPLv3-licensed library and link -with that, or credit the contributors in your advertising materials. - -If you want to use it under a different license, feel free to contact me by -e-mail at . diff -Nru chafa-1.2.1/chafa/smolscale/COPYING.BSD-4 chafa-1.12.4/chafa/smolscale/COPYING.BSD-4 --- chafa-1.2.1/chafa/smolscale/COPYING.BSD-4 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/COPYING.BSD-4 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -Copyright © 2019 Hans Petter Jansson. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -4. All advertising materials mentioning features or use of this - software must display the following acknowledgement: 'This - product includes software developed by Hans Petter Jansson - (https://hpjansson.org/).' - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff -Nru chafa-1.2.1/chafa/smolscale/COPYING.LGPLv3 chafa-1.12.4/chafa/smolscale/COPYING.LGPLv3 --- chafa-1.2.1/chafa/smolscale/COPYING.LGPLv3 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/COPYING.LGPLv3 1970-01-01 00:00:00.000000000 +0000 @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff -Nru chafa-1.2.1/chafa/smolscale/Makefile.am chafa-1.12.4/chafa/smolscale/Makefile.am --- chafa-1.2.1/chafa/smolscale/Makefile.am 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -noinst_LTLIBRARIES = libsmolscale.la - -SMOLSCALE_CFLAGS = $(LIBCHAFA_CFLAGS) -SMOLSCALE_LDFLAGS = $(LIBCHAFA_LDFLAGS) - -if HAVE_AVX2_INTRINSICS -SMOLSCALE_CFLAGS += -DSMOL_WITH_AVX2 -endif - -libsmolscale_la_CFLAGS = $(SMOLSCALE_CFLAGS) -libsmolscale_la_LDFLAGS = $(SMOLSCALE_LDFLAGS) -libsmolscale_la_LIBADD = - -libsmolscale_la_SOURCES = \ - smolscale.c \ - smolscale.h \ - smolscale-private.h - -if HAVE_AVX2_INTRINSICS -noinst_LTLIBRARIES += libsmolscale-avx2.la -libsmolscale_la_LIBADD += libsmolscale-avx2.la -libsmolscale_avx2_la_SOURCES = smolscale-avx2.c -libsmolscale_avx2_la_CFLAGS = $(SMOLSCALE_CFLAGS) -mavx2 -libsmolscale_avx2_la_LDFLAGS = $(SMOLSCALE_LDFLAGS) -endif diff -Nru chafa-1.2.1/chafa/smolscale/README chafa-1.12.4/chafa/smolscale/README --- chafa-1.2.1/chafa/smolscale/README 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/README 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -Smolscale -========= - -Smolscale is a smol piece of C code for quickly scaling images to a reasonable -level of quality using CPU resources only (no GPU). It operates on 4-channel -data with 32 bits per pixel, i.e. packed RGBA, ARGB, BGRA, etc. It supports -both premultiplied and unassociated alpha and can convert between the two. It -is host byte ordering agnostic. - -The design goals are: - -* High throughput: Optimized code, within reason. Easily parallelizable. - -* Decent quality: No "jaggies" as produced by nearest-neighbor scaling. - -* Low memory overhead: Mostly on the stack. - -* Simplicity: A modern C toolchain as the only dependency. - -* Ease of use: One-shot and row-batch APIs. - -Usage ------ - -First, read COPYING. If your project meets the requirements, you should be -able to copy the following files into it and add it to your build with -minimal fuss: - - smolscale.c - smolscale.h - smolscale-private.h - -If you want AVX2 SIMD support, optionally copy this additional file and -compile everything with -DSMOL_WITH_AVX2: - - smolscale-avx2.c - -Keep in mind that this file is mostly just a straight copy of the generic -code for the time being. Still, you will get a performance boost by building -it with -mavx2 and letting Smolscale pick the implementation at runtime. - -The API documentation lives in smolscale.h along with the public declarations. diff -Nru chafa-1.2.1/chafa/smolscale/smolscale-avx2.c chafa-1.12.4/chafa/smolscale/smolscale-avx2.c --- chafa-1.2.1/chafa/smolscale/smolscale-avx2.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/smolscale-avx2.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,2435 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright (C) 2019 Hans Petter Jansson. See COPYING for details. */ - -/* So it says AVX2 on the tin, but at the moment this is mostly just a - * straight copy of the generic code. Still, it gives a nice boost if - * compiled with -O3 -mavx2. */ - -#include /* assert */ -#include /* malloc, free */ -#include /* memset */ -#include /* alloca */ -#include -#include "smolscale-private.h" - -/* --- Premultiplication --- */ - -#define INVERTED_DIV_SHIFT 21 -#define INVERTED_DIV_ROUNDING (1U << (INVERTED_DIV_SHIFT - 1)) -#define INVERTED_DIV_ROUNDING_128BPP \ - (((uint64_t) INVERTED_DIV_ROUNDING << 32) | INVERTED_DIV_ROUNDING) - -/* This table is used to divide by an integer [1..255] using only a lookup, - * multiplication and a shift. This is faster than plain division on most - * architectures. - * - * Each entry represents the integer 2097152 (1 << 21) divided by the index - * of the entry. Consequently, - * - * (v / i) ~= (v * inverted_div_table [i] + (1 << 20)) >> 21 - * - * (1 << 20) is added for nearest rounding. It would've been nice to keep - * this table in uint16_t, but alas, we need the extra bits for sufficient - * precision. */ -static const uint32_t inverted_div_table [256] = -{ - 0,2097152,1048576, 699051, 524288, 419430, 349525, 299593, - 262144, 233017, 209715, 190650, 174763, 161319, 149797, 139810, - 131072, 123362, 116508, 110376, 104858, 99864, 95325, 91181, - 87381, 83886, 80660, 77672, 74898, 72316, 69905, 67650, - 65536, 63550, 61681, 59919, 58254, 56680, 55188, 53773, - 52429, 51150, 49932, 48771, 47663, 46603, 45590, 44620, - 43691, 42799, 41943, 41121, 40330, 39569, 38836, 38130, - 37449, 36792, 36158, 35545, 34953, 34380, 33825, 33288, - 32768, 32264, 31775, 31301, 30840, 30394, 29959, 29537, - 29127, 28728, 28340, 27962, 27594, 27236, 26887, 26546, - 26214, 25891, 25575, 25267, 24966, 24672, 24385, 24105, - 23831, 23564, 23302, 23046, 22795, 22550, 22310, 22075, - 21845, 21620, 21400, 21183, 20972, 20764, 20560, 20361, - 20165, 19973, 19784, 19600, 19418, 19240, 19065, 18893, - 18725, 18559, 18396, 18236, 18079, 17924, 17772, 17623, - 17476, 17332, 17190, 17050, 16913, 16777, 16644, 16513, - 16384, 16257, 16132, 16009, 15888, 15768, 15650, 15534, - 15420, 15308, 15197, 15087, 14980, 14873, 14769, 14665, - 14564, 14463, 14364, 14266, 14170, 14075, 13981, 13888, - 13797, 13707, 13618, 13530, 13443, 13358, 13273, 13190, - 13107, 13026, 12945, 12866, 12788, 12710, 12633, 12558, - 12483, 12409, 12336, 12264, 12193, 12122, 12053, 11984, - 11916, 11848, 11782, 11716, 11651, 11586, 11523, 11460, - 11398, 11336, 11275, 11215, 11155, 11096, 11038, 10980, - 10923, 10866, 10810, 10755, 10700, 10645, 10592, 10538, - 10486, 10434, 10382, 10331, 10280, 10230, 10180, 10131, - 10082, 10034, 9986, 9939, 9892, 9846, 9800, 9754, - 9709, 9664, 9620, 9576, 9533, 9489, 9447, 9404, - 9362, 9321, 9279, 9239, 9198, 9158, 9118, 9079, - 9039, 9001, 8962, 8924, 8886, 8849, 8812, 8775, - 8738, 8702, 8666, 8630, 8595, 8560, 8525, 8490, - 8456, 8422, 8389, 8355, 8322, 8289, 8257, 8224, -}; - -/* Masking and shifting out the results is left to the caller. In - * and out may not overlap. */ -static SMOL_INLINE void -unpremul_i_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, - uint64_t * SMOL_RESTRICT out, - uint8_t alpha) -{ - out [0] = ((in [0] * (uint64_t) inverted_div_table [alpha] - + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); - out [1] = ((in [1] * (uint64_t) inverted_div_table [alpha] - + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); -} - -static SMOL_INLINE void -unpremul_p_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, - uint64_t * SMOL_RESTRICT out, - uint8_t alpha) -{ - out [0] = (((in [0] << 8) * (uint64_t) inverted_div_table [alpha]) - >> INVERTED_DIV_SHIFT); - out [1] = (((in [1] << 8) * (uint64_t) inverted_div_table [alpha]) - >> INVERTED_DIV_SHIFT); -} - -static SMOL_INLINE uint64_t -unpremul_p_to_u_64bpp (const uint64_t in, - uint8_t alpha) -{ - uint64_t in_128bpp [2]; - uint64_t out_128bpp [2]; - - in_128bpp [0] = (in & 0x000000ff000000ff); - in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; - - unpremul_p_to_u_128bpp (in_128bpp, out_128bpp, alpha); - - return (out_128bpp [0] & 0x000000ff000000ff) - | ((out_128bpp [1] & 0x000000ff000000ff) << 16); -} - -static SMOL_INLINE uint64_t -premul_u_to_p_64bpp (const uint64_t in, - uint8_t alpha) -{ - return ((in * ((uint16_t) alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; -} - -/* --- Packing --- */ - -/* It's nice to be able to shift by a negative amount */ -#define SHIFT_S(in, s) ((s >= 0) ? (in) << (s) : (in) >> -(s)) - -/* This is kind of bulky (~13 x86 insns), but it's about the same as using - * unions, and we don't have to worry about endianness. */ -#define PACK_FROM_1234_64BPP(in, a, b, c, d) \ - ((SHIFT_S ((in), ((a) - 1) * 16 + 8 - 32) & 0xff000000) \ - | (SHIFT_S ((in), ((b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in), ((c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in), ((d) - 1) * 16 + 8 - 56) & 0x000000ff)) - -#define PACK_FROM_1234_128BPP(in, a, b, c, d) \ - ((SHIFT_S ((in [((a) - 1) >> 1]), (((a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ - | (SHIFT_S ((in [((b) - 1) >> 1]), (((b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in [((c) - 1) >> 1]), (((c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in [((d) - 1) >> 1]), (((d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) - -#define SWAP_2_AND_3(n) ((n) == 2 ? 3 : (n) == 3 ? 2 : n) - -#define PACK_FROM_1324_64BPP(in, a, b, c, d) \ - ((SHIFT_S ((in), (SWAP_2_AND_3 (a) - 1) * 16 + 8 - 32) & 0xff000000) \ - | (SHIFT_S ((in), (SWAP_2_AND_3 (b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in), (SWAP_2_AND_3 (c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in), (SWAP_2_AND_3 (d) - 1) * 16 + 8 - 56) & 0x000000ff)) - -/* Note: May not be needed */ -#define PACK_FROM_1324_128BPP(in, a, b, c, d) \ - ((SHIFT_S ((in [(SWAP_2_AND_3 (a) - 1) >> 1]), \ - ((SWAP_2_AND_3 (a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ - | (SHIFT_S ((in [(SWAP_2_AND_3 (b) - 1) >> 1]), \ - ((SWAP_2_AND_3 (b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in [(SWAP_2_AND_3 (c) - 1) >> 1]), \ - ((SWAP_2_AND_3 (c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in [(SWAP_2_AND_3 (d) - 1) >> 1]), \ - ((SWAP_2_AND_3 (d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) - -/* Pack p -> p */ - -static SMOL_INLINE uint32_t -pack_pixel_1324_p_to_1234_p_64bpp (uint64_t in) -{ - return in | (in >> 24); -} - -static void -pack_row_1324_p_to_1234_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); - } -} - -static void -pack_row_132a_p_to_123_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - /* FIXME: Would be faster to shift directly */ - uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_132a_p_to_321_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - /* FIXME: Would be faster to shift directly */ - uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -#define DEF_PACK_FROM_1324_P_TO_P_64BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (uint64_t in) \ -{ \ - return PACK_FROM_1324_64BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_1324_p_to_##a##b##c##d##_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - *(row_out++) = pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (*(row_in++)); \ -} - -DEF_PACK_FROM_1324_P_TO_P_64BPP (1, 4, 3, 2) -DEF_PACK_FROM_1324_P_TO_P_64BPP (2, 3, 4, 1) -DEF_PACK_FROM_1324_P_TO_P_64BPP (3, 2, 1, 4) -DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 1, 2, 3) -DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 3, 2, 1) - -static SMOL_INLINE uint32_t -pack_pixel_1234_p_to_1234_p_128bpp (const uint64_t *in) -{ - /* FIXME: Are masks needed? */ - return ((in [0] >> 8) & 0xff000000) - | ((in [0] << 16) & 0x00ff0000) - | ((in [1] >> 24) & 0x0000ff00) - | (in [1] & 0x000000ff); -} - -static void -pack_row_1234_p_to_1234_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_1234_p_to_1234_p_128bpp (row_in); - row_in += 2; - } -} - -#define DEF_PACK_FROM_1234_P_TO_P_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - return PACK_FROM_1234_128BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_1234_P_TO_P_128BPP (1, 4, 3, 2) -DEF_PACK_FROM_1234_P_TO_P_128BPP (2, 3, 4, 1) -DEF_PACK_FROM_1234_P_TO_P_128BPP (3, 2, 1, 4) -DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 1, 2, 3) -DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 3, 2, 1) - -static void -pack_row_123a_p_to_123_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = *row_in >> 32; - *(row_out++) = *(row_in++); - *(row_out++) = *(row_in++) >> 32; - } -} - -static void -pack_row_123a_p_to_321_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = row_in [1] >> 32; - *(row_out++) = row_in [0]; - *(row_out++) = row_in [0] >> 32; - row_in += 2; - } -} - -/* Pack p (alpha last) -> u */ - -static SMOL_INLINE uint32_t -pack_pixel_132a_p_to_1234_u_64bpp (uint64_t in) -{ - uint8_t alpha = in; - in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; - return in | (in >> 24); -} - -static void -pack_row_132a_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); - } -} - -static void -pack_row_132a_p_to_123_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_132a_p_to_321_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -#define DEF_PACK_FROM_132A_P_TO_U_64BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ -{ \ - uint8_t alpha = in; \ - in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; \ - return PACK_FROM_1324_64BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_132a_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - *(row_out++) = pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ -} - -DEF_PACK_FROM_132A_P_TO_U_64BPP (3, 2, 1, 4) -DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 1, 2, 3) -DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 3, 2, 1) - -#define DEF_PACK_FROM_123A_P_TO_U_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - uint64_t t [2]; \ - uint8_t alpha = in [1]; \ - unpremul_p_to_u_128bpp (in, t, alpha); \ - t [1] = (t [1] & 0xffffffff00000000) | alpha; \ - return PACK_FROM_1234_128BPP (t, a, b, c, d); \ -} \ - \ -static void \ -pack_row_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_123A_P_TO_U_128BPP (1, 2, 3, 4) -DEF_PACK_FROM_123A_P_TO_U_128BPP (3, 2, 1, 4) -DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 1, 2, 3) -DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 3, 2, 1) - -static void -pack_row_123a_p_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_123a_p_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -/* Pack p (alpha first) -> u */ - -static SMOL_INLINE uint32_t -pack_pixel_a324_p_to_1234_u_64bpp (uint64_t in) -{ - uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ - in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); - return in | (in >> 24); -} - -static void -pack_row_a324_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); - } -} - -static void -pack_row_a324_p_to_234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - *(row_out++) = p; - } -} - -static void -pack_row_a324_p_to_432_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - } -} - -#define DEF_PACK_FROM_A324_P_TO_U_64BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ -{ \ - uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ \ - in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); \ - return PACK_FROM_1324_64BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_a324_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - *(row_out++) = pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ -} - -DEF_PACK_FROM_A324_P_TO_U_64BPP (1, 4, 3, 2) -DEF_PACK_FROM_A324_P_TO_U_64BPP (2, 3, 4, 1) -DEF_PACK_FROM_A324_P_TO_U_64BPP (4, 3, 2, 1) - -#define DEF_PACK_FROM_A234_P_TO_U_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - uint64_t t [2]; \ - uint8_t alpha = in [0] >> 32; \ - unpremul_p_to_u_128bpp (in, t, alpha); \ - t [0] = (t [0] & 0x00000000ffffffff) | ((uint64_t) alpha << 32); \ - return PACK_FROM_1234_128BPP (t, a, b, c, d); \ -} \ - \ -static void \ -pack_row_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 2, 3, 4) -DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 4, 3, 2) -DEF_PACK_FROM_A234_P_TO_U_128BPP (2, 3, 4, 1) -DEF_PACK_FROM_A234_P_TO_U_128BPP (4, 3, 2, 1) - -static void -pack_row_a234_p_to_234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - *(row_out++) = p; - } -} - -static void -pack_row_a234_p_to_432_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - } -} - -/* Pack i (alpha last) to u */ - -static SMOL_INLINE uint32_t -pack_pixel_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT in) -{ - uint8_t alpha = in [1] & 0xff; - uint64_t t [2]; - - unpremul_i_to_u_128bpp (in, t, alpha); - - return ((t [0] >> 8) & 0xff000000) - | ((t [0] << 16) & 0x00ff0000) - | ((t [1] >> 24) & 0x0000ff00) - | alpha; -} - -static void -pack_row_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_123a_i_to_1234_u_128bpp (row_in); - row_in += 2; - } -} - -static void -pack_row_123a_i_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_123a_i_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -#define DEF_PACK_FROM_123A_I_TO_U_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - uint8_t alpha = in [1] & 0xff; \ - uint64_t t [2]; \ - unpremul_i_to_u_128bpp (in, t, alpha); \ - t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ - return PACK_FROM_1234_128BPP (t, a, b, c, d); \ -} \ - \ -static void \ -pack_row_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_123A_I_TO_U_128BPP(3, 2, 1, 4) -DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 1, 2, 3) -DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 3, 2, 1) - -/* Unpack p -> p */ - -static SMOL_INLINE uint64_t -unpack_pixel_1234_p_to_1324_p_64bpp (uint32_t p) -{ - return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); -} - -/* AVX2 has a useful instruction for this: __m256i _mm256_cvtepu8_epi16 (__m128i a); - * It results in a different channel ordering, so it'd be important to match with - * the right kind of re-pack. */ -static void -unpack_row_1234_p_to_1324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_1234_p_to_1324_p_64bpp (*(row_in++)); - } -} - -static SMOL_INLINE uint64_t -unpack_pixel_123_p_to_132a_p_64bpp (const uint8_t *p) -{ - return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) - | ((uint64_t) p [2] << 32) | 0xff; -} - -static void -unpack_row_123_p_to_132a_p_64bpp (const uint8_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_123_p_to_132a_p_64bpp (row_in); - row_in += 3; - } -} - -static SMOL_INLINE void -unpack_pixel_1234_p_to_1234_p_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = p; - out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); - out [1] = ((p64 & 0x0000ff00) << 24) | (p64 & 0x000000ff); -} - -static void -unpack_row_1234_p_to_1234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_1234_p_to_1234_p_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -static SMOL_INLINE void -unpack_pixel_123_p_to_123a_p_128bpp (const uint8_t *in, - uint64_t *out) -{ - out [0] = ((uint64_t) in [0] << 32) | in [1]; - out [1] = ((uint64_t) in [2] << 32) | 0xff; -} - -static void -unpack_row_123_p_to_123a_p_128bpp (const uint8_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_123_p_to_123a_p_128bpp (row_in, row_out); - row_in += 3; - row_out += 2; - } -} - -/* Unpack u (alpha first) -> p */ - -static SMOL_INLINE uint64_t -unpack_pixel_a234_u_to_a324_p_64bpp (uint32_t p) -{ - uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); - uint8_t alpha = p >> 24; - - return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); -} - -static void -unpack_row_a234_u_to_a324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_a234_u_to_a324_p_64bpp (*(row_in++)); - } -} - -static SMOL_INLINE void -unpack_pixel_a234_u_to_a234_p_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); - uint8_t alpha = p >> 24; - - p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); - out [0] = (p64 >> 16) & 0x000000ff000000ff; - out [1] = p64 & 0x000000ff000000ff; -} - -static void -unpack_row_a234_u_to_a234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_a234_u_to_a234_p_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* Unpack u (alpha first) -> i */ - -static SMOL_INLINE void -unpack_pixel_a234_u_to_234a_i_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = p; - uint64_t alpha = p >> 24; - - out [0] = (((((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8)) * alpha)); - out [1] = (((((p64 & 0x000000ff) << 32) * alpha))) | alpha; -} - -static void -unpack_row_a234_u_to_234a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_a234_u_to_234a_i_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* Unpack u (alpha last) -> p */ - -static SMOL_INLINE uint64_t -unpack_pixel_123a_u_to_132a_p_64bpp (uint32_t p) -{ - uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); - uint8_t alpha = p & 0xff; - - return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); -} - -static void -unpack_row_123a_u_to_132a_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_123a_u_to_132a_p_64bpp (*(row_in++)); - } -} - -static SMOL_INLINE void -unpack_pixel_123a_u_to_123a_p_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); - uint8_t alpha = p & 0xff; - - p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); - out [0] = (p64 >> 16) & 0x000000ff000000ff; - out [1] = p64 & 0x000000ff000000ff; -} - -static void -unpack_row_123a_u_to_123a_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_123a_u_to_123a_p_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* Unpack u (alpha last) -> i */ - -static SMOL_INLINE void -unpack_pixel_123a_u_to_123a_i_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = p; - uint64_t alpha = p & 0xff; - - out [0] = (((((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16)) * alpha)); - out [1] = (((((p64 & 0x0000ff00) << 24) * alpha))) | alpha; -} - -static void -unpack_row_123a_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_123a_u_to_123a_i_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* --- Filter helpers --- */ - -static SMOL_INLINE const uint32_t * -inrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, - uint32_t inrow_ofs) -{ - return scale_ctx->pixels_in + scale_ctx->rowstride_in * inrow_ofs; -} - -static SMOL_INLINE uint32_t * -outrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, - uint32_t outrow_ofs) -{ - return scale_ctx->pixels_out + scale_ctx->rowstride_out * outrow_ofs; -} - -static SMOL_INLINE uint64_t -weight_pixel_64bpp (uint64_t p, - uint16_t w) -{ - return ((p * w) >> 8) & 0x00ff00ff00ff00ff; -} - -/* p and out may be the same address */ -static SMOL_INLINE void -weight_pixel_128bpp (uint64_t *p, - uint64_t *out, - uint16_t w) -{ - out [0] = ((p [0] * w) >> 8) & 0x00ffffff00ffffffULL; - out [1] = ((p [1] * w) >> 8) & 0x00ffffff00ffffffULL; -} - -static SMOL_INLINE void -sum_parts_64bpp (const uint64_t ** SMOL_RESTRICT parts_in, - uint64_t * SMOL_RESTRICT accum, - uint32_t n) -{ - const uint64_t *pp_end; - const uint64_t * SMOL_RESTRICT pp = *parts_in; - - SMOL_ASSUME_ALIGNED (pp, const uint64_t *, sizeof (uint64_t)); - - for (pp_end = pp + n; pp < pp_end; pp++) - { - *accum += *pp; - } - - *parts_in = pp; -} - -static SMOL_INLINE void -sum_parts_128bpp (const uint64_t ** SMOL_RESTRICT parts_in, - uint64_t * SMOL_RESTRICT accum, - uint32_t n) -{ - const uint64_t *pp_end; - const uint64_t * SMOL_RESTRICT pp = *parts_in; - - SMOL_ASSUME_ALIGNED (pp, const uint64_t *, sizeof (uint64_t) * 2); - - for (pp_end = pp + n * 2; pp < pp_end; ) - { - accum [0] += *(pp++); - accum [1] += *(pp++); - } - - *parts_in = pp; -} - -static SMOL_INLINE uint64_t -scale_64bpp (uint64_t accum, - uint64_t multiplier) -{ - uint64_t a, b; - - /* Average the inputs */ - a = ((accum & 0x0000ffff0000ffffULL) * multiplier - + (BOXES_MULTIPLIER / 2) + ((BOXES_MULTIPLIER / 2) << 32)) / BOXES_MULTIPLIER; - b = (((accum & 0xffff0000ffff0000ULL) >> 16) * multiplier - + (BOXES_MULTIPLIER / 2) + ((BOXES_MULTIPLIER / 2) << 32)) / BOXES_MULTIPLIER; - - /* Return pixel */ - return (a & 0x000000ff000000ffULL) | ((b & 0x000000ff000000ffULL) << 16); -} - -static SMOL_INLINE uint64_t -scale_128bpp_half (uint64_t accum, - uint64_t multiplier) -{ - uint64_t a, b; - - a = accum & 0x00000000ffffffffULL; - a = (a * multiplier + BOXES_MULTIPLIER / 2) / BOXES_MULTIPLIER; - - b = (accum & 0xffffffff00000000ULL) >> 32; - b = (b * multiplier + BOXES_MULTIPLIER / 2) / BOXES_MULTIPLIER; - - return (a & 0x000000000000ffffULL) - | ((b & 0x000000000000ffffULL) << 32); -} - -static SMOL_INLINE void -scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, - uint64_t multiplier, - uint64_t ** SMOL_RESTRICT row_parts_out) -{ - *(*row_parts_out)++ = scale_128bpp_half (accum [0], multiplier); - *(*row_parts_out)++ = scale_128bpp_half (accum [1], multiplier); -} - -static void -add_parts (const uint64_t * SMOL_RESTRICT parts_in, - uint64_t * SMOL_RESTRICT parts_acc_out, - uint32_t n) -{ - const uint64_t *parts_in_max = parts_in + n; - - SMOL_ASSUME_TEMP_ALIGNED (parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_acc_out, uint64_t *); - - while (parts_in < parts_in_max) - *(parts_acc_out++) += *(parts_in++); -} - -/* --- Horizontal scaling --- */ - -#define DEF_INTERP_HORIZONTAL_BILINEAR(n_halvings) \ -static void \ -interp_horizontal_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ - const uint64_t * SMOL_RESTRICT row_parts_in, \ - uint64_t * SMOL_RESTRICT row_parts_out) \ -{ \ - uint64_t p, q; \ - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ - uint64_t F; \ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; \ - int i; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); \ - \ - do \ - { \ - uint64_t accum = 0; \ - \ - for (i = 0; i < (1 << (n_halvings)); i++) \ - { \ - row_parts_in += *(ofs_x++); \ - F = *(ofs_x++); \ - \ - p = *row_parts_in; \ - q = *(row_parts_in + 1); \ - \ - accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ - } \ - *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ - } \ - while (row_parts_out != row_parts_out_max); \ -} \ - \ -static void \ -interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ - const uint64_t * SMOL_RESTRICT row_parts_in, \ - uint64_t * SMOL_RESTRICT row_parts_out) \ -{ \ - uint64_t p, q; \ - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ - uint64_t F; \ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; \ - int i; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); \ - \ - do \ - { \ - uint64_t accum [2] = { 0 }; \ - \ - for (i = 0; i < (1 << (n_halvings)); i++) \ - { \ - row_parts_in += *(ofs_x++) * 2; \ - F = *(ofs_x++); \ - \ - p = row_parts_in [0]; \ - q = row_parts_in [2]; \ - \ - accum [0] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ - \ - p = row_parts_in [1]; \ - q = row_parts_in [3]; \ - \ - accum [1] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ - } \ - *(row_parts_out++) = ((accum [0]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ - *(row_parts_out++) = ((accum [1]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ - } \ - while (row_parts_out != row_parts_out_max); \ -} - -static void -interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t p, q; - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; - uint64_t F; - uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - do - { - row_parts_in += *(ofs_x++); - F = *(ofs_x++); - - p = *row_parts_in; - q = *(row_parts_in + 1); - - *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; - } - while (row_parts_out != row_parts_out_max); -} - -static void -interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t p, q; - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; - uint64_t F; - uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - do - { - row_parts_in += *(ofs_x++) * 2; - F = *(ofs_x++); - - p = row_parts_in [0]; - q = row_parts_in [2]; - - *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - - p = row_parts_in [1]; - q = row_parts_in [3]; - - *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - } - while (row_parts_out != row_parts_out_max); -} - -DEF_INTERP_HORIZONTAL_BILINEAR(1) -DEF_INTERP_HORIZONTAL_BILINEAR(2) -DEF_INTERP_HORIZONTAL_BILINEAR(3) -DEF_INTERP_HORIZONTAL_BILINEAR(4) -DEF_INTERP_HORIZONTAL_BILINEAR(5) -DEF_INTERP_HORIZONTAL_BILINEAR(6) - -static void -interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, - const uint64_t *row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - const uint64_t * SMOL_RESTRICT pp; - const uint16_t *ofs_x = scale_ctx->offsets_x; - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out - 1; - uint64_t accum = 0; - uint64_t p, q, r, s; - uint32_t n; - uint64_t F; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - pp = row_parts_in; - p = weight_pixel_64bpp (*(pp++), 256); - n = *(ofs_x++); - - while (row_parts_out != row_parts_out_max) - { - sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); - - F = *(ofs_x++); - n = *(ofs_x++); - - r = *(pp++); - s = r * F; - - q = (s >> 8) & 0x00ff00ff00ff00ffULL; - - accum += p + q; - - /* (255 * r) - (F * r) */ - p = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; - - *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); - accum = 0; - } - - /* Final box optionally features the rightmost fractional pixel */ - - sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); - - q = 0; - F = *(ofs_x); - if (F > 0) - q = weight_pixel_64bpp (*(pp), F); - - accum += p + q; - *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); -} - -static void -interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, - const uint64_t *row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - const uint64_t * SMOL_RESTRICT pp; - const uint16_t *ofs_x = scale_ctx->offsets_x; - uint64_t *row_parts_out_max = row_parts_out + (scale_ctx->width_out - /* 2 */ 1) * 2; - uint64_t accum [2] = { 0, 0 }; - uint64_t p [2], q [2], r [2], s [2]; - uint32_t n; - uint64_t F; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - pp = row_parts_in; - - p [0] = *(pp++); - p [1] = *(pp++); - weight_pixel_128bpp (p, p, 256); - - n = *(ofs_x++); - - while (row_parts_out != row_parts_out_max) - { - sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); - - F = *(ofs_x++); - n = *(ofs_x++); - - r [0] = *(pp++); - r [1] = *(pp++); - - s [0] = r [0] * F; - s [1] = r [1] * F; - - q [0] = (s [0] >> 8) & 0x00ffffff00ffffff; - q [1] = (s [1] >> 8) & 0x00ffffff00ffffff; - - accum [0] += p [0] + q [0]; - accum [1] += p [1] + q [1]; - - p [0] = (((r [0] << 8) - r [0] - s [0]) >> 8) & 0x00ffffff00ffffff; - p [1] = (((r [1] << 8) - r [1] - s [1]) >> 8) & 0x00ffffff00ffffff; - - scale_and_store_128bpp (accum, - scale_ctx->span_mul_x, - (uint64_t ** SMOL_RESTRICT) &row_parts_out); - - accum [0] = 0; - accum [1] = 0; - } - - /* Final box optionally features the rightmost fractional pixel */ - - sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); - - q [0] = 0; - q [1] = 0; - - F = *(ofs_x); - if (F > 0) - { - q [0] = *(pp++); - q [1] = *(pp++); - weight_pixel_128bpp (q, q, F); - } - - accum [0] += p [0] + q [0]; - accum [1] += p [1] + q [1]; - - scale_and_store_128bpp (accum, - scale_ctx->span_mul_x, - (uint64_t ** SMOL_RESTRICT) &row_parts_out); -} - -static void -interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; - uint64_t part; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - part = *row_parts_in; - while (row_parts_out != row_parts_out_max) - *(row_parts_out++) = part; -} - -static void -interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - while (row_parts_out != row_parts_out_max) - { - *(row_parts_out++) = row_parts_in [0]; - *(row_parts_out++) = row_parts_in [1]; - } -} - -static void -scale_horizontal (const SmolScaleCtx *scale_ctx, - const uint32_t *row_in, - uint64_t *row_parts_out) -{ - uint64_t * SMOL_RESTRICT unpacked_in; - - /* FIXME: Allocate less for 64bpp */ - unpacked_in = aligned_alloca (scale_ctx->width_in * sizeof (uint64_t) * 2, SMOL_TEMP_ALIGNMENT); - - scale_ctx->unpack_row_func (row_in, - unpacked_in, - scale_ctx->width_in); - scale_ctx->hfilter_func (scale_ctx, - unpacked_in, - row_parts_out); -} - -/* --- Vertical scaling --- */ - -static void -update_vertical_ctx_bilinear (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index) -{ - uint32_t new_in_ofs = scale_ctx->offsets_y [outrow_index * 2]; - - if (new_in_ofs == vertical_ctx->in_ofs) - return; - - if (new_in_ofs == vertical_ctx->in_ofs + 1) - { - uint64_t *t = vertical_ctx->parts_row [0]; - vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; - vertical_ctx->parts_row [1] = t; - - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), - vertical_ctx->parts_row [1]); - } - else - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, new_in_ofs), - vertical_ctx->parts_row [0]); - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), - vertical_ctx->parts_row [1]); - } - - vertical_ctx->in_ofs = new_in_ofs; -} - -static void -interp_vertical_bilinear_store_64bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT top_row_parts_in, - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, - uint64_t * SMOL_RESTRICT parts_out, - uint32_t width) -{ - uint64_t *parts_out_last = parts_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; - } - while (parts_out != parts_out_last); -} - -static void -interp_vertical_bilinear_add_64bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT top_row_parts_in, - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, - uint64_t * SMOL_RESTRICT accum_out, - uint32_t width) -{ - uint64_t *accum_out_last = accum_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (accum_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; - } - while (accum_out != accum_out_last); -} - -static void -interp_vertical_bilinear_store_128bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT SMOL_ALIGNED_64 top_row_parts_in, - const uint64_t * SMOL_RESTRICT SMOL_ALIGNED_64 bottom_row_parts_in, - uint64_t * SMOL_RESTRICT SMOL_ALIGNED_64 parts_out, - uint32_t width) -{ - uint64_t *parts_out_last = parts_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - } - while (parts_out != parts_out_last); -} - -static void -interp_vertical_bilinear_add_128bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT top_row_parts_in, - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, - uint64_t * SMOL_RESTRICT accum_out, - uint32_t width) -{ - uint64_t *accum_out_last = accum_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (accum_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - } - while (accum_out != accum_out_last); -} - -#define DEF_INTERP_VERTICAL_BILINEAR_FINAL(n_halvings) \ -static void \ -interp_vertical_bilinear_final_##n_halvings##h_64bpp (uint64_t F, \ - const uint64_t * SMOL_RESTRICT top_row_parts_in, \ - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ - uint64_t * SMOL_RESTRICT accum_inout, \ - uint32_t width) \ -{ \ - uint64_t *accum_inout_last = accum_inout + width; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (accum_inout, uint64_t *); \ - \ - do \ - { \ - uint64_t p, q; \ - \ - p = *(top_row_parts_in++); \ - q = *(bottom_row_parts_in++); \ - \ - p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ - p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ - \ - *(accum_inout++) = p; \ - } \ - while (accum_inout != accum_inout_last); \ -} \ - \ -static void \ -interp_vertical_bilinear_final_##n_halvings##h_128bpp (uint64_t F, \ - const uint64_t * SMOL_RESTRICT top_row_parts_in, \ - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ - uint64_t * SMOL_RESTRICT accum_inout, \ - uint32_t width) \ -{ \ - uint64_t *accum_inout_last = accum_inout + width; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (accum_inout, uint64_t *); \ - \ - do \ - { \ - uint64_t p, q; \ - \ - p = *(top_row_parts_in++); \ - q = *(bottom_row_parts_in++); \ - \ - p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ - p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ - \ - *(accum_inout++) = p; \ - } \ - while (accum_inout != accum_inout_last); \ -} - -#define DEF_SCALE_OUTROW_BILINEAR(n_halvings) \ -static void \ -scale_outrow_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ - SmolVerticalCtx *vertical_ctx, \ - uint32_t outrow_index, \ - uint32_t *row_out) \ -{ \ - uint32_t bilin_index = outrow_index << (n_halvings); \ - unsigned int i; \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out); \ - bilin_index++; \ - \ - for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ - { \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_add_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out); \ - bilin_index++; \ - } \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_final_##n_halvings##h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out); \ - \ - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ -} \ - \ -static void \ -scale_outrow_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ - SmolVerticalCtx *vertical_ctx, \ - uint32_t outrow_index, \ - uint32_t *row_out) \ -{ \ - uint32_t bilin_index = outrow_index << (n_halvings); \ - unsigned int i; \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out * 2); \ - bilin_index++; \ - \ - for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ - { \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_add_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out * 2); \ - bilin_index++; \ - } \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_final_##n_halvings##h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out * 2); \ - \ - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ -} - -static void -scale_outrow_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); - interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); - interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out * 2); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) - -static void -scale_outrow_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t bilin_index = outrow_index << 1; - - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - bilin_index++; - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_final_1h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t bilin_index = outrow_index << 1; - - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out * 2); - bilin_index++; - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_final_1h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out * 2); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) -DEF_SCALE_OUTROW_BILINEAR(2) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) -DEF_SCALE_OUTROW_BILINEAR(3) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) -DEF_SCALE_OUTROW_BILINEAR(4) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) -DEF_SCALE_OUTROW_BILINEAR(5) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) -DEF_SCALE_OUTROW_BILINEAR(6) - -static void -finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, - uint64_t multiplier, - uint64_t * SMOL_RESTRICT parts_out, - uint32_t n) -{ - uint64_t *parts_out_max = parts_out + n; - - SMOL_ASSUME_TEMP_ALIGNED (accums, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - while (parts_out != parts_out_max) - { - *(parts_out++) = scale_64bpp (*(accums++), multiplier); - } -} - -static void -weight_edge_row_64bpp (uint64_t *row, - uint16_t w, - uint32_t n) -{ - uint64_t *row_max = row + n; - - SMOL_ASSUME_TEMP_ALIGNED (row, uint64_t *); - - while (row != row_max) - { - *row = ((*row * w) >> 8) & 0x00ff00ff00ff00ffULL; - row++; - } -} - -static void -scale_and_weight_edge_rows_box_64bpp (const uint64_t * SMOL_RESTRICT first_row, - uint64_t * SMOL_RESTRICT last_row, - uint64_t * SMOL_RESTRICT accum, - uint16_t w2, - uint32_t n) -{ - const uint64_t *first_row_max = first_row + n; - - SMOL_ASSUME_TEMP_ALIGNED (first_row, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (last_row, uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (accum, uint64_t *); - - while (first_row != first_row_max) - { - uint64_t r, s, p, q; - - p = *(first_row++); - - r = *(last_row); - s = r * w2; - q = (s >> 8) & 0x00ff00ff00ff00ffULL; - /* (255 * r) - (F * r) */ - *(last_row++) = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; - - *(accum++) = p + q; - } -} - -static void -update_vertical_ctx_box_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t ofs_y, - uint32_t ofs_y_max, - uint16_t w1, - uint16_t w2) -{ - /* Old in_ofs is the previous max */ - if (ofs_y == vertical_ctx->in_ofs) - { - uint64_t *t = vertical_ctx->parts_row [0]; - vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; - vertical_ctx->parts_row [1] = t; - } - else - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [0]); - weight_edge_row_64bpp (vertical_ctx->parts_row [0], w1, scale_ctx->width_out); - } - - /* When w2 == 0, the final inrow may be out of bounds. Don't try to access it in - * that case. */ - if (w2 || ofs_y_max < scale_ctx->height_in) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y_max), - vertical_ctx->parts_row [1]); - } - else - { - memset (vertical_ctx->parts_row [1], 0, scale_ctx->width_out * sizeof (uint64_t)); - } - - vertical_ctx->in_ofs = ofs_y_max; -} - -static void -scale_outrow_box_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t ofs_y, ofs_y_max; - uint16_t w1, w2; - - /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ - - ofs_y = scale_ctx->offsets_y [outrow_index * 2]; - ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; - - /* Scale the first and last rows, weight them and store in accumulator */ - - w1 = (outrow_index == 0) ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1]; - w2 = scale_ctx->offsets_y [outrow_index * 2 + 1]; - - update_vertical_ctx_box_64bpp (scale_ctx, vertical_ctx, ofs_y, ofs_y_max, w1, w2); - - scale_and_weight_edge_rows_box_64bpp (vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - w2, - scale_ctx->width_out); - - ofs_y++; - - /* Add up whole rows */ - - while (ofs_y < ofs_y_max) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [0]); - add_parts (vertical_ctx->parts_row [0], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - - ofs_y++; - } - - finalize_vertical_64bpp (vertical_ctx->parts_row [2], - scale_ctx->span_mul_y, - vertical_ctx->parts_row [0], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); -} - -static void -finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, - uint64_t multiplier, - uint64_t * SMOL_RESTRICT parts_out, - uint32_t n) -{ - uint64_t *parts_out_max = parts_out + n * 2; - - SMOL_ASSUME_TEMP_ALIGNED (accums, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - while (parts_out != parts_out_max) - { - *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); - *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); - } -} - -static void -weight_row_128bpp (uint64_t *row, - uint16_t w, - uint32_t n) -{ - uint64_t *row_max = row + (n * 2); - - SMOL_ASSUME_TEMP_ALIGNED (row, uint64_t *); - - while (row != row_max) - { - row [0] = ((row [0] * w) >> 8) & 0x00ffffff00ffffffULL; - row [1] = ((row [1] * w) >> 8) & 0x00ffffff00ffffffULL; - row += 2; - } -} - -static void -scale_outrow_box_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t ofs_y, ofs_y_max; - uint16_t w; - - /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ - - ofs_y = scale_ctx->offsets_y [outrow_index * 2]; - ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; - - /* Scale the first inrow and store it */ - - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [0]); - weight_row_128bpp (vertical_ctx->parts_row [0], - outrow_index == 0 ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1], - scale_ctx->width_out); - ofs_y++; - - /* Add up whole rows */ - - while (ofs_y < ofs_y_max) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [1]); - add_parts (vertical_ctx->parts_row [1], - vertical_ctx->parts_row [0], - scale_ctx->width_out * 2); - - ofs_y++; - } - - /* Final row is optional; if this is the bottommost outrow it could be out of bounds */ - - w = scale_ctx->offsets_y [outrow_index * 2 + 1]; - if (w > 0) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [1]); - weight_row_128bpp (vertical_ctx->parts_row [1], - w - 1, /* Subtract 1 to avoid overflow */ - scale_ctx->width_out); - add_parts (vertical_ctx->parts_row [1], - vertical_ctx->parts_row [0], - scale_ctx->width_out * 2); - } - - finalize_vertical_128bpp (vertical_ctx->parts_row [0], - scale_ctx->span_mul_y, - vertical_ctx->parts_row [1], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [1], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_one_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t row_index, - uint32_t *row_out) -{ - SMOL_UNUSED (row_index); - - /* Scale the row and store it */ - - if (vertical_ctx->in_ofs != 0) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, 0), - vertical_ctx->parts_row [0]); - vertical_ctx->in_ofs = 0; - } - - scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_one_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t row_index, - uint32_t *row_out) -{ - SMOL_UNUSED (row_index); - - /* Scale the row and store it */ - - if (vertical_ctx->in_ofs != 0) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, 0), - vertical_ctx->parts_row [0]); - vertical_ctx->in_ofs = 0; - } - - scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); -} - -/* --- Conversion tables --- */ - -static const SmolConversionTable avx2_conversions = -{ -{ { - /* Conversions where accumulators must hold the sum of fewer than - * 256 pixels. This can be done in 64bpp, but 128bpp may be used - * e.g. for 16 bits per channel internally premultiplied data. */ - - /* RGBA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), - }, - /* BGRA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), - }, - /* ARGB8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), - }, - /* ABGR8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), - }, - /* RGBA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), - /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), - /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), - /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - }, - /* BGRA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), - /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), - /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), - /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - }, - /* ARGB8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), - /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), - /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), - /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - }, - /* ABGR8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), - /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), - /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), - /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - }, - /* RGB8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), - /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), - }, - /* BGR8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), - /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), - } - }, - - { - /* Conversions where accumulators must hold the sum of up to - * 65535 pixels. We need 128bpp for this. */ - - /* RGBA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), - }, - /* BGRA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), - }, - /* ARGB8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), - }, - /* ABGR8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), - }, - /* RGBA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), - /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), - /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), - /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - }, - /* BGRA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), - /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), - /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), - /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - }, - /* ARGB8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), - /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), - /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), - /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - }, - /* ABGR8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), - /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), - /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), - /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - }, - /* RGB8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), - /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), - }, - /* BGR8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), - /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), - } -} } -}; - -static const SmolImplementation avx2_implementation = -{ - { - /* Horizontal filters */ - { - /* 64bpp */ - interp_horizontal_one_64bpp, - interp_horizontal_bilinear_0h_64bpp, - interp_horizontal_bilinear_1h_64bpp, - interp_horizontal_bilinear_2h_64bpp, - interp_horizontal_bilinear_3h_64bpp, - interp_horizontal_bilinear_4h_64bpp, - interp_horizontal_bilinear_5h_64bpp, - interp_horizontal_bilinear_6h_64bpp, - interp_horizontal_boxes_64bpp - }, - { - /* 128bpp */ - interp_horizontal_one_128bpp, - interp_horizontal_bilinear_0h_128bpp, - interp_horizontal_bilinear_1h_128bpp, - interp_horizontal_bilinear_2h_128bpp, - interp_horizontal_bilinear_3h_128bpp, - interp_horizontal_bilinear_4h_128bpp, - interp_horizontal_bilinear_5h_128bpp, - interp_horizontal_bilinear_6h_128bpp, - interp_horizontal_boxes_128bpp - } - }, - { - /* Vertical filters */ - { - /* 64bpp */ - scale_outrow_one_64bpp, - scale_outrow_bilinear_0h_64bpp, - scale_outrow_bilinear_1h_64bpp, - scale_outrow_bilinear_2h_64bpp, - scale_outrow_bilinear_3h_64bpp, - scale_outrow_bilinear_4h_64bpp, - scale_outrow_bilinear_5h_64bpp, - scale_outrow_bilinear_6h_64bpp, - scale_outrow_box_64bpp - }, - { - /* 128bpp */ - scale_outrow_one_128bpp, - scale_outrow_bilinear_0h_128bpp, - scale_outrow_bilinear_1h_128bpp, - scale_outrow_bilinear_2h_128bpp, - scale_outrow_bilinear_3h_128bpp, - scale_outrow_bilinear_4h_128bpp, - scale_outrow_bilinear_5h_128bpp, - scale_outrow_bilinear_6h_128bpp, - scale_outrow_box_128bpp - } - }, - &avx2_conversions -}; - -const SmolImplementation * -_smol_get_avx2_implementation (void) -{ - return &avx2_implementation; -} diff -Nru chafa-1.2.1/chafa/smolscale/smolscale.c chafa-1.12.4/chafa/smolscale/smolscale.c --- chafa-1.2.1/chafa/smolscale/smolscale.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/smolscale.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,2980 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright © 2019 Hans Petter Jansson. See COPYING for details. */ - -#include /* assert */ -#include /* malloc, free */ -#include /* memset */ -#include /* alloca */ -#include -#include "smolscale-private.h" - -/* --- Premultiplication --- */ - -#define INVERTED_DIV_SHIFT 21 -#define INVERTED_DIV_ROUNDING (1U << (INVERTED_DIV_SHIFT - 1)) -#define INVERTED_DIV_ROUNDING_128BPP \ - (((uint64_t) INVERTED_DIV_ROUNDING << 32) | INVERTED_DIV_ROUNDING) - -/* This table is used to divide by an integer [1..255] using only a lookup, - * multiplication and a shift. This is faster than plain division on most - * architectures. - * - * Each entry represents the integer 2097152 (1 << 21) divided by the index - * of the entry. Consequently, - * - * (v / i) ~= (v * inverted_div_table [i] + (1 << 20)) >> 21 - * - * (1 << 20) is added for nearest rounding. It would've been nice to keep - * this table in uint16_t, but alas, we need the extra bits for sufficient - * precision. */ -static const uint32_t inverted_div_table [256] = -{ - 0,2097152,1048576, 699051, 524288, 419430, 349525, 299593, - 262144, 233017, 209715, 190650, 174763, 161319, 149797, 139810, - 131072, 123362, 116508, 110376, 104858, 99864, 95325, 91181, - 87381, 83886, 80660, 77672, 74898, 72316, 69905, 67650, - 65536, 63550, 61681, 59919, 58254, 56680, 55188, 53773, - 52429, 51150, 49932, 48771, 47663, 46603, 45590, 44620, - 43691, 42799, 41943, 41121, 40330, 39569, 38836, 38130, - 37449, 36792, 36158, 35545, 34953, 34380, 33825, 33288, - 32768, 32264, 31775, 31301, 30840, 30394, 29959, 29537, - 29127, 28728, 28340, 27962, 27594, 27236, 26887, 26546, - 26214, 25891, 25575, 25267, 24966, 24672, 24385, 24105, - 23831, 23564, 23302, 23046, 22795, 22550, 22310, 22075, - 21845, 21620, 21400, 21183, 20972, 20764, 20560, 20361, - 20165, 19973, 19784, 19600, 19418, 19240, 19065, 18893, - 18725, 18559, 18396, 18236, 18079, 17924, 17772, 17623, - 17476, 17332, 17190, 17050, 16913, 16777, 16644, 16513, - 16384, 16257, 16132, 16009, 15888, 15768, 15650, 15534, - 15420, 15308, 15197, 15087, 14980, 14873, 14769, 14665, - 14564, 14463, 14364, 14266, 14170, 14075, 13981, 13888, - 13797, 13707, 13618, 13530, 13443, 13358, 13273, 13190, - 13107, 13026, 12945, 12866, 12788, 12710, 12633, 12558, - 12483, 12409, 12336, 12264, 12193, 12122, 12053, 11984, - 11916, 11848, 11782, 11716, 11651, 11586, 11523, 11460, - 11398, 11336, 11275, 11215, 11155, 11096, 11038, 10980, - 10923, 10866, 10810, 10755, 10700, 10645, 10592, 10538, - 10486, 10434, 10382, 10331, 10280, 10230, 10180, 10131, - 10082, 10034, 9986, 9939, 9892, 9846, 9800, 9754, - 9709, 9664, 9620, 9576, 9533, 9489, 9447, 9404, - 9362, 9321, 9279, 9239, 9198, 9158, 9118, 9079, - 9039, 9001, 8962, 8924, 8886, 8849, 8812, 8775, - 8738, 8702, 8666, 8630, 8595, 8560, 8525, 8490, - 8456, 8422, 8389, 8355, 8322, 8289, 8257, 8224, -}; - -/* Masking and shifting out the results is left to the caller. In - * and out may not overlap. */ -static SMOL_INLINE void -unpremul_i_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, - uint64_t * SMOL_RESTRICT out, - uint8_t alpha) -{ - out [0] = ((in [0] * (uint64_t) inverted_div_table [alpha] - + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); - out [1] = ((in [1] * (uint64_t) inverted_div_table [alpha] - + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); -} - -static SMOL_INLINE void -unpremul_p_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, - uint64_t * SMOL_RESTRICT out, - uint8_t alpha) -{ - out [0] = (((in [0] << 8) * (uint64_t) inverted_div_table [alpha]) - >> INVERTED_DIV_SHIFT); - out [1] = (((in [1] << 8) * (uint64_t) inverted_div_table [alpha]) - >> INVERTED_DIV_SHIFT); -} - -static SMOL_INLINE uint64_t -unpremul_p_to_u_64bpp (const uint64_t in, - uint8_t alpha) -{ - uint64_t in_128bpp [2]; - uint64_t out_128bpp [2]; - - in_128bpp [0] = (in & 0x000000ff000000ff); - in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; - - unpremul_p_to_u_128bpp (in_128bpp, out_128bpp, alpha); - - return (out_128bpp [0] & 0x000000ff000000ff) - | ((out_128bpp [1] & 0x000000ff000000ff) << 16); -} - -static SMOL_INLINE uint64_t -premul_u_to_p_64bpp (const uint64_t in, - uint8_t alpha) -{ - return ((in * ((uint16_t) alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; -} - -/* --- Packing --- */ - -/* It's nice to be able to shift by a negative amount */ -#define SHIFT_S(in, s) ((s >= 0) ? (in) << (s) : (in) >> -(s)) - -/* This is kind of bulky (~13 x86 insns), but it's about the same as using - * unions, and we don't have to worry about endianness. */ -#define PACK_FROM_1234_64BPP(in, a, b, c, d) \ - ((SHIFT_S ((in), ((a) - 1) * 16 + 8 - 32) & 0xff000000) \ - | (SHIFT_S ((in), ((b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in), ((c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in), ((d) - 1) * 16 + 8 - 56) & 0x000000ff)) - -#define PACK_FROM_1234_128BPP(in, a, b, c, d) \ - ((SHIFT_S ((in [((a) - 1) >> 1]), (((a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ - | (SHIFT_S ((in [((b) - 1) >> 1]), (((b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in [((c) - 1) >> 1]), (((c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in [((d) - 1) >> 1]), (((d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) - -#define SWAP_2_AND_3(n) ((n) == 2 ? 3 : (n) == 3 ? 2 : n) - -#define PACK_FROM_1324_64BPP(in, a, b, c, d) \ - ((SHIFT_S ((in), (SWAP_2_AND_3 (a) - 1) * 16 + 8 - 32) & 0xff000000) \ - | (SHIFT_S ((in), (SWAP_2_AND_3 (b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in), (SWAP_2_AND_3 (c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in), (SWAP_2_AND_3 (d) - 1) * 16 + 8 - 56) & 0x000000ff)) - -/* Note: May not be needed */ -#define PACK_FROM_1324_128BPP(in, a, b, c, d) \ - ((SHIFT_S ((in [(SWAP_2_AND_3 (a) - 1) >> 1]), \ - ((SWAP_2_AND_3 (a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ - | (SHIFT_S ((in [(SWAP_2_AND_3 (b) - 1) >> 1]), \ - ((SWAP_2_AND_3 (b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ - | (SHIFT_S ((in [(SWAP_2_AND_3 (c) - 1) >> 1]), \ - ((SWAP_2_AND_3 (c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ - | (SHIFT_S ((in [(SWAP_2_AND_3 (d) - 1) >> 1]), \ - ((SWAP_2_AND_3 (d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) - -/* Pack p -> p */ - -static SMOL_INLINE uint32_t -pack_pixel_1324_p_to_1234_p_64bpp (uint64_t in) -{ - return in | (in >> 24); -} - -static void -pack_row_1324_p_to_1234_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); - } -} - -static void -pack_row_132a_p_to_123_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - /* FIXME: Would be faster to shift directly */ - uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_132a_p_to_321_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - /* FIXME: Would be faster to shift directly */ - uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -#define DEF_PACK_FROM_1324_P_TO_P_64BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (uint64_t in) \ -{ \ - return PACK_FROM_1324_64BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_1324_p_to_##a##b##c##d##_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - *(row_out++) = pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (*(row_in++)); \ -} - -DEF_PACK_FROM_1324_P_TO_P_64BPP (1, 4, 3, 2) -DEF_PACK_FROM_1324_P_TO_P_64BPP (2, 3, 4, 1) -DEF_PACK_FROM_1324_P_TO_P_64BPP (3, 2, 1, 4) -DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 1, 2, 3) -DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 3, 2, 1) - -static SMOL_INLINE uint32_t -pack_pixel_1234_p_to_1234_p_128bpp (const uint64_t *in) -{ - /* FIXME: Are masks needed? */ - return ((in [0] >> 8) & 0xff000000) - | ((in [0] << 16) & 0x00ff0000) - | ((in [1] >> 24) & 0x0000ff00) - | (in [1] & 0x000000ff); -} - -static void -pack_row_1234_p_to_1234_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_1234_p_to_1234_p_128bpp (row_in); - row_in += 2; - } -} - -#define DEF_PACK_FROM_1234_P_TO_P_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - return PACK_FROM_1234_128BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_1234_P_TO_P_128BPP (1, 4, 3, 2) -DEF_PACK_FROM_1234_P_TO_P_128BPP (2, 3, 4, 1) -DEF_PACK_FROM_1234_P_TO_P_128BPP (3, 2, 1, 4) -DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 1, 2, 3) -DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 3, 2, 1) - -static void -pack_row_123a_p_to_123_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = *row_in >> 32; - *(row_out++) = *(row_in++); - *(row_out++) = *(row_in++) >> 32; - } -} - -static void -pack_row_123a_p_to_321_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = row_in [1] >> 32; - *(row_out++) = row_in [0]; - *(row_out++) = row_in [0] >> 32; - row_in += 2; - } -} - -/* Pack p (alpha last) -> u */ - -static SMOL_INLINE uint32_t -pack_pixel_132a_p_to_1234_u_64bpp (uint64_t in) -{ - uint8_t alpha = in; - in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; - return in | (in >> 24); -} - -static void -pack_row_132a_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); - } -} - -static void -pack_row_132a_p_to_123_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_132a_p_to_321_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -#define DEF_PACK_FROM_132A_P_TO_U_64BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ -{ \ - uint8_t alpha = in; \ - in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; \ - return PACK_FROM_1324_64BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_132a_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - *(row_out++) = pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ -} - -DEF_PACK_FROM_132A_P_TO_U_64BPP (3, 2, 1, 4) -DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 1, 2, 3) -DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 3, 2, 1) - -#define DEF_PACK_FROM_123A_P_TO_U_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - uint64_t t [2]; \ - uint8_t alpha = in [1]; \ - unpremul_p_to_u_128bpp (in, t, alpha); \ - t [1] = (t [1] & 0xffffffff00000000) | alpha; \ - return PACK_FROM_1234_128BPP (t, a, b, c, d); \ -} \ - \ -static void \ -pack_row_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_123A_P_TO_U_128BPP (1, 2, 3, 4) -DEF_PACK_FROM_123A_P_TO_U_128BPP (3, 2, 1, 4) -DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 1, 2, 3) -DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 3, 2, 1) - -static void -pack_row_123a_p_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_123a_p_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -/* Pack p (alpha first) -> u */ - -static SMOL_INLINE uint32_t -pack_pixel_a324_p_to_1234_u_64bpp (uint64_t in) -{ - uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ - in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); - return in | (in >> 24); -} - -static void -pack_row_a324_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); - } -} - -static void -pack_row_a324_p_to_234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - *(row_out++) = p; - } -} - -static void -pack_row_a324_p_to_432_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); - *(row_out++) = p; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - } -} - -#define DEF_PACK_FROM_A324_P_TO_U_64BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ -{ \ - uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ \ - in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); \ - return PACK_FROM_1324_64BPP (in, a, b, c, d); \ -} \ - \ -static void \ -pack_row_a324_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - *(row_out++) = pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ -} - -DEF_PACK_FROM_A324_P_TO_U_64BPP (1, 4, 3, 2) -DEF_PACK_FROM_A324_P_TO_U_64BPP (2, 3, 4, 1) -DEF_PACK_FROM_A324_P_TO_U_64BPP (4, 3, 2, 1) - -#define DEF_PACK_FROM_A234_P_TO_U_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - uint64_t t [2]; \ - uint8_t alpha = in [0] >> 32; \ - unpremul_p_to_u_128bpp (in, t, alpha); \ - t [0] = (t [0] & 0x00000000ffffffff) | ((uint64_t) alpha << 32); \ - return PACK_FROM_1234_128BPP (t, a, b, c, d); \ -} \ - \ -static void \ -pack_row_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 2, 3, 4) -DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 4, 3, 2) -DEF_PACK_FROM_A234_P_TO_U_128BPP (2, 3, 4, 1) -DEF_PACK_FROM_A234_P_TO_U_128BPP (4, 3, 2, 1) - -static void -pack_row_a234_p_to_234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - *(row_out++) = p; - } -} - -static void -pack_row_a234_p_to_432_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels * 3; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - } -} - -/* Pack i (alpha last) to u */ - -static SMOL_INLINE uint32_t -pack_pixel_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT in) -{ - uint8_t alpha = in [1] & 0xff; - uint64_t t [2]; - - unpremul_i_to_u_128bpp (in, t, alpha); - - return ((t [0] >> 8) & 0xff000000) - | ((t [0] << 16) & 0x00ff0000) - | ((t [1] >> 24) & 0x0000ff00) - | alpha; -} - -static void -pack_row_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint32_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint32_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = pack_pixel_123a_i_to_1234_u_128bpp (row_in); - row_in += 2; - } -} - -static void -pack_row_123a_i_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 24; - *(row_out++) = p >> 16; - *(row_out++) = p >> 8; - } -} - -static void -pack_row_123a_i_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, - uint8_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint8_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); - - while (row_out != row_out_max) - { - uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); - row_in += 2; - *(row_out++) = p >> 8; - *(row_out++) = p >> 16; - *(row_out++) = p >> 24; - } -} - -#define DEF_PACK_FROM_123A_I_TO_U_128BPP(a, b, c, d) \ -static SMOL_INLINE uint32_t \ -pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ -{ \ - uint8_t alpha = in [1] & 0xff; \ - uint64_t t [2]; \ - unpremul_i_to_u_128bpp (in, t, alpha); \ - t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ - return PACK_FROM_1234_128BPP (t, a, b, c, d); \ -} \ - \ -static void \ -pack_row_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ - uint32_t * SMOL_RESTRICT row_out, \ - uint32_t n_pixels) \ -{ \ - uint32_t *row_out_max = row_out + n_pixels; \ - SMOL_ASSUME_TEMP_ALIGNED (row_in, const uint64_t *); \ - while (row_out != row_out_max) \ - { \ - *(row_out++) = pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (row_in); \ - row_in += 2; \ - } \ -} - -DEF_PACK_FROM_123A_I_TO_U_128BPP(3, 2, 1, 4) -DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 1, 2, 3) -DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 3, 2, 1) - -/* Unpack p -> p */ - -static SMOL_INLINE uint64_t -unpack_pixel_1234_p_to_1324_p_64bpp (uint32_t p) -{ - return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); -} - -/* AVX2 has a useful instruction for this: __m256i _mm256_cvtepu8_epi16 (__m128i a); - * It results in a different channel ordering, so it'd be important to match with - * the right kind of re-pack. */ -static void -unpack_row_1234_p_to_1324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_1234_p_to_1324_p_64bpp (*(row_in++)); - } -} - -static SMOL_INLINE uint64_t -unpack_pixel_123_p_to_132a_p_64bpp (const uint8_t *p) -{ - return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) - | ((uint64_t) p [2] << 32) | 0xff; -} - -static void -unpack_row_123_p_to_132a_p_64bpp (const uint8_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_123_p_to_132a_p_64bpp (row_in); - row_in += 3; - } -} - -static SMOL_INLINE void -unpack_pixel_1234_p_to_1234_p_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = p; - out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); - out [1] = ((p64 & 0x0000ff00) << 24) | (p64 & 0x000000ff); -} - -static void -unpack_row_1234_p_to_1234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_1234_p_to_1234_p_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -static SMOL_INLINE void -unpack_pixel_123_p_to_123a_p_128bpp (const uint8_t *in, - uint64_t *out) -{ - out [0] = ((uint64_t) in [0] << 32) | in [1]; - out [1] = ((uint64_t) in [2] << 32) | 0xff; -} - -static void -unpack_row_123_p_to_123a_p_128bpp (const uint8_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_123_p_to_123a_p_128bpp (row_in, row_out); - row_in += 3; - row_out += 2; - } -} - -/* Unpack u (alpha first) -> p */ - -static SMOL_INLINE uint64_t -unpack_pixel_a234_u_to_a324_p_64bpp (uint32_t p) -{ - uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); - uint8_t alpha = p >> 24; - - return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); -} - -static void -unpack_row_a234_u_to_a324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_a234_u_to_a324_p_64bpp (*(row_in++)); - } -} - -static SMOL_INLINE void -unpack_pixel_a234_u_to_a234_p_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); - uint8_t alpha = p >> 24; - - p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); - out [0] = (p64 >> 16) & 0x000000ff000000ff; - out [1] = p64 & 0x000000ff000000ff; -} - -static void -unpack_row_a234_u_to_a234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_a234_u_to_a234_p_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* Unpack u (alpha first) -> i */ - -static SMOL_INLINE void -unpack_pixel_a234_u_to_234a_i_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = p; - uint64_t alpha = p >> 24; - - out [0] = (((((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8)) * alpha)); - out [1] = (((((p64 & 0x000000ff) << 32) * alpha))) | alpha; -} - -static void -unpack_row_a234_u_to_234a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_a234_u_to_234a_i_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* Unpack u (alpha last) -> p */ - -static SMOL_INLINE uint64_t -unpack_pixel_123a_u_to_132a_p_64bpp (uint32_t p) -{ - uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); - uint8_t alpha = p & 0xff; - - return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); -} - -static void -unpack_row_123a_u_to_132a_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - *(row_out++) = unpack_pixel_123a_u_to_132a_p_64bpp (*(row_in++)); - } -} - -static SMOL_INLINE void -unpack_pixel_123a_u_to_123a_p_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); - uint8_t alpha = p & 0xff; - - p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); - out [0] = (p64 >> 16) & 0x000000ff000000ff; - out [1] = p64 & 0x000000ff000000ff; -} - -static void -unpack_row_123a_u_to_123a_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_123a_u_to_123a_p_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* Unpack u (alpha last) -> i */ - -static SMOL_INLINE void -unpack_pixel_123a_u_to_123a_i_128bpp (uint32_t p, - uint64_t *out) -{ - uint64_t p64 = p; - uint64_t alpha = p & 0xff; - - out [0] = (((((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16)) * alpha)); - out [1] = (((((p64 & 0x0000ff00) << 24) * alpha))) | alpha; -} - -static void -unpack_row_123a_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, - uint64_t * SMOL_RESTRICT row_out, - uint32_t n_pixels) -{ - uint64_t *row_out_max = row_out + n_pixels * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_out, uint64_t *); - - while (row_out != row_out_max) - { - unpack_pixel_123a_u_to_123a_i_128bpp (*(row_in++), row_out); - row_out += 2; - } -} - -/* --- Filter helpers --- */ - -static SMOL_INLINE const uint32_t * -inrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, - uint32_t inrow_ofs) -{ - return scale_ctx->pixels_in + scale_ctx->rowstride_in * inrow_ofs; -} - -static SMOL_INLINE uint32_t * -outrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, - uint32_t outrow_ofs) -{ - return scale_ctx->pixels_out + scale_ctx->rowstride_out * outrow_ofs; -} - -static SMOL_INLINE uint64_t -weight_pixel_64bpp (uint64_t p, - uint16_t w) -{ - return ((p * w) >> 8) & 0x00ff00ff00ff00ff; -} - -/* p and out may be the same address */ -static SMOL_INLINE void -weight_pixel_128bpp (uint64_t *p, - uint64_t *out, - uint16_t w) -{ - out [0] = ((p [0] * w) >> 8) & 0x00ffffff00ffffffULL; - out [1] = ((p [1] * w) >> 8) & 0x00ffffff00ffffffULL; -} - -static SMOL_INLINE void -sum_parts_64bpp (const uint64_t ** SMOL_RESTRICT parts_in, - uint64_t * SMOL_RESTRICT accum, - uint32_t n) -{ - const uint64_t *pp_end; - const uint64_t * SMOL_RESTRICT pp = *parts_in; - - SMOL_ASSUME_ALIGNED (pp, const uint64_t *, sizeof (uint64_t)); - - for (pp_end = pp + n; pp < pp_end; pp++) - { - *accum += *pp; - } - - *parts_in = pp; -} - -static SMOL_INLINE void -sum_parts_128bpp (const uint64_t ** SMOL_RESTRICT parts_in, - uint64_t * SMOL_RESTRICT accum, - uint32_t n) -{ - const uint64_t *pp_end; - const uint64_t * SMOL_RESTRICT pp = *parts_in; - - SMOL_ASSUME_ALIGNED (pp, const uint64_t *, sizeof (uint64_t) * 2); - - for (pp_end = pp + n * 2; pp < pp_end; ) - { - accum [0] += *(pp++); - accum [1] += *(pp++); - } - - *parts_in = pp; -} - -static SMOL_INLINE uint64_t -scale_64bpp (uint64_t accum, - uint64_t multiplier) -{ - uint64_t a, b; - - /* Average the inputs */ - a = ((accum & 0x0000ffff0000ffffULL) * multiplier - + (BOXES_MULTIPLIER / 2) + ((BOXES_MULTIPLIER / 2) << 32)) / BOXES_MULTIPLIER; - b = (((accum & 0xffff0000ffff0000ULL) >> 16) * multiplier - + (BOXES_MULTIPLIER / 2) + ((BOXES_MULTIPLIER / 2) << 32)) / BOXES_MULTIPLIER; - - /* Return pixel */ - return (a & 0x000000ff000000ffULL) | ((b & 0x000000ff000000ffULL) << 16); -} - -static SMOL_INLINE uint64_t -scale_128bpp_half (uint64_t accum, - uint64_t multiplier) -{ - uint64_t a, b; - - a = accum & 0x00000000ffffffffULL; - a = (a * multiplier + BOXES_MULTIPLIER / 2) / BOXES_MULTIPLIER; - - b = (accum & 0xffffffff00000000ULL) >> 32; - b = (b * multiplier + BOXES_MULTIPLIER / 2) / BOXES_MULTIPLIER; - - return (a & 0x000000000000ffffULL) - | ((b & 0x000000000000ffffULL) << 32); -} - -static SMOL_INLINE void -scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, - uint64_t multiplier, - uint64_t ** SMOL_RESTRICT row_parts_out) -{ - *(*row_parts_out)++ = scale_128bpp_half (accum [0], multiplier); - *(*row_parts_out)++ = scale_128bpp_half (accum [1], multiplier); -} - -static void -add_parts (const uint64_t * SMOL_RESTRICT parts_in, - uint64_t * SMOL_RESTRICT parts_acc_out, - uint32_t n) -{ - const uint64_t *parts_in_max = parts_in + n; - - SMOL_ASSUME_TEMP_ALIGNED (parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_acc_out, uint64_t *); - - while (parts_in < parts_in_max) - *(parts_acc_out++) += *(parts_in++); -} - -/* --- Precalculation --- */ - -static void -pick_filter_params (uint32_t dim_in, - uint32_t dim_out, - uint32_t *halvings_out, - uint32_t *dim_bilin_out, - SmolFilterType *filter_out, - SmolStorageType *storage_out) -{ - *dim_bilin_out = dim_out; - *storage_out = SMOL_STORAGE_64BPP; - - /* The box algorithms are only sufficiently precise when - * dim_in > dim_out * 5. box_64bpp typically starts outperforming - * bilinear+halving at dim_in > dim_out * 8. */ - - if (dim_in > dim_out * 255) - { - *filter_out = SMOL_FILTER_BOX; - *storage_out = SMOL_STORAGE_128BPP; - } - else if (dim_in > dim_out * 8) - { - *filter_out = SMOL_FILTER_BOX; - } - else if (dim_in == 1) - { - *filter_out = SMOL_FILTER_ONE; - } - else - { - uint32_t n_halvings = 0; - uint32_t d = dim_out; - - for (;;) - { - d *= 2; - if (d >= dim_in) - break; - n_halvings++; - } - - dim_out <<= n_halvings; - *dim_bilin_out = dim_out; - *filter_out = SMOL_FILTER_BILINEAR_0H + n_halvings; - *halvings_out = n_halvings; - } -} - -static void -precalc_bilinear_array (uint16_t *array, - uint32_t dim_in, - uint32_t dim_out, - unsigned int make_absolute_offsets) -{ - uint64_t ofs_stepF, fracF, frac_stepF; - uint16_t *pu16 = array; - uint16_t last_ofs = 0; - - if (dim_in > dim_out) - { - /* Minification */ - frac_stepF = ofs_stepF = (dim_in * BILIN_MULTIPLIER) / dim_out; - fracF = (frac_stepF - BILIN_MULTIPLIER) / 2; - } - else - { - /* Magnification */ - frac_stepF = ofs_stepF = ((dim_in - 1) * BILIN_MULTIPLIER) / (dim_out > 1 ? (dim_out - 1) : 1); - fracF = 0; - } - - do - { - uint16_t ofs = fracF / BILIN_MULTIPLIER; - - /* We sample ofs and its neighbor -- prevent out of bounds access - * for the latter. */ - if (ofs >= dim_in - 1) - break; - - *(pu16++) = make_absolute_offsets ? ofs : ofs - last_ofs; - *(pu16++) = SMALL_MUL - ((fracF / (BILIN_MULTIPLIER / SMALL_MUL)) % SMALL_MUL); - fracF += frac_stepF; - - last_ofs = ofs; - } - while (--dim_out); - - /* Instead of going out of bounds, sample the final pair of pixels with a 100% - * bias towards the last pixel */ - while (dim_out) - { - *(pu16++) = make_absolute_offsets ? dim_in - 2 : (dim_in - 2) - last_ofs; - *(pu16++) = 0; - dim_out--; - - last_ofs = dim_in - 2; - } -} - -static void -precalc_boxes_array (uint16_t *array, - uint32_t *span_mul, - uint32_t dim_in, - uint32_t dim_out, - unsigned int make_absolute_offsets) -{ - uint64_t fracF, frac_stepF; - uint16_t *pu16 = array; - uint16_t ofs, next_ofs; - uint64_t f; - uint64_t stride; - uint64_t a, b; - - frac_stepF = ((uint64_t) dim_in * BIG_MUL) / (uint64_t) dim_out; - fracF = 0; - ofs = 0; - - stride = frac_stepF / (uint64_t) BIG_MUL; - f = (frac_stepF / SMALL_MUL) % SMALL_MUL; - - a = (BOXES_MULTIPLIER * 255); - b = ((stride * 255) + ((f * 255) / 256)); - *span_mul = (a + (b / 2)) / b; - - do - { - fracF += frac_stepF; - next_ofs = (uint64_t) fracF / ((uint64_t) BIG_MUL); - - /* Prevent out of bounds access */ - if (ofs >= dim_in - 1) - break; - - if (next_ofs > dim_in) - { - next_ofs = dim_in; - if (next_ofs <= ofs) - break; - } - - stride = next_ofs - ofs - 1; - f = (fracF / SMALL_MUL) % SMALL_MUL; - - /* Fraction is the other way around, since left pixel of each span - * comes first, and it's on the right side of the fractional sample. */ - *(pu16++) = make_absolute_offsets ? ofs : stride; - *(pu16++) = f; - - ofs = next_ofs; - } - while (--dim_out); - - /* Instead of going out of bounds, sample the final pair of pixels with a 100% - * bias towards the last pixel */ - while (dim_out) - { - *(pu16++) = make_absolute_offsets ? ofs : 0; - *(pu16++) = 0; - dim_out--; - } - - *(pu16++) = make_absolute_offsets ? ofs : 0; - *(pu16++) = 0; -} - -/* --- Horizontal scaling --- */ - -#define DEF_INTERP_HORIZONTAL_BILINEAR(n_halvings) \ -static void \ -interp_horizontal_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ - const uint64_t * SMOL_RESTRICT row_parts_in, \ - uint64_t * SMOL_RESTRICT row_parts_out) \ -{ \ - uint64_t p, q; \ - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ - uint64_t F; \ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; \ - int i; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); \ - \ - do \ - { \ - uint64_t accum = 0; \ - \ - for (i = 0; i < (1 << (n_halvings)); i++) \ - { \ - row_parts_in += *(ofs_x++); \ - F = *(ofs_x++); \ - \ - p = *row_parts_in; \ - q = *(row_parts_in + 1); \ - \ - accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ - } \ - *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ - } \ - while (row_parts_out != row_parts_out_max); \ -} \ - \ -static void \ -interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ - const uint64_t * SMOL_RESTRICT row_parts_in, \ - uint64_t * SMOL_RESTRICT row_parts_out) \ -{ \ - uint64_t p, q; \ - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ - uint64_t F; \ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; \ - int i; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); \ - \ - do \ - { \ - uint64_t accum [2] = { 0 }; \ - \ - for (i = 0; i < (1 << (n_halvings)); i++) \ - { \ - row_parts_in += *(ofs_x++) * 2; \ - F = *(ofs_x++); \ - \ - p = row_parts_in [0]; \ - q = row_parts_in [2]; \ - \ - accum [0] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ - \ - p = row_parts_in [1]; \ - q = row_parts_in [3]; \ - \ - accum [1] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ - } \ - *(row_parts_out++) = ((accum [0]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ - *(row_parts_out++) = ((accum [1]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ - } \ - while (row_parts_out != row_parts_out_max); \ -} - -static void -interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t p, q; - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; - uint64_t F; - uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - do - { - row_parts_in += *(ofs_x++); - F = *(ofs_x++); - - p = *row_parts_in; - q = *(row_parts_in + 1); - - *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; - } - while (row_parts_out != row_parts_out_max); -} - -static void -interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t p, q; - const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; - uint64_t F; - uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - do - { - row_parts_in += *(ofs_x++) * 2; - F = *(ofs_x++); - - p = row_parts_in [0]; - q = row_parts_in [2]; - - *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - - p = row_parts_in [1]; - q = row_parts_in [3]; - - *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - } - while (row_parts_out != row_parts_out_max); -} - -DEF_INTERP_HORIZONTAL_BILINEAR(1) -DEF_INTERP_HORIZONTAL_BILINEAR(2) -DEF_INTERP_HORIZONTAL_BILINEAR(3) -DEF_INTERP_HORIZONTAL_BILINEAR(4) -DEF_INTERP_HORIZONTAL_BILINEAR(5) -DEF_INTERP_HORIZONTAL_BILINEAR(6) - -static void -interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, - const uint64_t *row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - const uint64_t * SMOL_RESTRICT pp; - const uint16_t *ofs_x = scale_ctx->offsets_x; - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out - 1; - uint64_t accum = 0; - uint64_t p, q, r, s; - uint32_t n; - uint64_t F; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - pp = row_parts_in; - p = weight_pixel_64bpp (*(pp++), 256); - n = *(ofs_x++); - - while (row_parts_out != row_parts_out_max) - { - sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); - - F = *(ofs_x++); - n = *(ofs_x++); - - r = *(pp++); - s = r * F; - - q = (s >> 8) & 0x00ff00ff00ff00ffULL; - - accum += p + q; - - /* (255 * r) - (F * r) */ - p = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; - - *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); - accum = 0; - } - - /* Final box optionally features the rightmost fractional pixel */ - - sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); - - q = 0; - F = *(ofs_x); - if (F > 0) - q = weight_pixel_64bpp (*(pp), F); - - accum += p + q; - *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); -} - -static void -interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, - const uint64_t *row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - const uint64_t * SMOL_RESTRICT pp; - const uint16_t *ofs_x = scale_ctx->offsets_x; - uint64_t *row_parts_out_max = row_parts_out + (scale_ctx->width_out - /* 2 */ 1) * 2; - uint64_t accum [2] = { 0, 0 }; - uint64_t p [2], q [2], r [2], s [2]; - uint32_t n; - uint64_t F; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - pp = row_parts_in; - - p [0] = *(pp++); - p [1] = *(pp++); - weight_pixel_128bpp (p, p, 256); - - n = *(ofs_x++); - - while (row_parts_out != row_parts_out_max) - { - sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); - - F = *(ofs_x++); - n = *(ofs_x++); - - r [0] = *(pp++); - r [1] = *(pp++); - - s [0] = r [0] * F; - s [1] = r [1] * F; - - q [0] = (s [0] >> 8) & 0x00ffffff00ffffff; - q [1] = (s [1] >> 8) & 0x00ffffff00ffffff; - - accum [0] += p [0] + q [0]; - accum [1] += p [1] + q [1]; - - p [0] = (((r [0] << 8) - r [0] - s [0]) >> 8) & 0x00ffffff00ffffff; - p [1] = (((r [1] << 8) - r [1] - s [1]) >> 8) & 0x00ffffff00ffffff; - - scale_and_store_128bpp (accum, - scale_ctx->span_mul_x, - (uint64_t ** SMOL_RESTRICT) &row_parts_out); - - accum [0] = 0; - accum [1] = 0; - } - - /* Final box optionally features the rightmost fractional pixel */ - - sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); - - q [0] = 0; - q [1] = 0; - - F = *(ofs_x); - if (F > 0) - { - q [0] = *(pp++); - q [1] = *(pp++); - weight_pixel_128bpp (q, q, F); - } - - accum [0] += p [0] + q [0]; - accum [1] += p [1] + q [1]; - - scale_and_store_128bpp (accum, - scale_ctx->span_mul_x, - (uint64_t ** SMOL_RESTRICT) &row_parts_out); -} - -static void -interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; - uint64_t part; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - part = *row_parts_in; - while (row_parts_out != row_parts_out_max) - *(row_parts_out++) = part; -} - -static void -interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, - const uint64_t * SMOL_RESTRICT row_parts_in, - uint64_t * SMOL_RESTRICT row_parts_out) -{ - uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; - - SMOL_ASSUME_TEMP_ALIGNED (row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (row_parts_out, uint64_t *); - - while (row_parts_out != row_parts_out_max) - { - *(row_parts_out++) = row_parts_in [0]; - *(row_parts_out++) = row_parts_in [1]; - } -} - -static void -scale_horizontal (const SmolScaleCtx *scale_ctx, - const uint32_t *row_in, - uint64_t *row_parts_out) -{ - uint64_t * SMOL_RESTRICT unpacked_in; - - /* FIXME: Allocate less for 64bpp */ - unpacked_in = aligned_alloca (scale_ctx->width_in * sizeof (uint64_t) * 2, SMOL_TEMP_ALIGNMENT); - - scale_ctx->unpack_row_func (row_in, - unpacked_in, - scale_ctx->width_in); - scale_ctx->hfilter_func (scale_ctx, - unpacked_in, - row_parts_out); -} - -/* --- Vertical scaling --- */ - -static void -update_vertical_ctx_bilinear (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index) -{ - uint32_t new_in_ofs = scale_ctx->offsets_y [outrow_index * 2]; - - if (new_in_ofs == vertical_ctx->in_ofs) - return; - - if (new_in_ofs == vertical_ctx->in_ofs + 1) - { - uint64_t *t = vertical_ctx->parts_row [0]; - vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; - vertical_ctx->parts_row [1] = t; - - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), - vertical_ctx->parts_row [1]); - } - else - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, new_in_ofs), - vertical_ctx->parts_row [0]); - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), - vertical_ctx->parts_row [1]); - } - - vertical_ctx->in_ofs = new_in_ofs; -} - -static void -interp_vertical_bilinear_store_64bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT top_row_parts_in, - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, - uint64_t * SMOL_RESTRICT parts_out, - uint32_t width) -{ - uint64_t *parts_out_last = parts_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; - } - while (parts_out != parts_out_last); -} - -static void -interp_vertical_bilinear_add_64bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT top_row_parts_in, - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, - uint64_t * SMOL_RESTRICT accum_out, - uint32_t width) -{ - uint64_t *accum_out_last = accum_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (accum_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; - } - while (accum_out != accum_out_last); -} - -static void -interp_vertical_bilinear_store_128bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT SMOL_ALIGNED_64 top_row_parts_in, - const uint64_t * SMOL_RESTRICT SMOL_ALIGNED_64 bottom_row_parts_in, - uint64_t * SMOL_RESTRICT SMOL_ALIGNED_64 parts_out, - uint32_t width) -{ - uint64_t *parts_out_last = parts_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - } - while (parts_out != parts_out_last); -} - -static void -interp_vertical_bilinear_add_128bpp (uint64_t F, - const uint64_t * SMOL_RESTRICT top_row_parts_in, - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, - uint64_t * SMOL_RESTRICT accum_out, - uint32_t width) -{ - uint64_t *accum_out_last = accum_out + width; - - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (accum_out, uint64_t *); - - do - { - uint64_t p, q; - - p = *(top_row_parts_in++); - q = *(bottom_row_parts_in++); - - *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; - } - while (accum_out != accum_out_last); -} - -#define DEF_INTERP_VERTICAL_BILINEAR_FINAL(n_halvings) \ -static void \ -interp_vertical_bilinear_final_##n_halvings##h_64bpp (uint64_t F, \ - const uint64_t * SMOL_RESTRICT top_row_parts_in, \ - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ - uint64_t * SMOL_RESTRICT accum_inout, \ - uint32_t width) \ -{ \ - uint64_t *accum_inout_last = accum_inout + width; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (accum_inout, uint64_t *); \ - \ - do \ - { \ - uint64_t p, q; \ - \ - p = *(top_row_parts_in++); \ - q = *(bottom_row_parts_in++); \ - \ - p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ - p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ - \ - *(accum_inout++) = p; \ - } \ - while (accum_inout != accum_inout_last); \ -} \ - \ -static void \ -interp_vertical_bilinear_final_##n_halvings##h_128bpp (uint64_t F, \ - const uint64_t * SMOL_RESTRICT top_row_parts_in, \ - const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ - uint64_t * SMOL_RESTRICT accum_inout, \ - uint32_t width) \ -{ \ - uint64_t *accum_inout_last = accum_inout + width; \ - \ - SMOL_ASSUME_TEMP_ALIGNED (top_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (bottom_row_parts_in, const uint64_t *); \ - SMOL_ASSUME_TEMP_ALIGNED (accum_inout, uint64_t *); \ - \ - do \ - { \ - uint64_t p, q; \ - \ - p = *(top_row_parts_in++); \ - q = *(bottom_row_parts_in++); \ - \ - p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ - p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ - \ - *(accum_inout++) = p; \ - } \ - while (accum_inout != accum_inout_last); \ -} - -#define DEF_SCALE_OUTROW_BILINEAR(n_halvings) \ -static void \ -scale_outrow_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ - SmolVerticalCtx *vertical_ctx, \ - uint32_t outrow_index, \ - uint32_t *row_out) \ -{ \ - uint32_t bilin_index = outrow_index << (n_halvings); \ - unsigned int i; \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out); \ - bilin_index++; \ - \ - for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ - { \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_add_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out); \ - bilin_index++; \ - } \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_final_##n_halvings##h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out); \ - \ - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ -} \ - \ -static void \ -scale_outrow_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ - SmolVerticalCtx *vertical_ctx, \ - uint32_t outrow_index, \ - uint32_t *row_out) \ -{ \ - uint32_t bilin_index = outrow_index << (n_halvings); \ - unsigned int i; \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out * 2); \ - bilin_index++; \ - \ - for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ - { \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_add_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out * 2); \ - bilin_index++; \ - } \ - \ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ - interp_vertical_bilinear_final_##n_halvings##h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ - vertical_ctx->parts_row [0], \ - vertical_ctx->parts_row [1], \ - vertical_ctx->parts_row [2], \ - scale_ctx->width_out * 2); \ - \ - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ -} - -static void -scale_outrow_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); - interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); - interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out * 2); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) - -static void -scale_outrow_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t bilin_index = outrow_index << 1; - - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - bilin_index++; - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_final_1h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t bilin_index = outrow_index << 1; - - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out * 2); - bilin_index++; - update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); - interp_vertical_bilinear_final_1h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], - vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - scale_ctx->width_out * 2); - scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); -} - -DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) -DEF_SCALE_OUTROW_BILINEAR(2) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) -DEF_SCALE_OUTROW_BILINEAR(3) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) -DEF_SCALE_OUTROW_BILINEAR(4) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) -DEF_SCALE_OUTROW_BILINEAR(5) -DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) -DEF_SCALE_OUTROW_BILINEAR(6) - -static void -finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, - uint64_t multiplier, - uint64_t * SMOL_RESTRICT parts_out, - uint32_t n) -{ - uint64_t *parts_out_max = parts_out + n; - - SMOL_ASSUME_TEMP_ALIGNED (accums, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - while (parts_out != parts_out_max) - { - *(parts_out++) = scale_64bpp (*(accums++), multiplier); - } -} - -static void -weight_edge_row_64bpp (uint64_t *row, - uint16_t w, - uint32_t n) -{ - uint64_t *row_max = row + n; - - SMOL_ASSUME_TEMP_ALIGNED (row, uint64_t *); - - while (row != row_max) - { - *row = ((*row * w) >> 8) & 0x00ff00ff00ff00ffULL; - row++; - } -} - -static void -scale_and_weight_edge_rows_box_64bpp (const uint64_t * SMOL_RESTRICT first_row, - uint64_t * SMOL_RESTRICT last_row, - uint64_t * SMOL_RESTRICT accum, - uint16_t w2, - uint32_t n) -{ - const uint64_t *first_row_max = first_row + n; - - SMOL_ASSUME_TEMP_ALIGNED (first_row, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (last_row, uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (accum, uint64_t *); - - while (first_row != first_row_max) - { - uint64_t r, s, p, q; - - p = *(first_row++); - - r = *(last_row); - s = r * w2; - q = (s >> 8) & 0x00ff00ff00ff00ffULL; - /* (255 * r) - (F * r) */ - *(last_row++) = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; - - *(accum++) = p + q; - } -} - -static void -update_vertical_ctx_box_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t ofs_y, - uint32_t ofs_y_max, - uint16_t w1, - uint16_t w2) -{ - /* Old in_ofs is the previous max */ - if (ofs_y == vertical_ctx->in_ofs) - { - uint64_t *t = vertical_ctx->parts_row [0]; - vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; - vertical_ctx->parts_row [1] = t; - } - else - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [0]); - weight_edge_row_64bpp (vertical_ctx->parts_row [0], w1, scale_ctx->width_out); - } - - /* When w2 == 0, the final inrow may be out of bounds. Don't try to access it in - * that case. */ - if (w2 || ofs_y_max < scale_ctx->height_in) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y_max), - vertical_ctx->parts_row [1]); - } - else - { - memset (vertical_ctx->parts_row [1], 0, scale_ctx->width_out * sizeof (uint64_t)); - } - - vertical_ctx->in_ofs = ofs_y_max; -} - -static void -scale_outrow_box_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t ofs_y, ofs_y_max; - uint16_t w1, w2; - - /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ - - ofs_y = scale_ctx->offsets_y [outrow_index * 2]; - ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; - - /* Scale the first and last rows, weight them and store in accumulator */ - - w1 = (outrow_index == 0) ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1]; - w2 = scale_ctx->offsets_y [outrow_index * 2 + 1]; - - update_vertical_ctx_box_64bpp (scale_ctx, vertical_ctx, ofs_y, ofs_y_max, w1, w2); - - scale_and_weight_edge_rows_box_64bpp (vertical_ctx->parts_row [0], - vertical_ctx->parts_row [1], - vertical_ctx->parts_row [2], - w2, - scale_ctx->width_out); - - ofs_y++; - - /* Add up whole rows */ - - while (ofs_y < ofs_y_max) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [0]); - add_parts (vertical_ctx->parts_row [0], - vertical_ctx->parts_row [2], - scale_ctx->width_out); - - ofs_y++; - } - - finalize_vertical_64bpp (vertical_ctx->parts_row [2], - scale_ctx->span_mul_y, - vertical_ctx->parts_row [0], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); -} - -static void -finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, - uint64_t multiplier, - uint64_t * SMOL_RESTRICT parts_out, - uint32_t n) -{ - uint64_t *parts_out_max = parts_out + n * 2; - - SMOL_ASSUME_TEMP_ALIGNED (accums, const uint64_t *); - SMOL_ASSUME_TEMP_ALIGNED (parts_out, uint64_t *); - - while (parts_out != parts_out_max) - { - *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); - *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); - } -} - -static void -weight_row_128bpp (uint64_t *row, - uint16_t w, - uint32_t n) -{ - uint64_t *row_max = row + (n * 2); - - SMOL_ASSUME_TEMP_ALIGNED (row, uint64_t *); - - while (row != row_max) - { - row [0] = ((row [0] * w) >> 8) & 0x00ffffff00ffffffULL; - row [1] = ((row [1] * w) >> 8) & 0x00ffffff00ffffffULL; - row += 2; - } -} - -static void -scale_outrow_box_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - uint32_t ofs_y, ofs_y_max; - uint16_t w; - - /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ - - ofs_y = scale_ctx->offsets_y [outrow_index * 2]; - ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; - - /* Scale the first inrow and store it */ - - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [0]); - weight_row_128bpp (vertical_ctx->parts_row [0], - outrow_index == 0 ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1], - scale_ctx->width_out); - ofs_y++; - - /* Add up whole rows */ - - while (ofs_y < ofs_y_max) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [1]); - add_parts (vertical_ctx->parts_row [1], - vertical_ctx->parts_row [0], - scale_ctx->width_out * 2); - - ofs_y++; - } - - /* Final row is optional; if this is the bottommost outrow it could be out of bounds */ - - w = scale_ctx->offsets_y [outrow_index * 2 + 1]; - if (w > 0) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, ofs_y), - vertical_ctx->parts_row [1]); - weight_row_128bpp (vertical_ctx->parts_row [1], - w - 1, /* Subtract 1 to avoid overflow */ - scale_ctx->width_out); - add_parts (vertical_ctx->parts_row [1], - vertical_ctx->parts_row [0], - scale_ctx->width_out * 2); - } - - finalize_vertical_128bpp (vertical_ctx->parts_row [0], - scale_ctx->span_mul_y, - vertical_ctx->parts_row [1], - scale_ctx->width_out); - scale_ctx->pack_row_func (vertical_ctx->parts_row [1], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_one_64bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t row_index, - uint32_t *row_out) -{ - SMOL_UNUSED (row_index); - - /* Scale the row and store it */ - - if (vertical_ctx->in_ofs != 0) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, 0), - vertical_ctx->parts_row [0]); - vertical_ctx->in_ofs = 0; - } - - scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); -} - -static void -scale_outrow_one_128bpp (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t row_index, - uint32_t *row_out) -{ - SMOL_UNUSED (row_index); - - /* Scale the row and store it */ - - if (vertical_ctx->in_ofs != 0) - { - scale_horizontal (scale_ctx, - inrow_ofs_to_pointer (scale_ctx, 0), - vertical_ctx->parts_row [0]); - vertical_ctx->in_ofs = 0; - } - - scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); -} - -static void -scale_outrow (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out) -{ - scale_ctx->vfilter_func (scale_ctx, - vertical_ctx, - outrow_index, - row_out); -} - -static void -do_rows (const SmolScaleCtx *scale_ctx, - void *outrows_dest, - uint32_t row_out_index, - uint32_t n_rows) -{ - SmolVerticalCtx vertical_ctx = { 0 }; - uint32_t n_parts_per_pixel = 1; - uint32_t n_stored_rows = 3; - uint32_t i; - - if (scale_ctx->storage_type == SMOL_STORAGE_128BPP) - n_parts_per_pixel = 2; - - if (scale_ctx->filter_v == SMOL_FILTER_ONE) - n_stored_rows = 1; - - /* Must be one less, or this test in update_vertical_ctx() will wrap around: - * if (new_in_ofs == vertical_ctx->in_ofs + 1) { ... } */ - vertical_ctx.in_ofs = UINT_MAX - 1; - - for (i = 0; i < n_stored_rows; i++) - { - vertical_ctx.parts_row [i] = - aligned_alloca (MAX (scale_ctx->width_in, scale_ctx->width_out) - * n_parts_per_pixel * sizeof (uint64_t), SMOL_TEMP_ALIGNMENT); - } - - for (i = row_out_index; i < row_out_index + n_rows; i++) - { - scale_outrow (scale_ctx, &vertical_ctx, i, outrows_dest); - outrows_dest = (uint32_t *) outrows_dest + scale_ctx->rowstride_out; - } -} - -/* --- Conversion tables --- */ - -static const SmolConversionTable generic_conversions = -{ -{ { - /* Conversions where accumulators must hold the sum of fewer than - * 256 pixels. This can be done in 64bpp, but 128bpp may be used - * e.g. for 16 bits per channel internally premultiplied data. */ - - /* RGBA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), - }, - /* BGRA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), - }, - /* ARGB8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), - }, - /* ABGR8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), - /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), - /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), - /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), - /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), - /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), - /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), - }, - /* RGBA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), - /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), - /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), - /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - }, - /* BGRA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), - /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), - /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), - /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - }, - /* ARGB8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), - /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), - /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), - /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - }, - /* ABGR8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), - /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), - /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), - /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - }, - /* RGB8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), - /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), - }, - /* BGR8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), - /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), - /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), - /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), - /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), - /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), - } - }, - - { - /* Conversions where accumulators must hold the sum of up to - * 65535 pixels. We need 128bpp for this. */ - - /* RGBA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), - }, - /* BGRA8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), - }, - /* ARGB8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), - }, - /* ABGR8 pre -> */ - { - /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), - /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), - /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), - /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), - /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), - /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), - /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), - /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), - /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), - /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), - }, - /* RGBA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), - /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), - /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), - /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - }, - /* BGRA8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), - /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), - /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), - /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), - /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), - }, - /* ARGB8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), - /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), - /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), - /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - }, - /* ABGR8 un -> */ - { - /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), - /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), - /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), - /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), - /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), - /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), - /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), - /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), - /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), - /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), - }, - /* RGB8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), - /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), - }, - /* BGR8 -> */ - { - /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), - /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), - /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), - /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), - /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), - /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), - } -} } -}; - -static const SmolImplementation generic_implementation = -{ - { - /* Horizontal filters */ - { - /* 64bpp */ - interp_horizontal_one_64bpp, - interp_horizontal_bilinear_0h_64bpp, - interp_horizontal_bilinear_1h_64bpp, - interp_horizontal_bilinear_2h_64bpp, - interp_horizontal_bilinear_3h_64bpp, - interp_horizontal_bilinear_4h_64bpp, - interp_horizontal_bilinear_5h_64bpp, - interp_horizontal_bilinear_6h_64bpp, - interp_horizontal_boxes_64bpp - }, - { - /* 128bpp */ - interp_horizontal_one_128bpp, - interp_horizontal_bilinear_0h_128bpp, - interp_horizontal_bilinear_1h_128bpp, - interp_horizontal_bilinear_2h_128bpp, - interp_horizontal_bilinear_3h_128bpp, - interp_horizontal_bilinear_4h_128bpp, - interp_horizontal_bilinear_5h_128bpp, - interp_horizontal_bilinear_6h_128bpp, - interp_horizontal_boxes_128bpp - } - }, - { - /* Vertical filters */ - { - /* 64bpp */ - scale_outrow_one_64bpp, - scale_outrow_bilinear_0h_64bpp, - scale_outrow_bilinear_1h_64bpp, - scale_outrow_bilinear_2h_64bpp, - scale_outrow_bilinear_3h_64bpp, - scale_outrow_bilinear_4h_64bpp, - scale_outrow_bilinear_5h_64bpp, - scale_outrow_bilinear_6h_64bpp, - scale_outrow_box_64bpp - }, - { - /* 128bpp */ - scale_outrow_one_128bpp, - scale_outrow_bilinear_0h_128bpp, - scale_outrow_bilinear_1h_128bpp, - scale_outrow_bilinear_2h_128bpp, - scale_outrow_bilinear_3h_128bpp, - scale_outrow_bilinear_4h_128bpp, - scale_outrow_bilinear_5h_128bpp, - scale_outrow_bilinear_6h_128bpp, - scale_outrow_box_128bpp - } - }, - &generic_conversions -}; - -/* In the absence of a proper build system, runtime detection is more - portable than compiler macros. WFM. */ -static SmolBool -host_is_little_endian (void) -{ - static const union - { - uint8_t u8 [4]; - uint32_t u32; - } - host_bytes = { { 0, 1, 2, 3 } }; - - if (host_bytes.u32 == 0x03020100UL) - return TRUE; - - return FALSE; -} - -/* The generic unpack/pack functions fetch and store pixels as u32. - * This means the byte order will be reversed on little endian, with - * consequences for the alpha channel and reordering logic. We deal - * with this by using the apparent byte order internally. */ -static SmolPixelType -get_host_pixel_type (SmolPixelType pixel_type) -{ - SmolPixelType host_pixel_type = SMOL_PIXEL_MAX; - - if (!host_is_little_endian ()) - return pixel_type; - - /* We use a switch for this so the compiler can remind us - * to keep it in sync with the SmolPixelType enum. */ - switch (pixel_type) - { - case SMOL_PIXEL_RGBA8_PREMULTIPLIED: - host_pixel_type = SMOL_PIXEL_ABGR8_PREMULTIPLIED; break; - case SMOL_PIXEL_BGRA8_PREMULTIPLIED: - host_pixel_type = SMOL_PIXEL_ARGB8_PREMULTIPLIED; break; - case SMOL_PIXEL_ARGB8_PREMULTIPLIED: - host_pixel_type = SMOL_PIXEL_BGRA8_PREMULTIPLIED; break; - case SMOL_PIXEL_ABGR8_PREMULTIPLIED: - host_pixel_type = SMOL_PIXEL_RGBA8_PREMULTIPLIED; break; - case SMOL_PIXEL_RGBA8_UNASSOCIATED: - host_pixel_type = SMOL_PIXEL_ABGR8_UNASSOCIATED; break; - case SMOL_PIXEL_BGRA8_UNASSOCIATED: - host_pixel_type = SMOL_PIXEL_ARGB8_UNASSOCIATED; break; - case SMOL_PIXEL_ARGB8_UNASSOCIATED: - host_pixel_type = SMOL_PIXEL_BGRA8_UNASSOCIATED; break; - case SMOL_PIXEL_ABGR8_UNASSOCIATED: - host_pixel_type = SMOL_PIXEL_RGBA8_UNASSOCIATED; break; - case SMOL_PIXEL_RGB8: - host_pixel_type = SMOL_PIXEL_RGB8; break; - case SMOL_PIXEL_BGR8: - host_pixel_type = SMOL_PIXEL_BGR8; break; - case SMOL_PIXEL_MAX: - host_pixel_type = SMOL_PIXEL_MAX; break; - } - - return host_pixel_type; -} - -#ifdef SMOL_WITH_AVX2 - -static SmolBool -have_avx2 (void) -{ -#ifdef HAVE_GCC_X86_FEATURE_BUILTINS - __builtin_cpu_init (); - - if (__builtin_cpu_supports ("avx2")) - return TRUE; -#endif - - return FALSE; -} - -#endif - -static void -try_override_conversion (SmolScaleCtx *scale_ctx, - const SmolImplementation *impl, - SmolPixelType ptype_in, - SmolPixelType ptype_out, - uint8_t *n_bytes_per_pixel) -{ - const SmolConversion *conv; - - conv = &impl->ctab->conversions - [scale_ctx->storage_type] [ptype_in] [ptype_out]; - - if (conv->unpack_row_func && conv->pack_row_func) - { - *n_bytes_per_pixel = conv->n_bytes_per_pixel; - scale_ctx->unpack_row_func = conv->unpack_row_func; - scale_ctx->pack_row_func = conv->pack_row_func; - } -} - -static void -try_override_filters (SmolScaleCtx *scale_ctx, - const SmolImplementation *impl) -{ - SmolHFilterFunc *hfilter_func; - SmolVFilterFunc *vfilter_func; - - hfilter_func = impl->hfilter_funcs - [scale_ctx->storage_type] [scale_ctx->filter_h]; - vfilter_func = impl->vfilter_funcs - [scale_ctx->storage_type] [scale_ctx->filter_v]; - - if (hfilter_func) - scale_ctx->hfilter_func = hfilter_func; - if (vfilter_func) - scale_ctx->vfilter_func = vfilter_func; -} - -static void -get_implementations (SmolScaleCtx *scale_ctx) -{ - const SmolConversion *conv; - SmolPixelType ptype_in, ptype_out; - uint8_t n_bytes_per_pixel; - const SmolImplementation *avx2_impl = NULL; - -#ifdef SMOL_WITH_AVX2 - if (have_avx2 ()) - avx2_impl = _smol_get_avx2_implementation (); -#endif - - ptype_in = get_host_pixel_type (scale_ctx->pixel_type_in); - ptype_out = get_host_pixel_type (scale_ctx->pixel_type_out); - - /* Install generic unpack()/pack() */ - - conv = &generic_implementation.ctab->conversions - [scale_ctx->storage_type] [ptype_in] [ptype_out]; - - n_bytes_per_pixel = conv->n_bytes_per_pixel; - scale_ctx->unpack_row_func = conv->unpack_row_func; - scale_ctx->pack_row_func = conv->pack_row_func; - - /* Try to override with better unpack()/pack() implementations */ - - if (avx2_impl) - try_override_conversion (scale_ctx, avx2_impl, - ptype_in, ptype_out, - &n_bytes_per_pixel); - - /* Some conversions require extra precision. This can only ever - * upgrade the storage from 64bpp to 128bpp, but we handle both - * cases here for clarity. */ - if (n_bytes_per_pixel == 8) - scale_ctx->storage_type = SMOL_STORAGE_64BPP; - else if (n_bytes_per_pixel == 16) - scale_ctx->storage_type = SMOL_STORAGE_128BPP; - else - { - assert (n_bytes_per_pixel == 8 || n_bytes_per_pixel == 16); - } - - /* Install generic filters */ - - scale_ctx->hfilter_func = generic_implementation.hfilter_funcs - [scale_ctx->storage_type] [scale_ctx->filter_h]; - scale_ctx->vfilter_func = generic_implementation.vfilter_funcs - [scale_ctx->storage_type] [scale_ctx->filter_v]; - - /* Try to override with better filter implementations */ - - if (avx2_impl) - try_override_filters (scale_ctx, avx2_impl); -} - -static void -smol_scale_init (SmolScaleCtx *scale_ctx, - SmolPixelType pixel_type_in, - const uint32_t *pixels_in, - uint32_t width_in, - uint32_t height_in, - uint32_t rowstride_in, - SmolPixelType pixel_type_out, - uint32_t *pixels_out, - uint32_t width_out, - uint32_t height_out, - uint32_t rowstride_out) -{ - SmolStorageType storage_type [2]; - - scale_ctx->pixel_type_in = pixel_type_in; - scale_ctx->pixels_in = pixels_in; - scale_ctx->width_in = width_in; - scale_ctx->height_in = height_in; - scale_ctx->rowstride_in = rowstride_in / sizeof (uint32_t); - scale_ctx->pixel_type_out = pixel_type_out; - scale_ctx->pixels_out = pixels_out; - scale_ctx->width_out = width_out; - scale_ctx->height_out = height_out; - scale_ctx->rowstride_out = rowstride_out / sizeof (uint32_t); - - pick_filter_params (width_in, width_out, - &scale_ctx->width_halvings, - &scale_ctx->width_bilin_out, - &scale_ctx->filter_h, - &storage_type [0]); - pick_filter_params (height_in, height_out, - &scale_ctx->height_halvings, - &scale_ctx->height_bilin_out, - &scale_ctx->filter_v, - &storage_type [1]); - - scale_ctx->storage_type = MAX (storage_type [0], storage_type [1]); - - scale_ctx->offsets_x = malloc (((scale_ctx->width_bilin_out + 1) * 2 - + (scale_ctx->height_bilin_out + 1) * 2) * sizeof (uint16_t)); - scale_ctx->offsets_y = scale_ctx->offsets_x + (scale_ctx->width_bilin_out + 1) * 2; - - if (scale_ctx->filter_h == SMOL_FILTER_ONE) - { - } - else if (scale_ctx->filter_h == SMOL_FILTER_BOX) - { - precalc_boxes_array (scale_ctx->offsets_x, &scale_ctx->span_mul_x, - width_in, scale_ctx->width_out, FALSE); - } - else /* SMOL_FILTER_BILINEAR_?H */ - { - precalc_bilinear_array (scale_ctx->offsets_x, - width_in, scale_ctx->width_bilin_out, FALSE); - } - - if (scale_ctx->filter_v == SMOL_FILTER_ONE) - { - } - else if (scale_ctx->filter_v == SMOL_FILTER_BOX) - { - precalc_boxes_array (scale_ctx->offsets_y, &scale_ctx->span_mul_y, - height_in, scale_ctx->height_out, TRUE); - } - else /* SMOL_FILTER_BILINEAR_?H */ - { - precalc_bilinear_array (scale_ctx->offsets_y, - height_in, scale_ctx->height_bilin_out, TRUE); - } - - get_implementations (scale_ctx); -} - -static void -smol_scale_finalize (SmolScaleCtx *scale_ctx) -{ - free (scale_ctx->offsets_x); -} - -/* --- Public API --- */ - -SmolScaleCtx * -smol_scale_new (SmolPixelType pixel_type_in, - const uint32_t *pixels_in, - uint32_t width_in, - uint32_t height_in, - uint32_t rowstride_in, - SmolPixelType pixel_type_out, - uint32_t *pixels_out, - uint32_t width_out, - uint32_t height_out, - uint32_t rowstride_out) -{ - SmolScaleCtx *scale_ctx; - - scale_ctx = calloc (sizeof (SmolScaleCtx), 1); - smol_scale_init (scale_ctx, - pixel_type_in, - pixels_in, - width_in, - height_in, - rowstride_in, - pixel_type_out, - pixels_out, - width_out, - height_out, - rowstride_out); - return scale_ctx; -} - -void -smol_scale_destroy (SmolScaleCtx *scale_ctx) -{ - smol_scale_finalize (scale_ctx); - free (scale_ctx); -} - -void -smol_scale_simple (SmolPixelType pixel_type_in, - const uint32_t *pixels_in, - uint32_t width_in, - uint32_t height_in, - uint32_t rowstride_in, - SmolPixelType pixel_type_out, - uint32_t *pixels_out, - uint32_t width_out, - uint32_t height_out, - uint32_t rowstride_out) -{ - SmolScaleCtx scale_ctx; - - smol_scale_init (&scale_ctx, - pixel_type_in, pixels_in, - width_in, height_in, rowstride_in, - pixel_type_out, pixels_out, - width_out, height_out, rowstride_out); - do_rows (&scale_ctx, - outrow_ofs_to_pointer (&scale_ctx, 0), - 0, - scale_ctx.height_out); - smol_scale_finalize (&scale_ctx); -} - -void -smol_scale_batch (const SmolScaleCtx *scale_ctx, - uint32_t first_out_row, - uint32_t n_out_rows) -{ - do_rows (scale_ctx, - outrow_ofs_to_pointer (scale_ctx, first_out_row), - first_out_row, - n_out_rows); -} - -void -smol_scale_batch_full (const SmolScaleCtx *scale_ctx, - void *outrows_dest, - uint32_t first_out_row, - uint32_t n_out_rows) -{ - do_rows (scale_ctx, - outrows_dest, - first_out_row, - n_out_rows); -} diff -Nru chafa-1.2.1/chafa/smolscale/smolscale.h chafa-1.12.4/chafa/smolscale/smolscale.h --- chafa-1.2.1/chafa/smolscale/smolscale.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/smolscale.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright © 2019 Hans Petter Jansson. See COPYING for details. */ - -#include - -#ifndef _SMOLSCALE_H_ -#define _SMOLSCALE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum -{ - /* 32 bits per pixel */ - - SMOL_PIXEL_RGBA8_PREMULTIPLIED, - SMOL_PIXEL_BGRA8_PREMULTIPLIED, - SMOL_PIXEL_ARGB8_PREMULTIPLIED, - SMOL_PIXEL_ABGR8_PREMULTIPLIED, - - SMOL_PIXEL_RGBA8_UNASSOCIATED, - SMOL_PIXEL_BGRA8_UNASSOCIATED, - SMOL_PIXEL_ARGB8_UNASSOCIATED, - SMOL_PIXEL_ABGR8_UNASSOCIATED, - - /* 24 bits per pixel */ - - SMOL_PIXEL_RGB8, - SMOL_PIXEL_BGR8, - - SMOL_PIXEL_MAX -} -SmolPixelType; - -typedef struct SmolScaleCtx SmolScaleCtx; - -/* Simple API: Scales an entire image in one shot. You must provide pointers to - * the source memory and an existing allocation to receive the output data. - * This interface can only be used from a single thread. */ - -void smol_scale_simple (SmolPixelType pixel_type_in, const uint32_t *pixels_in, - uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, - SmolPixelType pixel_type_out, uint32_t *pixels_out, - uint32_t width_out, uint32_t height_out, uint32_t rowstride_out); - -/* Batch API: Allows scaling a few rows at a time. Suitable for multithreading. */ - -SmolScaleCtx *smol_scale_new (SmolPixelType pixel_type_in, const uint32_t *pixels_in, - uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, - SmolPixelType pixel_type_out, uint32_t *pixels_out, - uint32_t width_out, uint32_t height_out, uint32_t rowstride_out); - -void smol_scale_destroy (SmolScaleCtx *scale_ctx); - -/* It's ok to call smol_scale_batch() without locking from multiple concurrent - * threads, as long as the outrows do not overlap. Make sure all workers are - * finished before you call smol_scale_destroy(). */ - -void smol_scale_batch (const SmolScaleCtx *scale_ctx, uint32_t first_outrow, uint32_t n_outrows); - -/* Like smol_scale_batch(), but will write the output rows to outrows_dest - * instead of relative to pixels_out address handed to smol_scale_new(). The - * other parameters from init (size, rowstride, etc) will still be used. */ - -void smol_scale_batch_full (const SmolScaleCtx *scale_ctx, - void *outrows_dest, - uint32_t first_outrow, uint32_t n_outrows); - -#ifdef __cplusplus -} -#endif - -#endif diff -Nru chafa-1.2.1/chafa/smolscale/smolscale-private.h chafa-1.12.4/chafa/smolscale/smolscale-private.h --- chafa-1.2.1/chafa/smolscale/smolscale-private.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa/smolscale/smolscale-private.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,171 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ - -/* Copyright © 2019 Hans Petter Jansson. See COPYING for details. */ - -#include -#include "smolscale.h" - -#ifndef _SMOLSCALE_PRIVATE_H_ -#define _SMOLSCALE_PRIVATE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "config.h" - -/* Enum switches must handle every value */ -#ifdef __GNUC__ -# pragma GCC diagnostic error "-Wswitch" -#endif - -#ifndef FALSE -# define FALSE (0) -#endif -#ifndef TRUE -# define TRUE (!FALSE) -#endif -#ifndef MIN -# define MIN(a, b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef MAX -# define MAX(a, b) ((a) > (b) ? (a) : (b)) -#endif - -typedef unsigned int SmolBool; - -#define SMOL_TEMP_ALIGNMENT 64 -#define SMOL_ASSUME_ALIGNED(x, t, a) (x) = (t) __builtin_assume_aligned ((x), (a)) -#define SMOL_ASSUME_TEMP_ALIGNED(x, t) (x) = (t) __builtin_assume_aligned ((x), SMOL_TEMP_ALIGNMENT) - -#define SMOL_UNUSED(x) (void) ((x)=(x)) -#define SMOL_RESTRICT __restrict -#define SMOL_INLINE __attribute__((always_inline)) inline -#define SMOL_CONST __attribute__((const)) -#define SMOL_PURE __attribute__((pure)) -#define SMOL_ALIGNED_4 __attribute__((aligned(4))) -#define SMOL_ALIGNED_8 __attribute__((aligned(8))) -#define SMOL_ALIGNED_16 __attribute__((aligned(16))) -#define SMOL_ALIGNED_32 __attribute__((aligned(32))) -#define SMOL_ALIGNED_64 __attribute__((aligned(64))) - -#define SMALL_MUL 256U -#define BIG_MUL 65536U -#define BOXES_MULTIPLIER ((uint64_t) BIG_MUL * SMALL_MUL) -#define BILIN_MULTIPLIER ((uint64_t) BIG_MUL * BIG_MUL) - -#define aligned_alloca(s, a) \ - ({ void *p = alloca ((s) + (a)); p = (void *) (((uintptr_t) (p) + (a)) & ~((a) - 1)); (p); }) - -typedef enum -{ - SMOL_STORAGE_64BPP, - SMOL_STORAGE_128BPP, - SMOL_STORAGE_MAX -} -SmolStorageType; - -typedef enum -{ - SMOL_FILTER_ONE, - SMOL_FILTER_BILINEAR_0H, - SMOL_FILTER_BILINEAR_1H, - SMOL_FILTER_BILINEAR_2H, - SMOL_FILTER_BILINEAR_3H, - SMOL_FILTER_BILINEAR_4H, - SMOL_FILTER_BILINEAR_5H, - SMOL_FILTER_BILINEAR_6H, - SMOL_FILTER_BOX, - - SMOL_FILTER_MAX -} -SmolFilterType; - -/* For reusing rows that have already undergone horizontal scaling */ -typedef struct -{ - uint32_t in_ofs; - uint64_t *parts_row [3]; -} -SmolVerticalCtx; - -typedef void (SmolUnpackRowFunc) (const uint32_t *row_in, - uint64_t *row_out, - uint32_t n_pixels); -typedef void (SmolPackRowFunc) (const uint64_t *row_in, - uint32_t *row_out, - uint32_t n_pixels); -typedef void (SmolHFilterFunc) (const SmolScaleCtx *scale_ctx, - const uint64_t *row_limbs_in, - uint64_t *row_limbs_out); -typedef void (SmolVFilterFunc) (const SmolScaleCtx *scale_ctx, - SmolVerticalCtx *vertical_ctx, - uint32_t outrow_index, - uint32_t *row_out); - -#define SMOL_CONV_UNDEFINED { 0, NULL, NULL } -#define SMOL_CONV(un_from_order, un_from_type, un_to_order, un_to_type, pk_from_order, pk_from_type, pk_to_order, pk_to_type, storage_bits) \ -{ storage_bits / 8, (SmolUnpackRowFunc *) unpack_row_##un_from_order##_##un_from_type##_to_##un_to_order##_##un_to_type##_##storage_bits##bpp, \ -(SmolPackRowFunc *) pack_row_##pk_from_order##_##pk_from_type##_to_##pk_to_order##_##pk_to_type##_##storage_bits##bpp } - -typedef struct -{ - uint8_t n_bytes_per_pixel; - SmolUnpackRowFunc *unpack_row_func; - SmolPackRowFunc *pack_row_func; -} -SmolConversion; - -typedef struct -{ - SmolConversion conversions [SMOL_STORAGE_MAX] [SMOL_PIXEL_MAX] [SMOL_PIXEL_MAX]; -} -SmolConversionTable; - -typedef struct -{ - SmolHFilterFunc *hfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; - SmolVFilterFunc *vfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; - - /* Can be a NULL pointer if the implementation does not override any - * conversions. */ - const SmolConversionTable *ctab; -} -SmolImplementation; - -struct SmolScaleCtx -{ - /* */ - - const uint32_t *pixels_in; - uint32_t *pixels_out; - uint32_t width_in, height_in, rowstride_in; - uint32_t width_out, height_out, rowstride_out; - - SmolPixelType pixel_type_in, pixel_type_out; - SmolFilterType filter_h, filter_v; - SmolStorageType storage_type; - - SmolUnpackRowFunc *unpack_row_func; - SmolPackRowFunc *pack_row_func; - SmolHFilterFunc *hfilter_func; - SmolVFilterFunc *vfilter_func; - - /* Each offset is split in two uint16s: { pixel index, fraction }. These - * are relative to the image after halvings have taken place. */ - uint16_t *offsets_x, *offsets_y; - uint32_t span_mul_x, span_mul_y; /* For box filter */ - - uint32_t width_bilin_out, height_bilin_out; - unsigned int width_halvings, height_halvings; -}; - -#ifdef SMOL_WITH_AVX2 -const SmolImplementation *_smol_get_avx2_implementation (void); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff -Nru chafa-1.2.1/chafa.pc.in chafa-1.12.4/chafa.pc.in --- chafa-1.2.1/chafa.pc.in 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/chafa.pc.in 2022-11-12 01:18:35.000000000 +0000 @@ -7,5 +7,6 @@ Description: Image to character art facsimile Requires: glib-2.0 Version: @VERSION@ -Libs: -L${libdir} -lchafa -lm -Cflags: -I${includedir}/chafa +Libs: -L${libdir} -lchafa +Libs.private: -lm +Cflags: -I${includedir}/chafa -I${libdir}/chafa/include diff -Nru chafa-1.2.1/configure.ac chafa-1.12.4/configure.ac --- chafa-1.2.1/configure.ac 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/configure.ac 2022-11-12 01:18:35.000000000 +0000 @@ -5,19 +5,19 @@ dnl --- Package configuration --- m4_define([chafa_major_version], [1]) -m4_define([chafa_minor_version], [2]) -m4_define([chafa_micro_version], [1]) +m4_define([chafa_minor_version], [12]) +m4_define([chafa_micro_version], [4]) m4_define([chafa_version], [chafa_major_version.chafa_minor_version.chafa_micro_version]) -AC_PREREQ(2.59) -AC_INIT([chafa], chafa_version, [hpj@copyleft.no]) +AC_PREREQ([2.69]) +AC_INIT([chafa],[chafa_version],[hpj@hpjansson.org]) AM_INIT_AUTOMAKE([1.9 foreign dist-xz no-dist-gzip -Wall]) AC_CONFIG_SRCDIR([chafa.pc.in]) AC_CONFIG_MACRO_DIRS([m4]) -AM_CONFIG_HEADER(config.h) +AC_CONFIG_HEADERS(config.h) CHAFA_MAJOR_VERSION=chafa_major_version CHAFA_MINOR_VERSION=chafa_minor_version @@ -36,16 +36,34 @@ dnl --- Standard setup --- +BASE_CFLAGS="-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26 \ +-Wall -Wextra -Wmissing-prototypes -Wwrite-strings -Wunused-macros -Wundef \ +-Wpointer-arith -Werror=format-security" + +# May want to look into -Wconversion sometime. For now, it's just too much noise. + +AC_USE_SYSTEM_EXTENSIONS AM_SANITY_CHECK AM_MAINTAINER_MODE AC_C_CONST AC_PROG_CC -AC_PROG_CC_C99 AC_PROG_CPP AC_PROG_INSTALL AM_PROG_AR -LT_INIT + +# We keep this obsolete macro around to allow configuration on older systems +# that require -std=c99, cf. CentOS 7. See github#113. +AC_PROG_CC_STDC + +LT_INIT([win32-dll]) + +dnl --- Check for extra compiler warnings --- + +AX_CHECK_COMPILE_FLAG([-Wstack-usage=131072],[BASE_CFLAGS="$BASE_CFLAGS -Wstack-usage=131072"],,[-Werror]) +AX_CHECK_COMPILE_FLAG([-Wfor-loop-analysis],[BASE_CFLAGS="$BASE_CFLAGS -Wfor-loop-analysis"],,[-Werror]) +AX_CHECK_COMPILE_FLAG([-Wlogical-op],[BASE_CFLAGS="$BASE_CFLAGS -Wlogical-op"],,[-Werror]) +AX_CHECK_COMPILE_FLAG([-Wlogical-op-parentheses],[BASE_CFLAGS="$BASE_CFLAGS -Wlogical-op-parentheses"],,[-Werror]) dnl --- Required standards --- @@ -71,16 +89,80 @@ dnl --- Dependency check --- -PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.10) +PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.26) AC_ARG_WITH(tools, [AS_HELP_STRING([--without-tools], [don't build command-line tools [default=on]])], , with_tools=yes) -AM_CONDITIONAL([WANT_TOOLS], [test "$with_tools" = "yes"]) AS_IF([test "$with_tools" != no], [ - PKG_CHECK_MODULES(MAGICKWAND, [MagickWand >= 6],, - [AC_MSG_ERROR([You need ImageMagick-devel (or libmagickwand-dev on debian) to build command-line tools, or pass --without-tools to build without.])])]) + dnl FreeType (required) + PKG_CHECK_MODULES(FREETYPE, [freetype2 >= 2.0.0],, + [AC_MSG_ERROR([You need freetype2-devel (or libfreetype6-dev on Debian) to build command-line tools, or pass --without-tools to build without.])]) + + dnl ImageMagick/MagickWand (optional) + AC_ARG_WITH(imagemagick, + [AS_HELP_STRING([--without-imagemagick], [don't build ImageMagick loader [default=on]])], + , + with_imagemagick=yes) + AS_IF([test "$with_imagemagick" != no], [PKG_CHECK_MODULES(MAGICKWAND, [MagickWand >= 6],, + missing_rpms="$missing_rpms ImageMagick-devel" + missing_debs="$missing_debs libmagickwand-dev" + with_imagemagick=no)]) + AS_IF([test "$with_imagemagick" != no], [AC_DEFINE([HAVE_MAGICKWAND], [1], [Define if we have ImageMagick support.])]) + + dnl libjpeg (optional) + AC_ARG_WITH(jpeg, + [AS_HELP_STRING([--without-jpeg], [don't build JPEG loader [default=on]])], + , + with_jpeg=yes) + AS_IF([test "$with_jpeg" != no], [PKG_CHECK_MODULES(JPEG, [libjpeg],, + missing_rpms="$missing_rpms libjpeg-devel" + missing_debs="$missing_debs libjpeg-dev" + with_jpeg=no)]) + AS_IF([test "$with_jpeg" != no], [AC_DEFINE([HAVE_JPEG], [1], [Define if we have JPEG support.])]) + + dnl librsvg (optional) + AC_ARG_WITH(svg, + [AS_HELP_STRING([--without-svg], [don't build SVG loader [default=on]])], + , + with_svg=yes) + AS_IF([test "$with_svg" != no], [PKG_CHECK_MODULES(SVG, [librsvg-2.0],, + missing_rpms="$missing_rpms librsvg-devel" + missing_debs="$missing_debs librsvg2-dev" + with_svg=no)]) + AS_IF([test "$with_svg" != no], [AC_DEFINE([HAVE_SVG], [1], [Define if we have SVG support.])]) + + dnl libtiff (optional) + AC_ARG_WITH(tiff, + [AS_HELP_STRING([--without-tiff], [don't build TIFF loader [default=on]])], + , + with_tiff=yes) + AS_IF([test "$with_tiff" != no], [PKG_CHECK_MODULES(TIFF, [libtiff-4],, + missing_rpms="$missing_rpms libtiff-devel" + missing_debs="$missing_debs libtiff-dev" + with_tiff=no)]) + AS_IF([test "$with_tiff" != no], [AC_DEFINE([HAVE_TIFF], [1], [Define if we have TIFF support.])]) + + dnl libwebp (optional) + AC_ARG_WITH(webp, + [AS_HELP_STRING([--without-webp], [don't build WebP loader [default=on]])], + , + with_webp=yes) + AS_IF([test "$with_webp" != no], [ + PKG_CHECK_MODULES(WEBP, [libwebpdemux],, + missing_rpms="$missing_rpms libwebp-devel" + missing_debs="$missing_debs libwebp-dev" + with_webp=no)]) + AS_IF([test "$with_webp" != no], [AC_DEFINE([HAVE_WEBP], [1], [Define if we have WebP support.])]) +]) + +AM_CONDITIONAL([WANT_TOOLS], [test "$with_tools" != no]) +AM_CONDITIONAL([HAVE_MAGICKWAND], [test "$with_tools" != no -a "$with_imagemagick" != no]) +AM_CONDITIONAL([HAVE_JPEG], [test "$with_tools" != no -a "$with_jpeg" != no]) +AM_CONDITIONAL([HAVE_SVG], [test "$with_tools" != no -a "$with_svg" != no]) +AM_CONDITIONAL([HAVE_TIFF], [test "$with_tools" != no -a "$with_tiff" != no]) +AM_CONDITIONAL([HAVE_WEBP], [test "$with_tools" != no -a "$with_webp" != no]) # Used by gtk-doc's fixxref. GLIB_PREFIX="`$PKG_CONFIG --variable=prefix glib-2.0`" @@ -147,7 +229,26 @@ dnl --- Specific checks --- -AC_CHECK_FUNCS(mmap) +AC_CHECK_FUNCS(ctermid getrandom mmap sigaction) +AC_CHECK_HEADERS(sys/ioctl.h termios.h windows.h) + +dnl +dnl Define IS_WIN32_BUILD if we're building for Microsoft Windows. In order to +dnl get UTF-8 support in command-line arguments and environment vars, we need +dnl to build a resource file with windres and link it in. +dnl + +is_windows_build=no +case "${host_os}" in + cygwin*|mingw*) + is_windows_build=yes + ;; +esac + +AM_CONDITIONAL([IS_WIN32_BUILD], + [test "x$is_windows_build" = "xyes"]) + +AC_CHECK_TOOL([WINDRES], [windres], [:]) dnl dnl Check for -Bsymbolic-functions linker flag used to avoid @@ -161,11 +262,9 @@ AC_MSG_CHECKING([for -Bsymbolic linker flag]) LDFLAGS=-Wl,-Bsymbolic LIBS= - AC_TRY_LINK([], [return 0], - AC_MSG_RESULT(yes) - enable_Bsymbolic=yes, - AC_MSG_RESULT(no) - enable_Bsymbolic=no) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[return 0]])],[AC_MSG_RESULT(yes) + enable_Bsymbolic=yes],[AC_MSG_RESULT(no) + enable_Bsymbolic=no]) LDFLAGS="${SAVED_LDFLAGS}" LIBS="${SAVED_LIBS}"]) if test "x${enable_Bsymbolic}" = "xyes"; then @@ -173,12 +272,12 @@ fi dnl -dnl Check for gcc x86 instruction set check builtins +dnl Check for runtime gcc x86 instruction set detection. Used in 'chafa-features.c'. dnl AC_CACHE_CHECK([for gcc __builtin_cpu_init function], [ax_cv_gcc_check_x86_cpu_init], - [AC_RUN_IFELSE( + [AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [__builtin_cpu_init ();])], [ax_cv_gcc_check_x86_cpu_init=yes], @@ -186,7 +285,7 @@ AC_CACHE_CHECK([for gcc __builtin_cpu_supports function], [ax_cv_gcc_check_x86_cpu_supports], - [AC_RUN_IFELSE( + [AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [__builtin_cpu_supports ("mmx");])], [ax_cv_gcc_check_x86_cpu_supports=yes], @@ -201,10 +300,10 @@ AC_MSG_CHECKING(for working MMX intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -mmmx" -AC_COMPILE_IFELSE( +AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include ]], - [[_mm_empty ();]])], + [[__m64 t [2] = { 0 }; t [0] = _mm_setzero_si64 ();]])], [AC_DEFINE([HAVE_MMX_INTRINSICS], [1], [Define if MMX intrinsics work.]) ac_cv_mmx_intrinsics=yes], [ac_cv_mmx_intrinsics=no]) @@ -216,7 +315,7 @@ AC_MSG_CHECKING(for working SSE 4.1 intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -msse4.1" -AC_COMPILE_IFELSE( +AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include ]], [[__m128i t = { 0 }; int r = _mm_test_all_ones (t);]])], @@ -231,7 +330,7 @@ AC_MSG_CHECKING(for working AVX2 intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -mavx2" -AC_COMPILE_IFELSE( +AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include ]], [[__m256i t = { 0 }; __m256i r = _mm256_abs_epi32 (t);]])], @@ -285,28 +384,35 @@ dnl On mingw32 we do -fvisibility=hidden and __declspec(dllexport) AC_DEFINE([_CHAFA_EXTERN], [__attribute__((visibility("default"))) __declspec(dllexport) extern], [Defines how to decorate public symbols while building]) - CFLAGS="${CFLAGS} -fvisibility=hidden" + CHAFA_VISIBILITY_CFLAGS="-fvisibility=hidden" ;; *) dnl On other compilers, check if we can do -fvisibility=hidden SAVED_CFLAGS="${CFLAGS}" CFLAGS="-fvisibility=hidden" AC_MSG_CHECKING([for -fvisibility=hidden compiler flag]) - AC_TRY_COMPILE([], [return 0], - AC_MSG_RESULT(yes) - enable_fvisibility_hidden=yes, - AC_MSG_RESULT(no) - enable_fvisibility_hidden=no) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[return 0]])],[AC_MSG_RESULT(yes) + enable_fvisibility_hidden=yes],[AC_MSG_RESULT(no) + enable_fvisibility_hidden=no]) CFLAGS="${SAVED_CFLAGS}" AS_IF([test "${enable_fvisibility_hidden}" = "yes"], [ AC_DEFINE([_CHAFA_EXTERN], [__attribute__((visibility("default"))) extern], - [defines how to decorate public symbols while building]) + [Defines how to decorate public symbols while building]) CHAFA_VISIBILITY_CFLAGS="-fvisibility=hidden" ]) ;; esac +dnl +dnl We're not picky about floating point behavior, and this makes e.g. +dnl lrintf() a lot faster. +dnl +AX_CHECK_COMPILE_FLAG([-ffast-math], + [BASE_CFLAGS="$BASE_CFLAGS -ffast-math"], + , + [-Werror]) + dnl --- ImageMagick checks --- dnl ImageMagick 6 needs #include @@ -323,7 +429,7 @@ CFLAGS="$CFLAGS $MAGICKWAND_CFLAGS" SAVED_LDFLAGS=$LDFLAGS LDFLAGS="$MAGICKWAND_LIBS $LDFLAGS" -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_WAND_MAGICKWAND_H # include #else @@ -341,7 +447,7 @@ CFLAGS="$CFLAGS $MAGICKWAND_CFLAGS" SAVED_LDFLAGS=$LDFLAGS LDFLAGS="$MAGICKWAND_LIBS $LDFLAGS" -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_WAND_MAGICKWAND_H # include #else @@ -374,42 +480,167 @@ dnl --- Set compiler flags --- -BASE_CFLAGS="-Wall -Wextra -Wmissing-prototypes" +dnl Disable some LodePNG features. In particular, the CRC feature, which would +dnl sometimes cause valid images to not load because of issues in the +dnl encoding software. + +LODEPNG_FEATURES="\ +-DLODEPNG_NO_COMPILE_ENCODER \ +-DLODEPNG_NO_COMPILE_DISK \ +-DLODEPNG_NO_COMPILE_CPP \ +-DLODEPNG_NO_COMPILE_CRC" + +LODEPNG_CFLAGS="$BASE_CFLAGS $LODEPNG_FEATURES" +LODEPNG_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" + +LIBNSGIF_CFLAGS="$BASE_CFLAGS" +LIBNSGIF_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" LIBCHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS" LIBCHAFA_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" -CHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS" +CHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS $LODEPNG_FEATURES" CHAFA_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" +AC_SUBST(LODEPNG_CFLAGS) +AC_SUBST(LODEPNG_LDFLAGS) + +AC_SUBST(LIBNSGIF_CFLAGS) +AC_SUBST(LIBNSGIF_LDFLAGS) + AC_SUBST(LIBCHAFA_CFLAGS) AC_SUBST(LIBCHAFA_LDFLAGS) AC_SUBST(CHAFA_CFLAGS) AC_SUBST(CHAFA_LDFLAGS) +AC_ARG_ENABLE(rpath, + [AS_HELP_STRING([--enable-rpath], + [use rpath [default=no]])]) + +AM_CONDITIONAL(ENABLE_RPATH, test "$enable_rpath" == yes) + dnl --- Output --- -AC_OUTPUT(Makefile - chafa/Makefile - chafa/smolscale/Makefile - libnsgif/Makefile - chafa.pc - docs/Makefile - docs/version.xml - tools/Makefile - tools/chafa/Makefile - tools/fontgen/Makefile) +AC_CONFIG_FILES([Makefile + chafa/Makefile + chafa/chafaconfig.h + chafa/internal/Makefile + chafa/internal/smolscale/Makefile + libnsgif/Makefile + lodepng/Makefile + chafa.pc + docs/Makefile + docs/version.xml + tests/Makefile + tests/data/Makefile + tests/data/bad/Makefile + tests/data/good/Makefile + tools/Makefile + tools/chafa/Makefile + tools/fontgen/Makefile]) +AC_OUTPUT + +dnl --- Print a neatly colorized summary --- + +colorize_vars=" + enable_man + ac_cv_mmx_intrinsics + ac_cv_sse41_intrinsics + ac_cv_avx2_intrinsics + ac_cv_popcnt32_intrinsics + ac_cv_popcnt64_intrinsics + with_tools + with_imagemagick + with_jpeg + with_svg + with_tiff + with_webp +" + +dnl Only use colors if the terminal supports the aixterm-style bright ones (16 total). + +cols=$(tput colors 2>/dev/null) + +if test ${cols:--1} -ge 16; then + normal=$(tput sgr0) + red=$(tput setaf 9) + green=$(tput setaf 10) + yellow=$(tput setaf 11) + blue=$(tput setaf 12) + + pyes=${green}yes${normal} + pno=${red}no${normal} + pyno=${yellow}no${normal} +else + normal= + red= + green= + yellow= + blue= + + pyes=yes + pno=no + pyno=no +fi + +dnl Gross. At least make sure eval arguments are sanitized. +for i in $colorize_vars; do + eval state=\$$i + if test x$state != xno; then state=yes; fi + eval p$i=\$p$state +done + +dnl gtk-doc needs special handling; since docs come pregenerated in the tarball, "no" +dnl here is less critical, so use a different color. Also, enable_gtk_doc can be empty. +if test x$enable_gtk_doc = xyes; then + penable_gtk_doc=$pyes +else + penable_gtk_doc=$pyno +fi + +echo >&AS_MESSAGE_FD $normal +echo >&AS_MESSAGE_FD "Build man page .............. $penable_man" +echo >&AS_MESSAGE_FD "Rebuild API documentation ... $penable_gtk_doc (--enable-gtk-doc)" +echo >&AS_MESSAGE_FD "Support MMX ................. $pac_cv_mmx_intrinsics" +echo >&AS_MESSAGE_FD "Support SSE 4.1 ............. $pac_cv_sse41_intrinsics" +echo >&AS_MESSAGE_FD "Support AVX2 ................ $pac_cv_avx2_intrinsics" +echo >&AS_MESSAGE_FD "Support popcount32 .......... $pac_cv_popcnt32_intrinsics" +echo >&AS_MESSAGE_FD "Support popcount64 .......... $pac_cv_popcnt64_intrinsics" +echo >&AS_MESSAGE_FD +echo >&AS_MESSAGE_FD "Build command-line tool ..... $pwith_tools" + +if test "x$with_tools" != xno; then +echo >&AS_MESSAGE_FD "With GIF loader ............. $pyes (internal)" +echo >&AS_MESSAGE_FD "With ImageMagick loader ..... $pwith_imagemagick" +echo >&AS_MESSAGE_FD "With JPEG loader ............ $pwith_jpeg" +echo >&AS_MESSAGE_FD "With PNG loader ............. $pyes (internal)" +echo >&AS_MESSAGE_FD "With SVG loader ............. $pwith_svg" +echo >&AS_MESSAGE_FD "With TIFF loader ............ $pwith_tiff" +echo >&AS_MESSAGE_FD "With WebP loader ............ $pwith_webp" +echo >&AS_MESSAGE_FD "With XWD loader ............. $pyes (internal)" +fi + +echo >&AS_MESSAGE_FD +echo >&AS_MESSAGE_FD "Install prefix .............. $blue$prefix$normal" +echo >&AS_MESSAGE_FD +echo >&AS_MESSAGE_FD "You can now type ${blue}gmake${normal} or ${blue}make${normal} to build the project." + +dnl --- Warn about missing dependencies --- + +dnl Remove leading spaces. +missing_rpms=$(echo $missing_rpms | sed 's/^ *//') +missing_debs=$(echo $missing_debs | sed 's/^ *//') +if test "x$missing_rpms" != x; then echo >&AS_MESSAGE_FD -echo >&AS_MESSAGE_FD "Build command-line tool ..... $with_tools" -echo >&AS_MESSAGE_FD "Build man page .............. $enable_man" -echo >&AS_MESSAGE_FD "Rebuild API documentation ... ${enable_gtk_doc:-no} (--enable-gtk-doc)" -echo >&AS_MESSAGE_FD "Support MMX ................. $ac_cv_mmx_intrinsics" -echo >&AS_MESSAGE_FD "Support SSE 4.1 ............. $ac_cv_sse41_intrinsics" -echo >&AS_MESSAGE_FD "Support AVX2 ................ $ac_cv_avx2_intrinsics" -echo >&AS_MESSAGE_FD "Support popcount32 .......... $ac_cv_popcnt32_intrinsics" -echo >&AS_MESSAGE_FD "Support popcount64 .......... $ac_cv_popcnt64_intrinsics" -echo >&AS_MESSAGE_FD "Install prefix .............. $prefix" +echo >&AS_MESSAGE_FD "Some optional libraries were not found. You may want to install these and" +echo >&AS_MESSAGE_FD "run configure again (package names may be different on your system)." echo >&AS_MESSAGE_FD -echo >&AS_MESSAGE_FD "You can now type \"gmake\" or \"make\" to build the project." +echo >&AS_MESSAGE_FD "On Fedora, openSUSE or similar:" +echo >&AS_MESSAGE_FD "${blue}${missing_rpms}${normal}" +echo >&AS_MESSAGE_FD +echo >&AS_MESSAGE_FD "On Debian, Ubuntu or similar:" +echo >&AS_MESSAGE_FD "${blue}${missing_debs}${normal}" +echo >&AS_MESSAGE_FD +fi diff -Nru chafa-1.2.1/debian/changelog chafa-1.12.4/debian/changelog --- chafa-1.2.1/debian/changelog 2019-08-15 15:22:15.000000000 +0000 +++ chafa-1.12.4/debian/changelog 2023-01-27 21:08:14.000000000 +0000 @@ -1,3 +1,126 @@ +chafa (1.12.4-1~20.04.sav0) focal; urgency=medium + + * Backport to Focal + * Revert "Remove dh_missing override" (compat level < 13) + * debian/control: Set debhelper-compat (= 12) BD + + -- Rob Savoury Fri, 27 Jan 2023 13:08:14 -0800 + +chafa (1.12.4-1) unstable; urgency=medium + + * New upstream version 1.12.4 (Nov 2022) + + -- Mo Zhou Sat, 12 Nov 2022 19:28:36 -0500 + +chafa (1.12.3-1) unstable; urgency=medium + + * New upstream version 1.12.3 (June 30 2022) + + -- Mo Zhou Thu, 30 Jun 2022 17:13:42 -0700 + +chafa (1.12.1-1) unstable; urgency=medium + + * New upstream version 1.12.1 (Fixes CVE-2022-2061) + + -- Mo Zhou Sun, 19 Jun 2022 16:23:41 -0700 + +chafa (1.12.0-1) unstable; urgency=medium + + * New upstream version 1.12.0 + * Add new B-D libwebp-dev. + * Apply wrap-and-sort. + * Run upstream testing program via make check. + * Refresh symbols control file. + + -- Mo Zhou Sun, 05 Jun 2022 19:15:15 -0700 + +chafa (1.10.3-1) unstable; urgency=medium + + * New upstream version 1.10.3 + + -- Mo Zhou Wed, 04 May 2022 10:40:03 -0400 + +chafa (1.10.2-1) unstable; urgency=medium + + * New upstream version 1.10.2 + + -- Mo Zhou Mon, 25 Apr 2022 00:35:24 -0400 + +chafa (1.10.1-1) unstable; urgency=medium + + * New upstream version 1.10.1 + + -- Mo Zhou Sun, 03 Apr 2022 19:39:40 -0400 + +chafa (1.10.0-1) unstable; urgency=medium + + [ Debian Janitor ] + * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, + Repository-Browse. + * Update standards version to 4.6.0, no changes needed. + * Avoid explicitly specifying -Wl,--as-needed linker flag. + + [ Mo Zhou ] + * New upstream version 1.10.0 + * Update copyright with cme update dpkg-copyright. + * Refresh symbols control file with additions. + + -- Mo Zhou Sun, 20 Mar 2022 21:58:27 -0400 + +chafa (1.8.0-1) unstable; urgency=medium + + [ Boyuan Yang ] + * Manually create m4 directory prior to configure to fix FTBFS + with autoconf 2.70+. (Closes: #978781) + + [ Mo Zhou ] + * New upstream version 1.8.0 + + -- Mo Zhou Sun, 05 Sep 2021 16:22:16 -0400 + +chafa (1.6.0-1) unstable; urgency=medium + + * New upstream version 1.6.0 + * Refresh symbols control file. + + -- Mo Zhou Fri, 15 Jan 2021 11:03:13 +0800 + +chafa (1.4.1-2) unstable; urgency=medium + + * Mark autopkgtest as superficial. (Closes: #969809) + * Bump debhelper compat level to 13. + * Remove dh_missing override as it defaults to --fail-missing since compat 13. + * Specify Build-Depends-Package: libchafa-dev in the symbols control file. + + -- Mo Zhou Tue, 15 Sep 2020 19:03:33 +0800 + +chafa (1.4.1-1) unstable; urgency=medium + + * New upstream version 1.4.1 + * Remove the merged rpath.patch. + + -- Mo Zhou Sat, 11 Apr 2020 09:21:02 +0800 + +chafa (1.4.0-1) unstable; urgency=medium + + * New upstream version 1.4.0 + * Change dh_missing behavior to fail-missing. + * Update symbols control file: new ABI, no breaking change. + * Install the auto-generated config header to the -dev package. + * Patch autoconf files, allowing us to disable the RPATH option. + + -- Mo Zhou Thu, 09 Apr 2020 12:11:55 +0800 + +chafa (1.2.2-1) unstable; urgency=medium + + * New upstream version 1.2.2 + * Refresh symbols control file. + * Update my own mail address in d/copyright and d/control. + * Specify Rules-Requires-Root: no. + * Bump Standards-Version to 4.5.0 (no change). + + -- Mo Zhou Sat, 14 Mar 2020 10:49:19 +0800 + chafa (1.2.1-1) unstable; urgency=medium * New upstream version 1.2.1 diff -Nru chafa-1.2.1/debian/clean chafa-1.12.4/debian/clean --- chafa-1.2.1/debian/clean 2019-08-07 04:39:15.000000000 +0000 +++ chafa-1.12.4/debian/clean 2022-11-13 00:25:10.000000000 +0000 @@ -1,2 +1,3 @@ gtk-doc.m4 gtk-doc.make +m4 diff -Nru chafa-1.2.1/debian/control chafa-1.12.4/debian/control --- chafa-1.2.1/debian/control 2019-08-07 05:57:39.000000000 +0000 +++ chafa-1.12.4/debian/control 2023-01-27 21:06:49.000000000 +0000 @@ -1,24 +1,23 @@ Source: chafa Section: graphics Homepage: https://hpjansson.org/chafa/ -# Upstream-Git: https://github.com/hpjansson/chafa Priority: optional -Standards-Version: 4.4.0 +Standards-Version: 4.6.0 Vcs-Git: https://salsa.debian.org/debian/chafa.git Vcs-Browser: https://salsa.debian.org/debian/chafa -Maintainer: Mo Zhou -Build-Depends: - debhelper-compat (=12), - libmagickwand-dev, - gtk-doc-tools, - libxml2-utils, - docbook, +Maintainer: Mo Zhou +Build-Depends: debhelper-compat (= 12), + docbook, + gtk-doc-tools, + libmagickwand-dev, + libwebp-dev, + libxml2-utils +Rules-Requires-Root: no Package: chafa Architecture: any Multi-Arch: foreign -Depends: ${misc:Depends}, ${shlibs:Depends}, - libchafa0 (= ${binary:Version}), +Depends: libchafa0 (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends} Description: Image-to-text converter supporting a wide range of symbols, etc. Chafa is a command-line utility that converts all kinds of images, including animated image formats like GIFs, into ANSI/Unicode character output that can @@ -37,7 +36,7 @@ Section: libs Architecture: any Multi-Arch: same -Depends: ${misc:Depends}, ${shlibs:Depends}, +Depends: ${misc:Depends}, ${shlibs:Depends} Description: library for image-to-text converter chafa Chafa is a command-line utility that converts all kinds of images, including animated image formats like GIFs, into ANSI/Unicode character output that can @@ -56,8 +55,7 @@ Section: libdevel Architecture: any Multi-Arch: same -Depends: ${misc:Depends}, - libchafa0 (= ${binary:Version}), +Depends: libchafa0 (= ${binary:Version}), ${misc:Depends} Description: development files for image-to-text converter chafa Chafa is a command-line utility that converts all kinds of images, including animated image formats like GIFs, into ANSI/Unicode character output that can diff -Nru chafa-1.2.1/debian/copyright chafa-1.12.4/debian/copyright --- chafa-1.2.1/debian/copyright 2019-08-07 05:29:05.000000000 +0000 +++ chafa-1.12.4/debian/copyright 2022-11-13 00:25:10.000000000 +0000 @@ -3,9 +3,33 @@ Upstream-Contact: Hans Petter Jansson Source: https://github.com/hpjansson/chafa -Files: * -Copyright: 2018, Hans Petter Jansson -License: LGPL-3.0+ +Files: * + chafa/* +Copyright: 2018-2022, Hans Petter Jansson +License: LGPL-3.0+ + +Files: debian/* +Copyright: 2018-2022, Mo Zhou +License: Expat + +Files: libnsgif/* +Copyright: 2008, Sean Fox + 2004, Richard Wilson + 2003, James Bursa + 2004, John Tytgat + 2017, Michael Drake +License: Expat or MIT + +Files: lodepng/* +Copyright: 2005-2022, Lode Vandevenne + 2018, Hans Petter Jansson +License: LGPL-3.0+ or Zlib + +Files: tools/fontgen/* +Copyright: 2018, Mo Zhou +License: LGPL-3.0+ + +License: LGPL-3.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -22,9 +46,25 @@ On Debian systems, the complete text of the GNU Lesser General Public License can be found in "/usr/share/common-licenses/LGPL-3". -Files: libnsgif/* -Copyright: 2004 Richard Wilson - 2008 Sean Fox +License: Expat + 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. + License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -44,23 +84,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Files: debian/* -Copyright: 2018 Mo Zhou -License: Expat - 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: +License: Zlib + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + . + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + . + 1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. . - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original 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. + 3. This notice may not be removed or altered from any source + distribution. diff -Nru chafa-1.2.1/debian/libchafa0.symbols chafa-1.12.4/debian/libchafa0.symbols --- chafa-1.2.1/debian/libchafa0.symbols 2019-08-07 04:48:20.000000000 +0000 +++ chafa-1.12.4/debian/libchafa0.symbols 2022-11-13 00:25:10.000000000 +0000 @@ -1,15 +1,21 @@ libchafa.so.0 libchafa0 #MINVER# +* Build-Depends-Package: libchafa-dev chafa_calc_canvas_geometry@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_build_ansi@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_copy@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_get_bg_color@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_get_canvas_mode@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_config_get_cell_geometry@Base 1.4.0 + chafa_canvas_config_get_color_extractor@Base 1.4.0 chafa_canvas_config_get_color_space@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_get_dither_grain_size@Base 1.2.0 chafa_canvas_config_get_dither_intensity@Base 1.2.0 chafa_canvas_config_get_dither_mode@Base 1.2.0 chafa_canvas_config_get_fg_color@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_config_get_fg_only_enabled@Base 1.10.0 chafa_canvas_config_get_geometry@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_config_get_optimizations@Base 1.6.0 + chafa_canvas_config_get_pixel_mode@Base 1.4.0 chafa_canvas_config_get_preprocessing_enabled@Base 0.9.0+git20180820.609f4c5 chafa_canvas_config_get_transparency_threshold@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_get_work_factor@Base 0.9.0+git20180731.5ddfe4c @@ -19,31 +25,124 @@ chafa_canvas_config_ref@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_set_bg_color@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_set_canvas_mode@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_config_set_cell_geometry@Base 1.4.0 + chafa_canvas_config_set_color_extractor@Base 1.4.0 chafa_canvas_config_set_color_space@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_set_dither_grain_size@Base 1.2.0 chafa_canvas_config_set_dither_intensity@Base 1.2.0 chafa_canvas_config_set_dither_mode@Base 1.2.0 chafa_canvas_config_set_fg_color@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_config_set_fg_only_enabled@Base 1.10.0 chafa_canvas_config_set_fill_symbol_map@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_set_geometry@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_config_set_optimizations@Base 1.6.0 + chafa_canvas_config_set_pixel_mode@Base 1.4.0 chafa_canvas_config_set_preprocessing_enabled@Base 0.9.0+git20180820.609f4c5 chafa_canvas_config_set_symbol_map@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_set_transparency_threshold@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_set_work_factor@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_config_unref@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_draw_all_pixels@Base 1.2.0 + chafa_canvas_get_char_at@Base 1.10.0 + chafa_canvas_get_colors_at@Base 1.10.0 + chafa_canvas_get_raw_colors_at@Base 1.10.0 chafa_canvas_new@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_new_similar@Base 0.9.0+git20180731.5ddfe4c chafa_canvas_peek_config@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_print@Base 1.6.0 chafa_canvas_ref@Base 0.9.0+git20180731.5ddfe4c + chafa_canvas_set_char_at@Base 1.10.0 + chafa_canvas_set_colors_at@Base 1.10.0 + chafa_canvas_set_contents_rgba8@Base 1.2.2 + chafa_canvas_set_raw_colors_at@Base 1.10.0 chafa_canvas_unref@Base 0.9.0+git20180731.5ddfe4c chafa_describe_features@Base 0.9.0+git20180731.5ddfe4c chafa_get_builtin_features@Base 0.9.0+git20180731.5ddfe4c + chafa_get_n_actual_threads@Base 1.10.0 + chafa_get_n_threads@Base 1.10.0 chafa_get_supported_features@Base 0.9.0+git20180731.5ddfe4c + chafa_set_n_threads@Base 1.10.0 + chafa_symbol_map_add_by_range@Base 1.4.0 chafa_symbol_map_add_by_tags@Base 0.9.0+git20180731.5ddfe4c + chafa_symbol_map_add_glyph@Base 1.4.0 chafa_symbol_map_apply_selectors@Base 0.9.0+git20180731.5ddfe4c chafa_symbol_map_copy@Base 0.9.0+git20180731.5ddfe4c + chafa_symbol_map_get_allow_builtin_glyphs@Base 1.4.0 + chafa_symbol_map_get_glyph@Base 1.12.0 chafa_symbol_map_new@Base 0.9.0+git20180731.5ddfe4c chafa_symbol_map_ref@Base 0.9.0+git20180731.5ddfe4c + chafa_symbol_map_remove_by_range@Base 1.4.0 chafa_symbol_map_remove_by_tags@Base 0.9.0+git20180731.5ddfe4c + chafa_symbol_map_set_allow_builtin_glyphs@Base 1.4.0 chafa_symbol_map_unref@Base 0.9.0+git20180731.5ddfe4c + chafa_term_db_copy@Base 1.6.0 + chafa_term_db_detect@Base 1.6.0 + chafa_term_db_get_default@Base 1.6.0 + chafa_term_db_get_fallback_info@Base 1.6.0 + chafa_term_db_new@Base 1.6.0 + chafa_term_db_ref@Base 1.6.0 + chafa_term_db_unref@Base 1.6.0 + chafa_term_info_copy@Base 1.6.0 + chafa_term_info_emit_begin_iterm2_image@Base 1.10.0 + chafa_term_info_emit_begin_kitty_image_chunk@Base 1.10.0 + chafa_term_info_emit_begin_kitty_immediate_image_v1@Base 1.10.0 + chafa_term_info_emit_begin_sixels@Base 1.6.0 + chafa_term_info_emit_clear@Base 1.6.0 + chafa_term_info_emit_cursor_down@Base 1.6.0 + chafa_term_info_emit_cursor_down_1@Base 1.6.0 + chafa_term_info_emit_cursor_down_scroll@Base 1.6.0 + chafa_term_info_emit_cursor_left@Base 1.6.0 + chafa_term_info_emit_cursor_left_1@Base 1.6.0 + chafa_term_info_emit_cursor_right@Base 1.6.0 + chafa_term_info_emit_cursor_right_1@Base 1.6.0 + chafa_term_info_emit_cursor_to_bottom_left@Base 1.6.0 + chafa_term_info_emit_cursor_to_pos@Base 1.6.0 + chafa_term_info_emit_cursor_to_top_left@Base 1.6.0 + chafa_term_info_emit_cursor_up@Base 1.6.0 + chafa_term_info_emit_cursor_up_1@Base 1.6.0 + chafa_term_info_emit_cursor_up_scroll@Base 1.6.0 + chafa_term_info_emit_delete_cells@Base 1.6.0 + chafa_term_info_emit_delete_rows@Base 1.6.0 + chafa_term_info_emit_disable_cursor@Base 1.6.0 + chafa_term_info_emit_disable_echo@Base 1.6.0 + chafa_term_info_emit_disable_insert@Base 1.6.0 + chafa_term_info_emit_disable_sixel_scrolling@Base 1.10.0 + chafa_term_info_emit_disable_wrap@Base 1.6.0 + chafa_term_info_emit_enable_bold@Base 1.12.0 + chafa_term_info_emit_enable_cursor@Base 1.6.0 + chafa_term_info_emit_enable_echo@Base 1.6.0 + chafa_term_info_emit_enable_insert@Base 1.6.0 + chafa_term_info_emit_enable_sixel_scrolling@Base 1.10.0 + chafa_term_info_emit_enable_wrap@Base 1.6.0 + chafa_term_info_emit_end_iterm2_image@Base 1.10.0 + chafa_term_info_emit_end_kitty_image@Base 1.10.0 + chafa_term_info_emit_end_kitty_image_chunk@Base 1.10.0 + chafa_term_info_emit_end_sixels@Base 1.6.0 + chafa_term_info_emit_insert_cells@Base 1.6.0 + chafa_term_info_emit_insert_rows@Base 1.6.0 + chafa_term_info_emit_invert_colors@Base 1.6.0 + chafa_term_info_emit_repeat_char@Base 1.6.0 + chafa_term_info_emit_reset_attributes@Base 1.6.0 + chafa_term_info_emit_reset_terminal_hard@Base 1.6.0 + chafa_term_info_emit_reset_terminal_soft@Base 1.6.0 + chafa_term_info_emit_set_color_bg_16@Base 1.6.0 + chafa_term_info_emit_set_color_bg_256@Base 1.6.0 + chafa_term_info_emit_set_color_bg_8@Base 1.12.0 + chafa_term_info_emit_set_color_bg_direct@Base 1.6.0 + chafa_term_info_emit_set_color_fg_16@Base 1.6.0 + chafa_term_info_emit_set_color_fg_256@Base 1.6.0 + chafa_term_info_emit_set_color_fg_8@Base 1.12.0 + chafa_term_info_emit_set_color_fg_direct@Base 1.6.0 + chafa_term_info_emit_set_color_fgbg_16@Base 1.6.0 + chafa_term_info_emit_set_color_fgbg_256@Base 1.6.0 + chafa_term_info_emit_set_color_fgbg_8@Base 1.12.0 + chafa_term_info_emit_set_color_fgbg_direct@Base 1.6.0 + chafa_term_info_emit_set_scrolling_rows@Base 1.6.0 + chafa_term_info_error_quark@Base 1.6.0 + chafa_term_info_get_seq@Base 1.6.0 + chafa_term_info_have_seq@Base 1.6.0 + chafa_term_info_new@Base 1.6.0 + chafa_term_info_ref@Base 1.6.0 + chafa_term_info_set_seq@Base 1.6.0 + chafa_term_info_supplement@Base 1.6.0 + chafa_term_info_unref@Base 1.6.0 diff -Nru chafa-1.2.1/debian/libchafa-dev.install chafa-1.12.4/debian/libchafa-dev.install --- chafa-1.2.1/debian/libchafa-dev.install 2019-08-07 04:39:15.000000000 +0000 +++ chafa-1.12.4/debian/libchafa-dev.install 2022-11-13 00:25:10.000000000 +0000 @@ -1,4 +1,5 @@ usr/include/chafa -usr/lib/*/pkgconfig/chafa.pc -usr/lib/*/libchafa.so +usr/lib/*/chafa/include/*.h usr/lib/*/libchafa.a +usr/lib/*/libchafa.so +usr/lib/*/pkgconfig/chafa.pc diff -Nru chafa-1.2.1/debian/rules chafa-1.12.4/debian/rules --- chafa-1.2.1/debian/rules 2019-08-07 05:49:50.000000000 +0000 +++ chafa-1.12.4/debian/rules 2023-01-27 21:08:14.000000000 +0000 @@ -1,7 +1,6 @@ #!/usr/bin/make -f export DEB_BUILD_MAINT_OPTIONS = hardening=+all -export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed %: dh $@ @@ -10,8 +9,15 @@ touch gtk-doc.make echo "EXTRA_DIST =" >> gtk-doc.make echo "CLEANFILES =" >> gtk-doc.make + mkdir m4 # needed by autoconf 2.70+ gtkdocize dh_autoreconf +override_dh_auto_configure: + dh_auto_configure -- --enable-rpath=no + +override_dh_auto_test: + $(MAKE) check + override_dh_missing: - dh_missing --list-missing + dh_missing --fail-missing diff -Nru chafa-1.2.1/debian/tests/control chafa-1.12.4/debian/tests/control --- chafa-1.2.1/debian/tests/control 2019-08-07 04:39:15.000000000 +0000 +++ chafa-1.12.4/debian/tests/control 2022-11-13 00:25:10.000000000 +0000 @@ -1,3 +1,2 @@ -# simply run the program to see if it fails Test-Command: chafa --version -Restrictions: allow-stderr +Restrictions: allow-stderr, superficial diff -Nru chafa-1.2.1/debian/upstream/metadata chafa-1.12.4/debian/upstream/metadata --- chafa-1.2.1/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/debian/upstream/metadata 2022-11-13 00:25:10.000000000 +0000 @@ -0,0 +1,5 @@ +--- +Bug-Database: https://github.com/hpjansson/chafa/issues +Bug-Submit: https://github.com/hpjansson/chafa/issues/new +Repository: https://github.com/hpjansson/chafa.git +Repository-Browse: https://github.com/hpjansson/chafa diff -Nru chafa-1.2.1/docs/chafa-docs.xml chafa-1.12.4/docs/chafa-docs.xml --- chafa-1.2.1/docs/chafa-docs.xml 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/docs/chafa-docs.xml 2022-11-12 01:18:35.000000000 +0000 @@ -28,9 +28,12 @@ + + + API Index diff -Nru chafa-1.2.1/docs/chafa-sections.txt chafa-1.12.4/docs/chafa-sections.txt --- chafa-1.2.1/docs/chafa-sections.txt 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/docs/chafa-sections.txt 2022-11-12 01:18:35.000000000 +0000 @@ -1,22 +1,31 @@
chafa-canvas ChafaCanvas -ChafaPixelType chafa_canvas_new chafa_canvas_new_similar chafa_canvas_ref chafa_canvas_unref chafa_canvas_peek_config chafa_canvas_draw_all_pixels +chafa_canvas_print +chafa_canvas_get_char_at +chafa_canvas_set_char_at +chafa_canvas_get_colors_at +chafa_canvas_set_colors_at +chafa_canvas_get_raw_colors_at +chafa_canvas_set_raw_colors_at chafa_canvas_build_ansi chafa_canvas_set_contents_rgba8
chafa-canvas-config +ChafaPixelMode ChafaColorSpace ChafaCanvasMode ChafaDitherMode +ChafaColorExtractor +ChafaOptimizations ChafaCanvasConfig chafa_canvas_config_new chafa_canvas_config_copy @@ -24,12 +33,16 @@ chafa_canvas_config_unref chafa_canvas_config_get_geometry chafa_canvas_config_set_geometry +chafa_canvas_config_get_cell_geometry +chafa_canvas_config_set_cell_geometry +chafa_canvas_config_get_pixel_mode +chafa_canvas_config_set_pixel_mode chafa_canvas_config_get_canvas_mode chafa_canvas_config_set_canvas_mode +chafa_canvas_config_get_color_extractor +chafa_canvas_config_set_color_extractor chafa_canvas_config_get_color_space chafa_canvas_config_set_color_space -chafa_canvas_config_get_dither_mode -chafa_canvas_config_set_dither_mode chafa_canvas_config_get_preprocessing_enabled chafa_canvas_config_set_preprocessing_enabled chafa_canvas_config_peek_symbol_map @@ -38,6 +51,8 @@ chafa_canvas_config_set_fill_symbol_map chafa_canvas_config_get_transparency_threshold chafa_canvas_config_set_transparency_threshold +chafa_canvas_config_get_fg_only_enabled +chafa_canvas_config_set_fg_only_enabled chafa_canvas_config_get_fg_color chafa_canvas_config_set_fg_color chafa_canvas_config_get_bg_color @@ -50,6 +65,8 @@ chafa_canvas_config_set_dither_grain_size chafa_canvas_config_get_dither_intensity chafa_canvas_config_set_dither_intensity +chafa_canvas_config_get_optimizations +chafa_canvas_config_set_optimizations
@@ -58,6 +75,9 @@ chafa_get_builtin_features chafa_get_supported_features chafa_describe_features +chafa_get_n_threads +chafa_set_n_threads +chafa_get_n_actual_threads
@@ -71,11 +91,111 @@ chafa_symbol_map_ref chafa_symbol_map_unref chafa_symbol_map_add_by_tags +chafa_symbol_map_add_by_range chafa_symbol_map_remove_by_tags +chafa_symbol_map_remove_by_range chafa_symbol_map_apply_selectors +chafa_symbol_map_get_allow_builtin_glyphs +chafa_symbol_map_set_allow_builtin_glyphs +chafa_symbol_map_get_glyph +chafa_symbol_map_add_glyph
chafa-util +CHAFA_VERSION_MIN_REQUIRED +CHAFA_VERSION_MAX_ALLOWED +CHAFA_VERSION_1_0 +CHAFA_VERSION_1_2 +CHAFA_VERSION_1_4 +CHAFA_VERSION_1_6 +CHAFA_VERSION_1_8 +CHAFA_VERSION_1_10 +CHAFA_VERSION_1_12 +ChafaPixelType chafa_calc_canvas_geometry
+ +
+chafa-term-info +CHAFA_TERM_SEQ_LENGTH_MAX +ChafaTermSeq +ChafaTermInfo +CHAFA_TERM_INFO_ERROR +ChafaTermInfoError +chafa_term_info_new +chafa_term_info_copy +chafa_term_info_ref +chafa_term_info_unref +chafa_term_info_get_seq +chafa_term_info_set_seq +chafa_term_info_have_seq +chafa_term_info_supplement +chafa_term_info_emit_reset_terminal_soft +chafa_term_info_emit_reset_terminal_hard +chafa_term_info_emit_reset_attributes +chafa_term_info_emit_clear +chafa_term_info_emit_cursor_to_pos +chafa_term_info_emit_cursor_to_top_left +chafa_term_info_emit_cursor_to_bottom_left +chafa_term_info_emit_cursor_up +chafa_term_info_emit_cursor_down +chafa_term_info_emit_cursor_left +chafa_term_info_emit_cursor_right +chafa_term_info_emit_cursor_up_1 +chafa_term_info_emit_cursor_down_1 +chafa_term_info_emit_cursor_left_1 +chafa_term_info_emit_cursor_right_1 +chafa_term_info_emit_set_scrolling_rows +chafa_term_info_emit_cursor_up_scroll +chafa_term_info_emit_cursor_down_scroll +chafa_term_info_emit_insert_cells +chafa_term_info_emit_delete_cells +chafa_term_info_emit_insert_rows +chafa_term_info_emit_delete_rows +chafa_term_info_emit_enable_cursor +chafa_term_info_emit_disable_cursor +chafa_term_info_emit_enable_echo +chafa_term_info_emit_disable_echo +chafa_term_info_emit_enable_insert +chafa_term_info_emit_disable_insert +chafa_term_info_emit_enable_wrap +chafa_term_info_emit_disable_wrap +chafa_term_info_emit_enable_bold +chafa_term_info_emit_invert_colors +chafa_term_info_emit_set_color_bg_8 +chafa_term_info_emit_set_color_fg_8 +chafa_term_info_emit_set_color_fgbg_8 +chafa_term_info_emit_set_color_fg_16 +chafa_term_info_emit_set_color_bg_16 +chafa_term_info_emit_set_color_fgbg_16 +chafa_term_info_emit_set_color_fg_256 +chafa_term_info_emit_set_color_bg_256 +chafa_term_info_emit_set_color_fgbg_256 +chafa_term_info_emit_set_color_fg_direct +chafa_term_info_emit_set_color_bg_direct +chafa_term_info_emit_set_color_fgbg_direct +chafa_term_info_emit_repeat_char +chafa_term_info_emit_begin_sixels +chafa_term_info_emit_end_sixels +chafa_term_info_emit_enable_sixel_scrolling +chafa_term_info_emit_disable_sixel_scrolling +chafa_term_info_emit_begin_kitty_immediate_image_v1 +chafa_term_info_emit_end_kitty_image +chafa_term_info_emit_begin_kitty_image_chunk +chafa_term_info_emit_end_kitty_image_chunk +chafa_term_info_emit_begin_iterm2_image +chafa_term_info_emit_end_iterm2_image +
+ +
+chafa-term-db +ChafaTermDb +chafa_term_db_new +chafa_term_db_copy +chafa_term_db_ref +chafa_term_db_unref +chafa_term_db_get_default +chafa_term_db_detect +chafa_term_db_get_fallback_info +
diff -Nru chafa-1.2.1/docs/chafa.xml chafa-1.12.4/docs/chafa.xml --- chafa-1.2.1/docs/chafa.xml 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/docs/chafa.xml 2022-11-12 01:18:35.000000000 +0000 @@ -2,7 +2,8 @@ - + + chafa @@ -34,11 +35,11 @@ Description -chafa is a utility that converts all kinds of images, -including animated GIFs, into (potentially animated) ANSI/Unicode character -output that can be displayed in a terminal. It supports alpha transparency -and multiple color modes and color spaces, and combines a range of Unicode -characters for optimal output. +chafa is a command-line utility that converts image data, +including animated GIFs, into graphics formats or ANSI/Unicode character art +suitable for display in a terminal. It has broad feature support, allowing it to +be used on devices ranging from historical teleprinters to modern terminal +emulators and everything in between. You can specify one or more input files, but the default behavior is slightly @@ -51,6 +52,14 @@ + + +Whether to allow animation [on, off]. Defaults to on. When off, will show a +still frame from each animation. + + + + Background color of display (color name or hex). Partially transparent input @@ -60,6 +69,13 @@ + + +Center images [on, off]. Defaults to off. + + + + Clear screen before processing each file. @@ -69,12 +85,33 @@ -Set output color mode; one of [none, 2, 16, 240, 256, full]. Defaults to full -(24-bit). The 240-color mode is recommended over the 256-color one, since the -lower 16 colors are unreliable and tend to differ between terminals. 16-color -mode will use aixterm extensions to produce 16 foreground and background -colors. 2-color mode will only emit the ANSI codes for reverse color and -attribute reset, while "none" will emit no ANSI color codes whatsoever. +Set output color mode; one of [none, 2, 8, 16/8 16, 240, 256, full]. The +240-color mode is recommended over the 256-color one, since the lower 16 colors +are unreliable and tend to differ between terminals. 16-color mode will use +aixterm extensions to produce 16 foreground and background colors. The 16/8 +mode allows for 8 colors plus another "bright" 8 colors in the foreground +implemented with the "bold" escape sequence. 2-color mode will only emit the +ANSI codes for reverse color and attribute reset, while "none" will emit no +escape sequences at all. + + +In sixel mode, "full" will dynamically generate a 256-color palette for each +image or animation frame. The other modes refer to built-in palettes. "none" +and "2" are interchangeable and will use the specified foreground/background +colors (see --fg and --bg). + + +If left unspecified, an optimal default will be chosen based on the current +environment. + + + + + + +Method for extracting color from an area; one of [average, median]. Median +normally produces crisper output, while average may perform better on noisy images. +Defaults to average. @@ -100,7 +137,8 @@ Dimensions of grain used when dithering. Specified as width x height, where each can be one of [1, 2, 4, 8] pixels. One character cell is by definition 8 -pixels across in both dimensions. Defaults to 4x4. +pixels across in both dimensions. Defaults to 4x4 in symbol mode and 1x1 +in sixel mode. @@ -117,9 +155,10 @@ -Time to show each file. If showing a single file, defaults to zero for a still -image and infinite for an animation. For multiple files, defaults to -3.0. Animations will always be played through at least once. +Time to show each file, in seconds. Defaults to zero for still images and for +animations when multiple files are specified. If a single animation is +specified, defaults to infinite. Animations will always be played through at +least once, even if duration is e.g. zero. @@ -134,6 +173,15 @@ + + +Leave the background color untouched. This produces character-cell output +using foreground colors only, and will avoid resetting or inverting the +colors. + + + + Specify character symbols to use for fill/gradients. Defaults to none. @@ -150,6 +198,27 @@ + + +Set output format; one of [iterm, kitty, sixels, symbols]. The default is +iterm, kitty or sixels if the connected terminal supports one of these, +falling back to symbols ("ANSI art") otherwise. + + + + + + +Load glyph information from file, which can be any font file supported by +FreeType (TTF, PCF, etc). The glyph outlines will replace any existing +outlines, including builtins. Useful in symbol mode for custom font +support or for improving quality with a specific font. Note that this only +makes sense if the output terminal is using a matching font. Can be +specified multiple times. + + + + Show a brief help text. @@ -165,6 +234,41 @@ + + +When terminal size is detected, reserve at least this many rows at the bottom +as a safety margin. Can be used to prevent images from scrolling out. +Defaults to 1. + + + + + + +When terminal size is detected, reserve at least this many columns on the +right-hand side as a safety margin. Defaults to 0. + + + + + + +Compress the output by using control sequences intelligently [0-9]. 0 +disables, 9 enables every available optimization. Defaults to 5, except +for when used with "-c none", where it defaults to 0. + + + + + + +Polite mode [on, off]. Defaults to on. Turning this off may enhance presentation +and prevent interference from other programs, but risks leaving the terminal in +an altered state (rude). + + + + Image preprocessing [on, off]. Defaults to on with 16 colors or lower, off @@ -174,6 +278,15 @@ + + +Scale image, respecting terminal's maximum dimensions. 1.0 approximates +original pixel dimensions. Specify "max" to use all available space. Defaults +to 1.0 for pixel graphics and 4.0 for symbols. + + + + Set maximum output dimensions in columns and rows. By default this will be the @@ -182,9 +295,18 @@ + + +Set the speed animations will play at. This can be either a unitless +multiplier (fractions are allowed), or a real number followed by "fps" +to apply a specific framerate. + + + + -Stretch image to fit output dimensions; ignore aspect. Implies --zoom. +Stretch image to fit output dimensions; ignore aspect. Implies --scale max. @@ -197,6 +319,14 @@ + + +Maximum number of CPU threads to use. If left unspecified or negative, +this will equal available CPU cores. + + + + @@ -229,22 +359,29 @@ - - - -Allow scaling up beyond one character per pixel. - - - +Exit Status + +chafa will return 0 on success, 1 on partial failure or +2 on complete failure (including when invoked with no arguments). + + +Status +Meaning +0Success +1Some files failed to display +2All files failed to display + + + Symbols Accepted classes for --symbols are [all, none, space, solid, stipple, block, border, diagonal, dot, quad, half, hhalf, vhalf, inverted, braille, -technical, geometric, ascii]. Some symbols belong to multiple classes, -e.g. diagonals are also borders. +technical, geometric, ascii, legacy, sextant, wedge, wide, narrow]. Some +symbols belong to multiple classes, e.g. diagonals are also borders. You can specify a list of classes separated by commas, or prefix them with + @@ -252,8 +389,8 @@ significant. -The default symbol set is all-stipple-braille-ascii+space-extra-inverted for all -modes except for "none", which uses all-stipple-braille-ascii+space-extra. +The default symbol set is block+border+space-wide-inverted for all modes +except "none", which uses block+border+space-wide (including inverse symbols). @@ -290,11 +427,16 @@ - +Further Reading + +See the Chafa homepage +for more information. + + Author - Written by Hans Petter Jansson hpj@copyleft.no. + Written by Hans Petter Jansson hpj@hpjansson.org. diff -Nru chafa-1.2.1/docs/Makefile.am chafa-1.12.4/docs/Makefile.am --- chafa-1.2.1/docs/Makefile.am 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/docs/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -19,14 +19,15 @@ SCAN_OPTIONS=--deprecated-guards="G_DISABLE_DEPRECATED" --ignore-decorators="GLIB_VAR|G_GNUC_WARN_UNUSED_RESULT" # Used for dependencies -HFILE_GLOB = $(top_srcdir)/chafa/chafa.h +HFILE_GLOB = $(top_srcdir)/chafa/chafa-*.h CFILE_GLOB = $(top_srcdir)/chafa/chafa-*.c # Ignore some private headers IGNORE_HFILES = \ chafa.h \ - named-colors.h \ - chafa-private.h + chafa-private.h \ + chafa-term-seq-doc-in.h \ + named-colors.h # Extra options to supply to gtkdoc-mkdb MKDB_OPTIONS=--output-format=xml --name-space=chafa @@ -41,34 +42,45 @@ # Other files to distribute EXTRA_DIST += \ - version.xml.in + version.xml.in \ + style.css ######################################################################## man_MANS = +manhtml = if ENABLE_MAN man_MANS += \ chafa.1 +manhtml += chafa.html + +all-local: $(manhtml) + XSLTPROC_FLAGS = \ --nonet \ --stringparam man.output.quietly 1 \ --stringparam funcsynopsis.style ansi \ --stringparam man.th.extra1.suppress 1 \ --stringparam man.authors.section.enabled 0 \ - --stringparam man.copyright.section.enabled 0 + --stringparam man.copyright.section.enabled 0 \ + --stringparam root.filename chafa \ + --stringparam html.stylesheet manpage.css .xml.1: $(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< +chafa.html: $(srcdir)/chafa.xml + $(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/html/onechunk.xsl $< + dist-local-check-mans-enabled: if grep "Man generation disabled" $(man_MANS) >/dev/null; then $(RM) $(man_MANS); fi else -$(man_MANS): +$(man_MANS) chafa.html: echo Man generation disabled. Creating dummy $@. Configure with --enable-man to enable it. echo Man generation disabled. Remove this file, configure with --enable-man, and rebuild > $@ @@ -80,9 +92,9 @@ endif CLEANFILES ?= -CLEANFILES += $(man_MANS) +CLEANFILES += $(man_MANS) $(manhtml) -EXTRA_DIST += $(man_MANS) +EXTRA_DIST += chafa.xml $(man_MANS) $(manhtml) manpage.css dist-hook-local: dist-local-check-mans-enabled all-local diff -Nru chafa-1.2.1/docs/manpage.css chafa-1.12.4/docs/manpage.css --- chafa-1.2.1/docs/manpage.css 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/docs/manpage.css 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,357 @@ +/* ===== * + * Reset * + * ===== */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +ol, ul { + list-style: none; +} + +blockquote, q { +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +a:focus { + outline: none; +} + +/* ============ * + * Theme Styles * + * ============ */ + +body { + box-sizing: border-box; + color:#000; + font-size: 16px; + font-family: 'yorktenslabnormregular', serif; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + + background: #f2f2f2; + border-top: 0px solid #111; + border-bottom: 1px solid #111; + + position: relative; + max-width: 640px; + padding: 10px 10px; + margin: 0px auto; +} + +h1, h2, h3, h4, h5, h6 { + margin: 10px 0; + font-weight: 600; + color:#000; + letter-spacing: -0.5px; +} + +h1 { + font-size: 36px; + font-weight: 600; +} + +h2 { + padding-bottom: 5px; + font-size: 32px; + background: url('../img/bg-hr.png') repeat-x bottom; +} + +h2 code { + font-size: 24px; +} + +h3 { + font-size: 24px; +} + +h4 { + font-size: 21px; +} + +h5 { + font-size: 18px; +} + +h6 { + font-size: 16px; +} + +p { + margin: 10px 0 15px 0; +} + +footer p { + color: #f2f2f2; +} + +a { + text-decoration: none; + color: #007ee0; + text-shadow: none; +} + +a:visited { + color: #802acb; +} + +footer a:hover { + color: #f2f2f2; + background-color: #007ee0; +} + +em { + font-style: italic; +} + +strong { + font-weight: bold; +} + +img { + position: relative; + margin: 0 auto; + max-width: 739px; + padding: 5px; + margin: 10px 0 10px 0; +} + +pre, code { + width: 100%; + color: #111; + background-color: #fff; + + font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; + font-size: 14px; + + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +pre { + width: 100%; + padding: 4px; + box-shadow: 0 0 4px rgba(0,0,0,.1); + overflow: auto; +} + +code { + padding: 3px; + margin: 0 3px; + white-space: nowrap; + box-shadow: 0 0 10px rgba(0,0,0,.1); +} + +pre code { + display: block; + box-shadow: none; +} + +blockquote { + color: #666; + margin-bottom: 20px; + padding: 0 0 0 20px; + border-left: 3px solid #bbb; +} + +ul, ol, dl { + margin-bottom: 15px +} + +ul li { + list-style: outside; + padding-left: 0em; + margin-left: 1em; +} + +ol li { + list-style: decimal inside; + padding-left: 0em; + margin-left: 1em; +} + +dl dt { + font-weight: bold; +} + +dl dd { + padding-left: 20px; +} + +dl p { + padding-left: 20px; +} + +hr { + height: 1px; + margin-bottom: 5px; + border: none; + background: url('../images/bg-hr.png') repeat-x center; +} + +table { + border: 1px solid #383838; + margin-bottom: 20px; + text-align: left; +} + +th { + font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; + padding: 4px; + background: #383838; + color: #fff; +} + +td { + padding: 4px; + border: 1px solid #383838; +} + +form { + background: #f2f2f2; +} + +img { + /* width: 100%; */ + max-width: 100%; +} + +figure { + width: 32%; + display: inline-block; + text-align: center; + margin: 10px 0px 0px 0px; + clear: text; + padding: 0; + vertical-align: top; +} + +figure img { + margin: 0; + padding: 0; +} + +figcaption { + font-style: italic; + margin: 0; + padding: 0; +} + +/* ===== * + * Fonts * + * ===== */ + +/*! + * Web Fonts from Fontspring.com + * + * All OpenType features and all extended glyphs have been removed. + * Fully installable fonts can be purchased at http://www.fontspring.com + * + * The fonts included in this stylesheet are subject to the End User License you purchased + * from Fontspring. The fonts are protected under domestic and international trademark and + * copyright law. You are prohibited from modifying, reverse engineering, duplicating, or + * distributing this font software. + * + * (c) 2010-2019 Fontspring + * + * The fonts included are copyrighted by the vendor listed below. + * + * Vendor: Insigne Design + * License URL: https://www.fontspring.com/licenses/insigne/webfont + */ + +@font-face { + font-display: swap; + font-family: 'yorktenslabnormregular'; + src: url('../yorktenslabnormregular-webfont.woff2') format('woff2'), + url('../yorktenslabnormregular-webfont.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +/* =================== * + * Small Device Styles * + * =================== */ + +@media screen and (max-width: 500px) { + body { + font-size: 16px; + min-width: 320px; + max-width: 480px; + } + + h1 { + font-size: 28px; + } + + h2 { + font-size: 24px; + } + + h3 { + font-size: 21px; + } + + h4 { + font-size: 18px; + } + + h5 { + font-size: 14px; + } + + h6 { + font-size: 12px; + } + + code, pre { + min-width: 320px; + max-width: 480px; + font-size: 11px; + } + + img { + width: 100%; + max-width: 100%; + } + + figure { + width: 100%; + max-width: 100%; + padding: 5px; + } + + figure.two { + width: 100%; + max-width: 100%; + padding: 5px; + } +} diff -Nru chafa-1.2.1/docs/style.css chafa-1.12.4/docs/style.css --- chafa-1.2.1/docs/style.css 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/docs/style.css 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,886 @@ +body +{ + font-family: cantarell, sans-serif; + font-size: 16px; + line-height: 1.5em; +} +.synopsis, .classsynopsis +{ + /* tango:aluminium 1/2 */ + background: #eeeeec; + background: rgba(238, 238, 236, 0.5); + border: solid 1px rgb(238, 238, 236); + padding: 0.5em; +} +.programlisting +{ + /* tango:sky blue 0/1 */ + /* fallback for no rgba support */ + background: #e6f3ff; + border: solid 1px #729fcf; + background: rgba(114, 159, 207, 0.1); + border: solid 1px rgba(114, 159, 207, 0.2); + padding: 0.5em; +} +.variablelist +{ + padding: 4px; + margin-left: 3em; +} +.variablelist td:first-child +{ + vertical-align: top; +} + +span.nowrap { + white-space: nowrap; +} + +div.gallery-float +{ + float: left; + padding: 10px; +} +div.gallery-float img +{ + border-style: none; +} +div.gallery-spacer +{ + clear: both; +} + +a, a:visited +{ + text-decoration: none; + /* tango:sky blue 2 */ + color: #3465a4; +} +a:hover +{ + text-decoration: underline; + /* tango:sky blue 1 */ + color: #729fcf; +} + +div.informaltable table +{ + border-collapse: separate; + border-spacing: 1em 0.3em; + border: none; +} + +div.informaltable table td, div.informaltable table th +{ + vertical-align: top; +} + +.function_type, +.variable_type, +.property_type, +.signal_type, +.parameter_name, +.struct_member_name, +.union_member_name, +.define_keyword, +.datatype_keyword, +.typedef_keyword +{ + text-align: right; +} + +/* dim non-primary columns */ +.c_punctuation, +.function_type, +.variable_type, +.property_type, +.signal_type, +.define_keyword, +.datatype_keyword, +.typedef_keyword, +.property_flags, +.signal_flags, +.parameter_annotations, +.enum_member_annotations, +.struct_member_annotations, +.union_member_annotations +{ + color: #888a85; +} + +.function_type a, +.function_type a:visited, +.function_type a:hover, +.property_type a, +.property_type a:visited, +.property_type a:hover, +.signal_type a, +.signal_type a:visited, +.signal_type a:hover, +.signal_flags a, +.signal_flags a:visited, +.signal_flags a:hover +{ + color: #729fcf; +} + +td p +{ + margin: 0.25em; +} + +div.table table +{ + border-collapse: collapse; + border-spacing: 0px; + /* tango:aluminium 3 */ + border: solid 1px #babdb6; +} + +div.table table td, div.table table th +{ + /* tango:aluminium 3 */ + border: solid 1px #babdb6; + padding: 3px; + vertical-align: top; +} + +div.table table th +{ + /* tango:aluminium 2 */ + background-color: #d3d7cf; +} + +h4 +{ + color: #555753; + margin-top: 1em; + margin-bottom: 1em; +} + +hr +{ + /* tango:aluminium 1 */ + color: #d3d7cf; + background: #d3d7cf; + border: none 0px; + height: 1px; + clear: both; + margin: 2.0em 0em 2.0em 0em; +} + +dl.toc dt +{ + padding-bottom: 0.25em; +} + +dl.toc > dt +{ + padding-top: 0.25em; + padding-bottom: 0.25em; + font-weight: bold; +} + +dl.toc > dl +{ + padding-bottom: 0.5em; +} + +.parameter +{ + font-style: normal; +} + +.footer +{ + padding-top: 3.5em; + /* tango:aluminium 3 */ + color: #babdb6; + text-align: center; + font-size: 80%; +} + +.informalfigure, +.figure +{ + margin: 1em; +} + +.informalexample, +.example +{ + margin-top: 1em; + margin-bottom: 1em; +} + +.warning +{ + /* tango:orange 0/1 */ + background: #ffeed9; + background: rgba(252, 175, 62, 0.1); + border-color: #ffb04f; + border-color: rgba(252, 175, 62, 0.2); +} +.note +{ + /* tango:chameleon 0/0.5 */ + background: #d8ffb2; + background: rgba(138, 226, 52, 0.1); + border-color: #abf562; + border-color: rgba(138, 226, 52, 0.2); +} +div.blockquote +{ + border-color: #eeeeec; +} +.note, .warning, div.blockquote +{ + padding: 0.5em; + border-width: 1px; + border-style: solid; + margin: 2em; +} +.note p, .warning p +{ + margin: 0; +} + +div.warning h3.title, +div.note h3.title +{ + display: none; +} + +p + div.section +{ + margin-top: 1em; +} + +div.refnamediv, +div.refsynopsisdiv, +div.refsect1, +div.refsect2, +div.toc, +div.section +{ + margin-bottom: 1em; +} + +/* blob links */ +h2 .extralinks, h3 .extralinks +{ + float: right; + /* tango:aluminium 3 */ + color: #babdb6; + font-size: 80%; + font-weight: normal; +} + +.lineart +{ + color: #d3d7cf; + font-weight: normal; +} + +.annotation +{ + /* tango:aluminium 5 */ + color: #555753; + font-weight: normal; +} + +.structfield +{ + font-style: normal; + font-weight: normal; +} + +acronym,abbr +{ + border-bottom: 1px dotted gray; +} + +/* code listings */ + +.listing_code .programlisting .normal, +.listing_code .programlisting .normal a, +.listing_code .programlisting .number, +.listing_code .programlisting .cbracket, +.listing_code .programlisting .symbol { color: #555753; } +.listing_code .programlisting .comment, +.listing_code .programlisting .linenum { color: #babdb6; } /* tango: aluminium 3 */ +.listing_code .programlisting .function, +.listing_code .programlisting .function a, +.listing_code .programlisting .preproc { color: #204a87; } /* tango: sky blue 3 */ +.listing_code .programlisting .string { color: #ad7fa8; } /* tango: plum */ +.listing_code .programlisting .keyword, +.listing_code .programlisting .usertype, +.listing_code .programlisting .type, +.listing_code .programlisting .type a { color: #4e9a06; } /* tango: chameleon 3 */ + +.listing_frame { + /* tango:sky blue 1 */ + border: solid 1px #729fcf; + border: solid 1px rgba(114, 159, 207, 0.2); + padding: 0px; +} + +.listing_lines, .listing_code { + margin-top: 0px; + margin-bottom: 0px; + padding: 0.5em; +} +.listing_lines { + /* tango:sky blue 0.5 */ + background: #a6c5e3; + background: rgba(114, 159, 207, 0.2); + /* tango:aluminium 6 */ + color: #2e3436; +} +.listing_code { + /* tango:sky blue 0 */ + background: #e6f3ff; + background: rgba(114, 159, 207, 0.1); +} +.listing_code .programlisting { + /* override from previous */ + border: none 0px; + padding: 0px; + background: none; +} +.listing_lines pre, .listing_code pre { + margin: 0px; +} + +@media screen { + /* these have a as a first child, but since there are no parent selectors + * we can't use that. */ + a.footnote + { + position: relative; + top: 0em ! important; + } + /* this is needed so that the local anchors are displayed below the naviagtion */ + div.footnote a[name], div.refnamediv a[name], div.refsect1 a[name], div.refsect2 a[name], div.index a[name], div.glossary a[name], div.sect1 a[name] + { + display: inline-block; + position: relative; + top:-5em; + } + /* this seems to be a bug in the xsl style sheets when generating indexes */ + div.index div.index + { + top: 0em; + } + body + { + padding-top: 2.5em; + max-width: 60em; + } + p + { + max-width: 60em; + } + /* style and size the navigation bar */ + table.navigation#top + { + position: fixed; + background: #e2e2e2; + border-bottom: solid 1px #babdb6; + border-spacing: 5px; + margin-top: 0; + margin-bottom: 0; + top: 0; + left: 0; + z-index: 10; + } + table.navigation#top td + { + padding-left: 6px; + padding-right: 6px; + } + .navigation a, .navigation a:visited + { + /* tango:sky blue 3 */ + color: #204a87; + } + .navigation a:hover + { + /* tango:sky blue 2 */ + color: #3465a4; + } + td.shortcuts + { + /* tango:sky blue 2 */ + color: #3465a4; + font-size: 80%; + white-space: nowrap; + } + td.shortcuts .dim + { + color: #babdb6; + } + .navigation .title + { + max-width: none; + margin: 0px; + font-weight: normal; + } +} +@media screen and (min-width: 60em) { + /* screen larger than 60em */ + body { margin: auto; } +} +@media screen and (max-width: 60em) { + /* screen less than 60em */ + #nav_hierarchy { display: none; } + #nav_interfaces { display: none; } + #nav_prerequisites { display: none; } + #nav_derived_interfaces { display: none; } + #nav_implementations { display: none; } + #nav_child_properties { display: none; } + #nav_style_properties { display: none; } + #nav_index { display: none; } + #nav_glossary { display: none; } + .gallery_image { display: none; } + .property_flags { display: none; } + .signal_flags { display: none; } + .parameter_annotations { display: none; } + .enum_member_annotations { display: none; } + .struct_member_annotations { display: none; } + .union_member_annotations { display: none; } + /* now that a column is hidden, optimize space */ + col.parameters_name { width: auto; } + col.parameters_description { width: auto; } + col.struct_members_name { width: auto; } + col.struct_members_description { width: auto; } + col.enum_members_name { width: auto; } + col.enum_members_description { width: auto; } + col.union_members_name { width: auto; } + col.union_members_description { width: auto; } + .listing_lines { display: none; } +} +@media print { + table.navigation { + visibility: collapse; + display: none; + } + div.titlepage table.navigation { + visibility: visible; + display: table; + background: #e2e2e2; + border: solid 1px #babdb6; + margin-top: 0; + margin-bottom: 0; + top: 0; + left: 0; + height: 3em; + } +} + +div.subindex { + margin: 2em 7em 1em 0em; + padding-left: 90px; + background-position: 0 10px; + background-repeat: no-repeat; + min-height: 96px; +} + +div.subindex p { + color: #666; +} + +div.subindex h2 { + padding: 0; + margin: 0; + font-size: 230%; +} + +div.subindex h2 a { + color: #babdb6; + text-decoration: inherit; +} + +div.subindex h2 a:hover { + text-decoration: underline; +} + +div.page_title { + border: none; +} + +a.external, a.doc-link { + text-decoration: none; +} + +a.external:hover, a.doc-link:hover { + text-decoration: underline; +} + +h1 { + color: #c4a000; + text-shadow: white 0 -2px; + border-bottom: 1px solid #d3d7cf; +} + +h2, h3 { + color: #c4a000; +} + +div#subindex-references { background-image: url(api-reference.png); } +div#subindex-guides { background-image: url(guides.png); } +div#subindex-demos { background-image: url(platform-demos.png); } +div#subindex-hig { background-image: url(hig.png); } + +.refentry hr { + margin: 10px 0; +} + +div.sidebar dt, +div.toc dt { + font-size: 100%; +} + +dd dl { + margin-left: 1em; +} + +div.homeblock { + margin: 0.7em 0 1.2em 0; +} + +div.homeblock h2 { + font-size: 42px; + color: #0489B7; + margin: 0; + padding-bottom: 1ex; +} + +div.homeblock a { + color: inherit; + text-decoration: none; +} + +div.homeblock a:hover { + text-decoration: underline; +} + +div.homeblock p { + margin-top: 0; + font-size: 18px; + line-height: 130%; +} + +span.module-more { + font-size: 75%; +} + +dl.doc-index dt span.module-more a { + font-weight: normal; +} + +ul.language-list { + -webkit-column-width: 15em; + -webkit-column-gap: 2em; + -moz-column-width: 15em; + -moz-column-gap: 2em; + column-width: 15em; + column-gap: 2em; +} + +div.sidebar.notitle { + margin-top: 3em; +} + +#footer_art.default { + background-image: url(footer_art-library.png); +} + + +div.blocky1 { + width: 65%; + padding: 2em; + display: table-cell +} + +div.blocky2 { + width: 35%; + padding: 2em; + display: table-cell +} + +div.row { + width: 50%; + padding: 2em; + display: table-row +} + + +.grid_4.subtle_box { + margin-top: 1em; +} + +span.citem { + display: table-cell; + text-align: center +} + +#platform-overview a { + background: #babdb6; + display: inline-block; + padding: 5px 10px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-decoration: none; + color: #000; + margin: 5px; + width: 65px; +} + +#platform-overview a:hover { + box-shadow: 0 1px 2px #0489d7, 0 1px rgba(255, 255, 255, 0.4) inset; + color: #0489d7; +} + +#platform-overview p { + padding: 0; + margin: 0; + padding-left: 5px; + margin-bottom: 0; + padding-bottom: 0; + font-size: 10px; +} + +#api-doc-box { + float: right; + width: 205px; + margin: 0; +} + +#api-doc-box input { + width: 190px; +} + +#api-doc-box h2 { + font-size: 20px; +} + +table#platform-overview { + width: 700px; + table-layout: fixed; +} + +table#platform-overview th { + width: 14%; +} + +table#platform-overview td { + background: #888a85; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + color: #2e3436; + margin-bottom: 10px; + border: 2px solid white; +} + +.synopsis, .classsynopsis +{ + background: #eee; + border: solid 1px #aaa; + padding: 0.5em; +} +.programlisting +{ + background: #eef; + border: solid 1px #aaf; + padding: 0.5em; +} +.variablelist +{ + padding: 4px; + margin-left: 3em; +} +.variablelist td:first-child +{ + vertical-align: top; +} + +.variablelist p { + margin: 0; +} + +.listing_lines, .listing_code { + margin-top: 0px; + margin-bottom: 0px; + padding: 0.5em; +} +.listing_lines { + /* tango:sky blue 0.5 */ + background: #a6c5e3; + /* tango:aluminium 6 */ + color: #2e3436; +} +.listing_code { + /* tango:sky blue 0 */ + background: #e6f3ff; +} + +div.gtk-doc dt { + color: #2e3456; + margin: 0; +} + +div.gtk-doc div.index dt { + line-height: 1.2; + font-size: 100%; +} + +div.gtk-doc td.shortcuts { + background: white; + border: 1px solid #d3d7cf; + text-align: center; +} + +div.gtk-doc span.refpurpose { + color: #555; + font-weight: normal; +} + +#container .gsc-control-cse { + background: transparent; +} + +#container ul.i18n { + /* force left-to-right for language sidebar */ + direction: ltr; +} + +table, table tr, table td, table th, table tfoot, table thead, table tbody { + /* those properties are reset by reset.css, restore them */ + border: 1px none #2E3436; +} + +#container div.blockquote { + background-image: none; +} + +dt { + color: inherit; +} + +acronym { + border-bottom: 1px dotted; +} + +table tbody tr td p +{ + margin: 0.5ex 1em 0.5ex 0; +} + +br + br { + display: none; +} + +.docbook-legal-stuff > h3 { + text-align: center; + cursor: pointer; + font-size: 100%; +} + +.docbook-legal-stuff > div { + font-size: 80%; +} + +.docbook-legal-stuff > div dt { + font-size: 100%; +} + +img.application-icon { + padding-right: 1ex; + position: relative; + top: 0.8ex; + max-width: 48px; + max-height: 48px; +} + +h1 img.application-icon { + top: 0.4ex; +} + +div#frontpage-indexes { + clear: both; + padding-top: 2em; +} + +div#frontpage-indexes > div { + margin: 0 1em 1em 0; +} + +p.doc-feedback { + margin-top: 2em; +} + +img.attachment { + max-width: 100%; +} + +div.body.homepage { + background: url(cogs.png) 90% 150px no-repeat; +} + +div#welcome h1 { + color: #c4a000; + text-shadow: white 0 -2px; + border-bottom: 1px solid #d3d7cf; +} + +div#welcome div { + width: 96%; +} + +div#welcome p { + font-size: 150%; + width: 65%; +} + +pre.cmdsynopsis { + white-space: normal; + word-break: keep-all; +} + +h2, h3, +dl dt { + margin: 0; +} + +p { + margin: 0.4em 0; +} + +p.module-more a { + text-decoration: none; +} + +p.module-more a:hover { + text-decoration: underline; +} + +dt .module-more { + font-weight: normal; +} + +dl.doc-index dt{ + font-size: 120%; +} + +p.no-translation { + opacity: 0.6; +} + +/* =================== * + * Small Device Styles * + * =================== */ + +@media screen and (max-width: 500px) { + font-size: 18px; +} diff -Nru chafa-1.2.1/docs/using.xml chafa-1.12.4/docs/using.xml --- chafa-1.2.1/docs/using.xml 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/docs/using.xml 2022-11-12 01:18:35.000000000 +0000 @@ -108,15 +108,17 @@ canvas = chafa_canvas_new (config); /* Draw pixels and build ANSI string */ - chafa_canvas_set_contents_rgba8 (canvas, - pixels, - PIX_WIDTH, - PIX_HEIGHT, - PIX_WIDTH * N_CHANNELS); + chafa_canvas_draw_all_pixels (canvas, + CHAFA_PIXEL_RGBA8_UNASSOCIATED, + pixels, + PIX_WIDTH, + PIX_HEIGHT, + PIX_WIDTH * N_CHANNELS); gs = chafa_canvas_build_ansi (canvas); /* Print the string */ fwrite (gs->str, sizeof (char), gs->len, stdout); + fputc ('\n', stdout); /* Free resources */ g_string_free (gs, TRUE); diff -Nru chafa-1.2.1/.gitignore chafa-1.12.4/.gitignore --- chafa-1.2.1/.gitignore 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/.gitignore 2022-11-12 01:18:35.000000000 +0000 @@ -1,13 +1,19 @@ -*/*.la -*/*.lo -*/*.o -*/.deps -*/.libs +*~ +\#*\# +*.la +*.lo +*.o +.deps +.libs Makefile Makefile.in aclocal.m4 +ar-lib autom4te.cache -chafa/chafa +chafa.pc +chafa/chafa-term-seq-doc.h +chafa/chafaconfig.h +chafa/chafaconfig-stamp compile config.guess config.h @@ -17,10 +23,19 @@ config.sub configure depcomp +docs/html +docs/version.xml +docs/xml +gtk-doc.make install-sh libtool ltmain.sh m4 missing stamp-h1 +tests/term-info-test +tools/chafa/chafa + test +demo +resource diff -Nru chafa-1.2.1/HACKING chafa-1.12.4/HACKING --- chafa-1.2.1/HACKING 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/HACKING 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,89 @@ +How to hack on Chafa +==================== + +Code formatting and structure +----------------------------- + +The code is mostly C99 with limited use of extensions. It should compile with +most standards-compliant C compilers released in the last couple of years. + +GLib is our primary support library, and the code is in general very GLib-y. +We use only the base library, no GObject or GIO. + +Formatting is done with spaces (no tabs) and four-space indenting stops. + +The directory layout is as follows: + +* Top level ............ Build scripts, README, etc. + |- chafa ............. The Chafa library. All exported APIs are here. + | `- internal ....... Chafa library internals. Internal APIs. + | `- smolscale ... Private copy of a pixmap scaling library. + |- docs .............. Built documentation (API and man pages). + |- libnsgif .......... Private copy of a GIF library, used by tools. + |- lodepng ........... Private copy of a PNG library, used by tools. + |- tests ............. Tests for library and tools. + `- tools ............. Command-line tools. + |- chafa .......... The Chafa command-line graphics viewer. + `- fontgen ........ Experimental font generator. + +Making source releases +---------------------- + +Releases are made as compressed, signed tar archives ("tarballs"). We use +semantic versioning. + +The following can be done multiple times and at any time during development, +always on the master branch: + +1) Write/edit NEWS section with a (TBA) placeholder for release date. + +Then right before the release, still on the master branch: + +2) Update the soversion in chafa/Makefile.am (-version-info c:r:a): + - If the library source code has changed at all since the last update, + then increment revision (‘c:r:a’ becomes ‘c:r+1:a’). + - If any interfaces have been added, removed, or changed since the last + update, increment current, and set revision to 0. + - If any interfaces have been added since the last public release, then + increment age. + - If any interfaces have been removed or changed since the last public + release, then set age to 0. + +3) If this is a minor (x.y.0) release, bump package to the next even version + in configure.ac. + +4) Make sure 'make distcheck' passes. Correct any issues. + +5) Commit and push above changes. Wait for green CI and correct any issues. + +6) Edit NEWS and replace (TBA) with today's date. + +7) If this is a minor (x.y.0) release, edit README.md and update the CI links. + They should reference master and the latest stable branch. + +8) If this is a micro (x.y.z) release, switch to that release's maintenance + branch (x.y) and cherry-pick all changes from the previous steps into it, + then increment the micro version in configure.ac. + +9) Commit above changes. + +10) Tag and sign the release: 'git tag -s x.y.z'. Annotate with the appropriate + NEWS item, without the --- underline for the heading. + +11) If this is a minor (x.y.0) release, make a maintenance branch for it, + rooted at the tag: 'git branch x.y'. But keep working on master. + +12) Build tarball: 'make distcheck'. + +13) Sign tarball: 'gpg --sign --detach --armor chafa-x.y.z.tar.xz'. + +14) If this was a minor (x.y.0) release, bump package to the next odd version. + +15) Commit the post-release version bump. + +16) Push changes. Make sure to push tags and branches too. + +17) Upload the tarball and signature to GitHub, and copy the NEWS item there. + Add markdown formatting. + +That should do it. diff -Nru chafa-1.2.1/libnsgif/libnsgif.c chafa-1.12.4/libnsgif/libnsgif.c --- chafa-1.2.1/libnsgif/libnsgif.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/libnsgif/libnsgif.c 2022-11-12 01:18:35.000000000 +0000 @@ -43,7 +43,7 @@ #define GIF_TRANSPARENT_COLOUR 0x00 /* GIF Flags */ -#define GIF_FRAME_COMBINE 1 +/* #define GIF_FRAME_COMBINE 1 */ /* Unused macro */ #define GIF_FRAME_CLEAR 2 #define GIF_FRAME_RESTORE 3 #define GIF_FRAME_QUIRKS_RESTORE 4 @@ -57,7 +57,7 @@ #define GIF_DISPOSAL_MASK 0x1c #define GIF_TRANSPARENCY_MASK 0x01 #define GIF_EXTENSION_COMMENT 0xfe -#define GIF_EXTENSION_PLAIN_TEXT 0x01 +/* #define GIF_EXTENSION_PLAIN_TEXT 0x01 */ /* Unused macro */ #define GIF_EXTENSION_APPLICATION 0xff #define GIF_BLOCK_TERMINATOR 0x00 #define GIF_TRAILER 0x3b @@ -123,8 +123,8 @@ gif_initialise_frame_extensions(gif_animation *gif, const int frame) { const unsigned char *gif_data, *gif_end; - int gif_bytes; - unsigned int block_size; + ssize_t gif_bytes; + ssize_t block_size; /* Get our buffer position etc. */ gif_data = (const unsigned char *)(gif->gif_data + gif->buffer_position); @@ -255,10 +255,10 @@ gif_frame *temp_buf; const unsigned char *gif_data, *gif_end; - int gif_bytes; + ssize_t gif_bytes; unsigned int flags = 0; unsigned int width, height, offset_x, offset_y; - unsigned int block_size, colour_table_size; + ssize_t block_size, colour_table_size; bool first_image = true; gif_result return_value; bool premature_eof = false; @@ -287,9 +287,8 @@ /* We could theoretically get some junk data that gives us millions of * frames, so we ensure that we don't have a silly number */ - if (frame > 4096) { + if (frame > 262143) return GIF_FRAME_DATA_ERROR; - } /* Get some memory to store our pointers in etc. */ if ((int)gif->frame_holders <= frame) { @@ -300,6 +299,9 @@ } gif->frames = temp_buf; gif->frame_holders = frame + 1; + + /* Clear all frame fields */ + memset(&gif->frames[frame], 0, sizeof(gif_frame)); } /* Store our frame pointer. We would do it when allocating except we @@ -435,7 +437,7 @@ if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA; block_size = gif_data[0] + 1; /* Check if the frame data runs off the end of the file */ - if ((int)(gif_bytes - block_size) < 0) { + if ((ssize_t)(gif_bytes - block_size) < 0) { /* Try to recover by signaling the end of the gif. * Once we get garbage data, there is no logical way to * determine where the next frame is. It's probably @@ -484,8 +486,8 @@ static gif_result gif_skip_frame_extensions(gif_animation *gif) { const unsigned char *gif_data, *gif_end; - int gif_bytes; - unsigned int block_size; + ssize_t gif_bytes; + ssize_t block_size; /* Get our buffer position etc. */ gif_data = (const unsigned char *)(gif->gif_data + gif->buffer_position); @@ -585,17 +587,23 @@ { unsigned int index = 0; const unsigned char *gif_data, *gif_end; - int gif_bytes; + ssize_t gif_bytes; unsigned int width, height, offset_x, offset_y; unsigned int flags, colour_table_size, interlace; unsigned int *colour_table; unsigned int *frame_data = 0; // Set to 0 for no warnings unsigned int *frame_scanline; - unsigned int save_buffer_position; + ssize_t save_buffer_position; unsigned int return_value = 0; unsigned int x, y, decode_y, burst_bytes; register unsigned char colour; + /* If the GIF has no frame data, frame holders will not be allocated in + * gif_initialise() */ + if (gif->frames == NULL) { + return GIF_INSUFFICIENT_DATA; + } + /* Ensure this frame is supposed to be decoded */ if (gif->frames[frame].display == false) { return GIF_OK; @@ -1063,6 +1071,9 @@ } gif->frame_holders = 1; + /* Clear all frame fields */ + memset(gif->frames, 0, sizeof(gif_frame)); + /* Initialise the bitmap header */ assert(gif->bitmap_callbacks.bitmap_create); gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height); diff -Nru chafa-1.2.1/libnsgif/libnsgif.h chafa-1.12.4/libnsgif/libnsgif.h --- chafa-1.2.1/libnsgif/libnsgif.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/libnsgif/libnsgif.h 2022-11-12 01:18:35.000000000 +0000 @@ -119,9 +119,9 @@ /* Internal members are listed below */ /** current index into GIF data */ - unsigned int buffer_position; + ssize_t buffer_position; /** total number of bytes of GIF data available */ - unsigned int buffer_size; + ssize_t buffer_size; /** current number of frame holders */ unsigned int frame_holders; /** index in the colour table for the background colour */ diff -Nru chafa-1.2.1/libnsgif/lzw.c chafa-1.12.4/libnsgif/lzw.c --- chafa-1.2.1/libnsgif/lzw.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/libnsgif/lzw.c 2022-11-12 01:18:35.000000000 +0000 @@ -34,12 +34,12 @@ */ struct lzw_read_ctx { const uint8_t *data; /**< Pointer to start of input data */ - uint32_t data_len; /**< Input data length */ - uint32_t data_sb_next; /**< Offset to sub-block size */ + uint64_t data_len; /**< Input data length */ + uint64_t data_sb_next; /**< Offset to sub-block size */ const uint8_t *sb_data; /**< Pointer to current sub-block in data */ - uint32_t sb_bit; /**< Current bit offset in sub-block */ - uint32_t sb_bit_count; /**< Bit count in sub-block */ + uint64_t sb_bit; /**< Current bit offset in sub-block */ + uint64_t sb_bit_count; /**< Bit count in sub-block */ }; /** @@ -113,8 +113,8 @@ */ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) { - uint32_t block_size; - uint32_t next_block_pos = ctx->data_sb_next; + uint64_t block_size; + uint64_t next_block_pos = ctx->data_sb_next; const uint8_t *data_next = ctx->data + next_block_pos; if (next_block_pos >= ctx->data_len) { @@ -264,8 +264,8 @@ lzw_result lzw_decode_init( struct lzw_ctx *ctx, const uint8_t *compressed_data, - uint32_t compressed_data_len, - uint32_t compressed_data_pos, + uint64_t compressed_data_len, + uint64_t compressed_data_pos, uint8_t code_size, const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out) @@ -329,6 +329,10 @@ /* Code is invalid */ return LZW_BAD_CODE; + } else if (code_new >= 1 << LZW_CODE_MAX) { + /* Don't access out of bound */ + return LZW_BAD_CODE; + } else if (code_new < current_entry) { /* Code is in table */ code_out = code_new; diff -Nru chafa-1.2.1/libnsgif/lzw.h chafa-1.12.4/libnsgif/lzw.h --- chafa-1.2.1/libnsgif/lzw.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/libnsgif/lzw.h 2022-11-12 01:18:35.000000000 +0000 @@ -75,8 +75,8 @@ lzw_result lzw_decode_init( struct lzw_ctx *ctx, const uint8_t *compressed_data, - uint32_t compressed_data_len, - uint32_t compressed_data_pos, + uint64_t compressed_data_len, + uint64_t compressed_data_pos, uint8_t code_size, const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out); diff -Nru chafa-1.2.1/libnsgif/Makefile.am chafa-1.12.4/libnsgif/Makefile.am --- chafa-1.2.1/libnsgif/Makefile.am 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/libnsgif/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -1,4 +1,7 @@ noinst_LTLIBRARIES = libnsgif.la libnsgif_la_SOURCES = libnsgif.c libnsgif.h lzw.c lzw.h log.h -AM_CPPFLAGS = -I${top_srcdir} +libnsgif_la_CFLAGS = ${LIBNSGIF_CFLAGS} -I${top_srcdir} +libnsgif_la_LDFLAGS = ${LIBNSGIF_LDFLAGS} + +EXTRA_DIST = COPYING README README-chafa diff -Nru chafa-1.2.1/lodepng/LICENSE chafa-1.12.4/lodepng/LICENSE --- chafa-1.2.1/lodepng/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/lodepng/LICENSE 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,21 @@ +Copyright (c) 2005-2018 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + diff -Nru chafa-1.2.1/lodepng/lodepng.c chafa-1.12.4/lodepng/lodepng.c --- chafa-1.2.1/lodepng/lodepng.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/lodepng/lodepng.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,6524 @@ +/* +LodePNG version 20220109 + +Copyright (c) 2005-2022 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#ifdef LODEPNG_COMPILE_DISK +#include /* LONG_MAX */ +#include /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20220109"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#ifdef LODEPNG_COMPILE_ENCODER +# define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; /*success*/ +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + p->size = size; + return ucvector_reserve(p, size); +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +/*also appends null termination character*/ +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +The nbits parameter is unused but is given for documentation purposes, error +checking for amount of bits must be done beforehand. +*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer = reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if(l == 0) continue; + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + advanceBits(reader, FIRSTBITS); + value += peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[value] - FIRSTBITS); + return codetree->table_value[value]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/ + ensureBits17(reader, 14); + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype, size_t max_output_size) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */ + int done = 0; + + if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + + while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This + appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/ + ensureBits32(reader, 30); + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) { + /*slightly faster code path if multiple literals in a row*/ + out->data[out->size++] = (unsigned char)code_ll; + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + } + if(code_ll <= 255) /*literal symbol*/ { + out->data[out->size++] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + ensureBits25(reader, 5); + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + out->size += length; + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + done = 1; /*end code, finish the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + if(out->allocsize - out->size < reserved_size) { + if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + if(max_output_size && out->size > max_output_size) { + ERROR_BREAK(109); /*error, larger than max size*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + /*out->data can be NULL (when LEN is zero), and arithmetics on NULL ptr is undefined. so we check*/ + if (out->data) { + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + } + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/ + ensureBits9(&reader, 3); + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ + if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; + if(error) break; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + if(error) { + /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && out->size > settings->max_output_size) error = 109; + } + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); + /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + unsigned error; + if(settings->custom_zlib) { + error = settings->custom_zlib(out, outsize, in, insize, settings); + if(error) { + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; + } + } else { + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + } + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->max_output_size = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +#ifndef LODEPNG_NO_COMPILE_CRC +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else + return 0; +} +#endif + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); +#ifndef LODEPNG_NO_COMPILE_CRC + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); +#else + unsigned CRC = 0; +#endif + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = NULL; + dest->text_strings = NULL; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = NULL; + dest->itext_langtags = NULL; + dest->itext_transkeys = NULL; + dest->itext_strings = NULL; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + +#ifndef LODEPNG_NO_COMPILE_CRC + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } +#endif + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; + break; + } + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds + too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + recon[i + 3] = s3 + ((r3 + p3) >> 1u); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + } + } + for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); + } + break; + case 4: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version is also possible but that + adds too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i, ++j) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[j])); + } + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i != length; ++i, ++j) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[j]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_icc_size; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, &zlibsettings); + /*error: ICC profile larger than decoder->max_icc_size*/ + if(error && size > zlibsettings.max_output_size) error = 113; + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(data + chunkLength + 4 > in + insize) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { +#ifndef LODEPNG_NO_COMPILE_CRC + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ +#endif + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { +#ifndef LODEPNG_NO_COMPILE_CRC + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ +#endif + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + if(expected_size > LODEPNG_IMAGE_DATA_SIZE_MAX) { + state->error = 114; + } + } + + if (!state->error) { + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + if (outsize > LODEPNG_IMAGE_DATA_SIZE_MAX) { + state->error = 114; + } + } + + if(!state->error) { + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*disable reading things that this function doesn't output*/ + state.decoder.read_text_chunks = 0; + state.decoder.remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; + settings->max_text_size = 16777216; + settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + + lodepng_info_init(&info); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&info.color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + /*this limit can be configured in LodePNGDecompressSettings*/ + case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; + case 110: return "custom zlib or inflate decompression failed"; + case 111: return "custom zlib or deflate compression failed"; + /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large text sizes.*/ + case 112: return "compressed text unreasonably large"; + /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large ICC profile*/ + case 113: return "ICC profile unreasonably large"; + /*max size of an in-memory image buffer*/ + case 114: return "image data unreasonably large"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff -Nru chafa-1.2.1/lodepng/lodepng.h chafa-1.12.4/lodepng/lodepng.h --- chafa-1.2.1/lodepng/lodepng.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/lodepng/lodepng.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,2030 @@ +/* +LodePNG version 20220109 + +Copyright (c) 2005-2022 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/*Hard upper limit on size of an uncompressed in-memory image buffer. The +total memory consumption may be higher, e.g. during postProcessScanlines().*/ +#define LODEPNG_IMAGE_DATA_SIZE_MAX 0xffffffffU + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, + return an error, output a data size > max_output_size and all the data up to that point. This is + not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is + ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. + Set to 0 to impose no limit (the default).*/ + size_t max_output_size; + + /*use custom zlib decoder instead of built in one (default: null). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. + + So when decoding, you may get these in a different color mode than the one you requested + for the raw pixels. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + Non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including strings, keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long (plus the + additional null terminator). It's discouraged to use a single line length + longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + + Standard text chunk keywords and strings are encoded using Latin-1. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + International text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys", and the following text encodings are used: + keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. + keys must be 1-79 characters (plus the additional null terminator), the other + strings are any length. + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +unsigned lodepng_convert_rgb(unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; + + /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, + unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. + By default it is a value that prevents unreasonably large strings from hogging memory. */ + size_t max_text_size; + + /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to + 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any + legitimate profile could be to hog memory. */ + size_t max_icc_size; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it +read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +#ifndef LODEPNG_NO_COMPILE_CRC +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); +#endif + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + +It is not recommended to use the numerical values to programmatically make +different decisions based on error types as the numbers are not guaranteed to +stay backwards compatible. They are for human consumption only. Programmatically +only 0 or non-0 matter. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +NOTE: these examples do not support wide-character filenames, you can use an +external method to handle such files and encode or decode in-memory + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 09 jan 2022: minor decoder speed improvements. +*) 27 jun 2021: added warnings that file reading/writing functions don't support + wide-character filenames (support for this is not planned, opening files is + not the core part of PNG decoding/decoding and is platform dependent). +*) 17 okt 2020: prevent decoding too large text/icc chunks by default. +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2022 Lode Vandevenne +*/ diff -Nru chafa-1.2.1/lodepng/Makefile.am chafa-1.12.4/lodepng/Makefile.am --- chafa-1.2.1/lodepng/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/lodepng/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,7 @@ +noinst_LTLIBRARIES = liblodepng.la + +liblodepng_la_SOURCES = lodepng.c lodepng.h +liblodepng_la_CFLAGS = ${LODEPNG_CFLAGS} -I${top_srcdir} +liblodepng_la_LDFLAGS = ${LODEPNG_LDFLAGS} + +EXTRA_DIST = LICENSE README.md diff -Nru chafa-1.2.1/lodepng/README.md chafa-1.12.4/lodepng/README.md --- chafa-1.2.1/lodepng/README.md 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/lodepng/README.md 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,75 @@ +LodePNG +------- + +PNG encoder and decoder in C and C++, without dependencies + +Home page: http://lodev.org/lodepng/ + +### Documentation + +Detailed documentation is included in a large comment in the second half of the +header file `lodepng.h`. + +Source code examples using LodePNG can be found in the examples directory. + +An FAQ can be found on http://lodev.org/lodepng/ + +### Building + +Only two files are needed to encode and decode PNGs: + +* `lodepng.cpp` (or renamed to `lodepng.c`) +* `lodepng.h` + +All other files are just source code examples, tests, misc utilities, etc..., +which are normally not needed in projects using this. + +You can include the files directly in your project's source tree and its +makefile, IDE project file, or other build system. No library is necessary. + +In addition to C++, LodePNG also supports ANSI C (C89), with all the same +functionality: C++ only adds extra convenience API. + +For C, rename `lodepng.cpp` to `lodepng.c`. + +Consider using git submodules to include LodePNG in your project. + +### Compiling in C++ + +If you have a hypothetical `your_program.cpp` that #includes and uses `lodepng.h`, +you can build as follows: + +`g++ your_program.cpp lodepng.cpp -Wall -Wextra -pedantic -ansi -O3` + +or: + +`clang++ your_program.cpp lodepng.cpp -Wall -Wextra -pedantic -ansi -O3` + +This shows compiler flags it was designed for, but normally one would use the +compiler or build system of their project instead of those commands, and other +C++ compilers are supported. + +### Compiling in C + +Rename `lodepng.cpp` to `lodepng.c` for this. + +If you have a hypothetical your_program.c that #includes and uses lodepng.h, +you can build as follows: + +`gcc your_program.c lodepng.c -ansi -pedantic -Wall -Wextra -O3` + +or + +`clang your_program.c lodepng.c -ansi -pedantic -Wall -Wextra -O3` + +This shows compiler flags it was designed for, but normally one would use the +compiler or build system of their project instead of those commands, and other +C compilers are supported. + +### Makefile + +There is a Makefile, but this is not intended for using LodePNG itself since the +way to use that one is to include its source files in your program. The Makefile +only builds development and testing utilities. It can be used as follows: + +`make -j` diff -Nru chafa-1.2.1/Makefile.am chafa-1.12.4/Makefile.am --- chafa-1.2.1/Makefile.am 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -1,7 +1,9 @@ -SUBDIRS = chafa docs libnsgif tools +SUBDIRS = chafa docs libnsgif lodepng tools tests EXTRA_DIST = \ + HACKING \ README.md \ + SECURITY.md \ autogen.sh \ chafa.pc.in @@ -14,4 +16,6 @@ @echo --- Success! You can now run tools/chafa/chafa, or install everything @echo --- using "make install" or "sudo make install". @echo --- + @echo -e '--- \e[0;91mNOTE:\e[0m You may have to run \e[0;93msudo ldconfig\e[0m after installing.' + @echo --- @echo diff -Nru chafa-1.2.1/NEWS chafa-1.12.4/NEWS --- chafa-1.2.1/NEWS 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/NEWS 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,424 @@ Chafa releases ============== +1.12.4 (2022-11-12) +------------------- + +This release improves support for Microsoft Windows and fixes several bugs. + +* MS Windows: Added support for Unicode command-line arguments. + +* MS Windows: Enabled support for building a DLL. + +* Improved quality of accelerated symbol picking (the default with -w 6 and + lower), especially along sharp edges. + +* The XWD loader now supports unaligned image data, which can occur when the + header is of an uneven length. Such images will no longer be rejected. + +* Bug fixes: + github#100 Reading image data from stdin fails on Windows (reported by + @TransparentLC). + github#104 Artifacts with transparent animations on Kitty (reported by Akash + Patel). + github#112 Bad documentation for -c default (reported by Jakub Wilk). + github#113 Autogen/build fails on CentOS 7 (partial solution) (reported by + Ivan Shatsky). + huntr.dev Uncontrolled memory allocation in lodepng (reported by + @JieyongMa). + [unfiled] -lm should not be in pkg-config Libs: list (Tomasz Kłoczko). + [unfiled] The --watch switch was broken with the introduction of --animate. + +1.12.3 (2022-07-01) +------------------- + +A bug crept into the previous release that prevented successful package builds +in some environments. This has been fixed. + +* Increased GLib minimum version to 2.26. + +* Enabled a few compiler warnings not included in '-Wall -Wextra' in order to + catch more potential issues early. Also started using '-Werror' selectively. + +* Silenced deprecation warnings for older GLib APIs that we would like to + keep using a bit longer. + +* Bug fixes: + github#96 Regression: Fails to build on Linux/Debian (reported by Mo Zhou). + +1.12.2 (2022-06-28) +------------------- + +This release adds basic support for Microsoft Windows. + +* Made everything compile for the x86_64-w64-mingw32 target using gcc. + +* Added support for the Windows 10 Command Prompt. + +* Improved error reporting. + +1.12.1 (2022-06-20) +------------------- + +This release fixes one important input validation bug and several instances of +undefined behavior revealed by fuzzing. + +* Increased GLib minimum version to 2.20. + +* Added 12 new test inputs, including bad inputs to handle gracefully. + +* Added a few symbols to API documentation that were accidentally left out. + +* Bug fixes: + huntr.dev CVE-2022-2061: Out-of-bounds read in libnsgif's lzw_decode() + (Sudhakar Verma of CrowdStrike). + [unfiled] Undefined behavior in libnsgif due to uninitialized frame fields. + [unfiled] Signed integer overflow in chafa_pack_color(). + [unfiled] Integer overflow in normalization pass on some images. + [unfiled] Potential unaligned access with corrupt XWD images. + [unfiled] Integer overflow in quantization on some images. + [unfiled] Calculating offset from NULL pointer in LodePNG. + +1.12.0 (2022-06-06) +------------------- + +This is a feature release with new image loaders aimed at phasing out +ImageMagick. It also has new convenience functions, character art improvements, +tests and bug fixes. + +Special thanks go to the very fine security researchers at huntr.dev for their +help in hardening Chafa over the last few releases. + +* The ImageMagick dependency is now optional and deprecated. Packagers are + encouraged to drop the ImageMagick dependency (--without-imagemagick) and + add direct dependencies on relevant image codecs. ImageMagick support will be + removed in a future release. + +* Added image loaders for the following formats: JPEG, SVG, TIFF, WebP. Like + the existing GIF, PNG and XWD loaders, these are much faster and generally + safer than their ImageMagick counterparts. If ImageMagick is enabled, it will + be used as a fallback. Supported image loaders will be listed in the + --version output. + +* Implemented a 16/8 mode producing 8 colors and an additional 8 bright + foreground-only colors for a total of 16 foreground and 8 background colors. + When using ANSI escape sequences in symbol mode, the sequence for bold text + is used to enable bright colors. This scheme corresponds to that of the IBM + PC's VGA hardware text modes and was popular with terminal emulators in the + late 1980s to early 2000s. + + With a few tweaks, output from this mode can be turned into ANSI art scene + .ANS files compatible with utilities like the wonderful Ansilove, e.g: + + $ chafa -f symbols -c 16/8 -s 80 --symbols space+solid+half --fill stipple \ + in.jpg | tr -d '\n' | iconv -c -f utf8 -t cp437 > out.ans + $ ansilove out.ans -o out.png + +* New builtin Latin symbols (available with --symbols latin). This class + comprises most of the symbols from the Latin-1 Supplement, Latin Extended-A + and -B, IPA Extensions and Spacing Modifier Letters plus a few Latin-like + symbols from other ranges, using Terminus as the reference font. The ASCII + class is also a subset of this class. + +* Reworked ASCII symbols to be more representative of modern terminal fonts. As + with the other Latin ranges, the reference font is now Terminus. + +* New option: --scale . This takes a real number specifying the on-screen + scaling factor relative to the input's pixel size, respecting the terminal + size. The special argument 'max' will fit the output to the terminal. The + defaults are 1.0 for iTerm, Kitty and sixels, and 4.0 for symbols. Suggested + by Lionel Dricot in github#84. + +* Deprecated option: --zoom. Use '--scale max' instead. + +* Added a battery of simple tests that can be run with 'make check'. + +* Made 'configure' friendlier. It's now more lenient with dependencies, and + the summary is more detailed and colorized if possible. + +* Bug fixes: + github#62 Too big alloc on bogus terminal dimensions (reported by + Sotiris Papatheodorou and Mo Zhou). + huntr.dev Null pointer dereference caused by calling post_func on unused + batch entries (reported by @han0nly). + [unfiled] Small memory leaks when using iTerm and Kitty formats. + [unfiled] Wide symbol coverages leaked in symbol map destructor. + [unfiled] No error code if files failed to load. + +1.10.3 (2022-05-04) +------------------- + +This release fixes multiple input validation issues. These were found in the +'chafa' command-line tool and do not affect the library backend. + +* Improved input validation in the XWD loader. + +* Bug fixes: + huntr.dev Buffer over-read when compiled with -O0 or non-x86 target + (reported by @JieyongMa). + +1.10.2 (2022-04-25) +------------------- + +This release adds security/responsible disclosure guidelines and fixes a few +issues with input validation in the 'chafa' command-line tool. + +* Added disclosure guidelines in SECURITY.md (suggested by Jamie Slome). + +* Bug fixes: + huntr.dev Null pointer dereference in libnsgif with crafted GIF file + (reported by @JieyongMa). + [unfiled] File magic would not effectively rule out internal loaders. + [unfiled] Very big images could cause absurd allocation requests triggering + an abort in the loader. + +1.10.1 (2022-04-04) +------------------- + +This release brings one small but important fix and a few minor corrections +to the documentation. + +* Bug fixes: + github#87 Garbled last row of pixels on some images (found by @hydrargyrum). + [unfiled] Correctly label new functions since 1.10. + +1.10.0 (2022-03-20) +------------------- + +This is a feature release focused on compatibility, presentation and +ergonomics. + +* New option: --animate . On by default. Can be turned off to replace + animations with a still frame. Suggested by Lionel Dricot in github#79. + +* New option: --center or -C . Off by default. Can be turned on to center + images. Suggested by Lionel Dricot in github#83. + +* New options: --margin-bottom and --margin-right . These permit + using all available space (with --margin-bottom 0) or some smaller amount. + Suggested by @crmabs in github#61. + +* New option: --polite . On by default. Can be turned off to correct + issues caused by previous terminal state (e.g. no sixel scrolling) and + improve presentation (e.g. by temporarily hiding the cursor). This can + leave the terminal in an altered state, which is somewhat rude. + +* New option: --threads . Allows manually specifying the number of threads + to use. Defaults to one per detected logical CPU core. + +* When invoked with redirected input and no arguments, act like a filter as + if invoked with "-". Here's an example that downloads an image, converts it + to Unicode text and mails it to hello@example.com: + + $ curl https://hpjansson.org/chafa/img/lc.jpg \ + | chafa -f symbols -c none -s 77 --invert --dither bayer \ + | mailx hello@example.com + +* If input or output is being redirected, default to playing animations only + once instead of looping forever. This can be overridden with '-d inf'. + +* Replaced obsolete Autoconf macros (Mikel Olasagasti Uranga). + +* Improved image loaders: + - GIF: Load GIF87a images as well as GIF89a. + - PNG: Add an internal copy of LodePNG, bypassing ImageMagick. This improves + performance and helps with eventually phasing out the latter. + - XWD: Support images generated by 'convert'. + +* Improved terminal support: + - Contour: Enable sixel support (Dmitry Atamanov). + - foot: Open-ended TERM string matching (Daniel Eklöf). + - Konsole: Enable sixel support (Dmitry Atamanov). + - WezTerm: Enable sixel support. + +* Bug fixes: + github#76 Smolscale uses too much stack space (found by Hoang Nguyen). + github#81 Chafa --version is returned to stderr, not stdout (found by + Lionel Dricot). + [unfiled] Potential memory overrun when using Floyd-Steinberg dithering + in symbols mode. + [unfiled] iTerm2 compatibility issue with WezTerm and possibly others. + +1.8.0 (2021-08-31) +------------------ + +This is a major feature release. + +* Added basic support for the Kitty and iTerm2 graphics protocols. These are + enabled automatically when corresponding terminal support is detected, or + manually with '-f kitty' or '-f iterm'. + +* Implemented an 8-color mode, selectable with '-c 8' (Øyvind Kolås). + +* Implemented a foreground-only switch, '--fg-only'. This produces character + art using foreground colors only, and will avoid modifying or resetting + the background color. Looks best with non-contiguous symbols (e.g. ascii). + +* Added builtins for Japanese kana fullwidth symbols. These can now be used + without loading any external fonts (try '-c none --symbols wide'). + +* Added builtins for legacy computer symbols (mainly wedges and sextant + blocks). These were widely used in PETSCII and Teletext, and can be enabled + using --symbols or --fill with their respective tags: legacy, wedge and + sextant (Øyvind Kolås). + +* Since there is a growing number of builtin symbols that may not be available + everywhere, the default selection has been restricted to the widely supported + block and border sets. + +* If possible, we now select a visually blank character from the specified + symbol/fill sets instead of hardcoding ASCII space for featureless cells. + One practical upshot of this is that the constant-width braille range can + be used to produce consistent images even in contexts with variable-width + fonts. U+2800 (BRAILLE PATTERN BLANK) will then be used in blank cells. + +* Improved terminal size detection when used with pipelines and redirection. + This should now produce an image properly sized for your terminal: + + $ curl https://i.imgur.com/WFDEFVg.jpeg | chafa - | tee out + +* ChafaCanvas gained API functions for programmatically getting and setting + character cell contents. These are used in a new example in tests/ncurses.c + demonstrating ncurses integration. + +* Made --disable-rpath the default in order to simplify packaging. + +* Added a HACKING file featuring a much needed release checklist. + +* Improved terminal support: + - Ctx will now use optimizing REP sequences at high -O levels. + - foot now defaults to sixels (Daniel Eklöf). + - iTerm2 now defaults to the iTerm2 protocol. + - Kitty now defaults to the Kitty protocol. + - st now defaults to truecolor symbols (Roman Wagner). + +* Bug fixes: + github#44 Missing error handling on stdout writes (reported by Markus + Elfring). + [unfiled] Solid symbols erroneously replaced by fill in FGBG mode. + [unfiled] Integer formatter was not using fast path for 8-bit values. + [unfiled] Wrong default cell aspect used for sixel graphics. + +1.6.1 (2021-06-03) +------------------ + +This is a bugfix release. + +* Add NOCONFIGURE variable to autogen.sh to skip configure (Biswapriyo Nath). + +* Bug fixes: + github#50 SIGBUS while loading huge GIFs (reported by Grzegorz Koperwas). + github#52 Produces small glitches in output with some images (reported by + Sami Farin). + github#54 Haiku port fails on 32bit (Luc Schrijvers). + [unfiled] Exclude RTL code points that could break the output. + [unfiled] Apple Terminal lacks truecolor support, so make it default to 256 + colors (reported by Behdad Esfahbod). + [unfiled] Fix typo affecting middle dot symbol. + +1.6.0 (2021-01-15) +------------------ + +This release adds major features and important fixes to both libchafa and +the chafa command-line tool. + +* Added support for fullwidth symbols that take up two character + cells. These are common in East Asian scripts. Single-cell and + double-cell symbols can be mixed, and -f symbols mode will use both + if possible. + +* New symbol tags: Alpha, digit, alnum, narrow, wide, ambiguous, ugly, bad. + "Ambiguous" symbols have uncertain widths and may render poorly in some + terminals. "Ugly" denotes symbols that are unsuitable for Chafa's + cell-based graphics (multicolor emoji, ideographic descriptors, etc). + "Bad" is a superset of these two categories. Bad symbols are always + excluded unless explicitly enabled with e.g. CHAFA_SYMBOL_TAG_BAD + (--symbols +bad in the frontend). + +* The font loader (--glyph-file option) now does a better job with + proportional fonts. + +* Added options for controlling lossless optimization of output. Currently, + attribute reuse and character repetition (REP sequence) are implemented. + +* Added -O option to the frontend. This controls the optimization level. + +* Added a simple abstraction layer for terminal control sequences + (ChafaTermInfo and ChafaTermDb). This allows for improved terminal + support. + +* FbTerm is now supported with TERM=fbterm in the environment. + +* Bug fixes: + github#43 Fix signal handler (reported by Markus Elfring). + [unfiled] Crash when invalid font paths were passed on command line. + [unfiled] Small typo in fontgen's README (Tim Gates). + [unfiled] Bad contrast adjustment in images with transparency. + +1.4.1 (2020-04-10) +------------------ + +This is a bugfix release. + +* Added configure option --disable-rpath. This allows packagers to + prevent the hard-coded library search path from being embedded in + the chafa command-line tool (Mo Zhou, github#39). + +* Added defaults for the yaft terminal. + +* Bug fixes: + github#40 Fails to build on hurd-i386 + other i386 (reported by + Mo Zhou). + [unfiled] Rare failed assert with mostly transparent sixel image + (reported by Reddit user spryfigure). + [unfiled] Minor typo in function docstring. + +1.4.0 (2020-04-01) +------------------ + +This release adds major features to both libchafa and the chafa +command-line tool. + +* Added sixel graphics support. Chafa will automatically produce sixels + if the connected terminal supports it. It can also be forced using the + new -f or --format flag. This is a new implementation written from the + ground up to maximize throughput. + +* Added the --glyph-file option, which loads glyph-symbol mappings from + any file format supported by FreeType (TTF, PCF, etc). This allows for + custom font support and improved symbol selection. + +* Added the --speed option specifying animation speed. It accepts a + unitless multiplier, a specific number of frames per second, or "max" + for maximum throughput. + +* There are now two ways to assign colors to symbol cells. Formerly, + this was done based on the average color of the covered area. The new + default is to use the median color, which produces sharper edges, + but is slightly more prone to high-frequency noise. The new option + --color-extractor selects the method to employ. + +* When displaying multiple files, the default delay between files has + been changed from three seconds to zero. Animations will still play + through once. This can be overridden on the chafa command line with -d + or --duration. + +* Minor tweaks to built-in symbols. + +* Performance improvements: + - Halved pixel storage requirements from 64 bits to 32 bits, resulting + in significant memory savings. + - Now builds with -ffast-math, yielding a big speedup for DIN99d. + +1.2.2 (2020-03-02) +------------------ + +This minor release fixes a bug causing builds linking with libchafa to +fail. + +* Bug fixes: + github#34 Cannot compile example (found by Petr Bílek). + 1.2.1 (2019-08-15) ------------------ diff -Nru chafa-1.2.1/README chafa-1.12.4/README --- chafa-1.2.1/README 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/README 2022-11-12 01:18:35.000000000 +0000 @@ -1,17 +1,18 @@ Chafa ===== -Chafa is a command-line utility that converts all kinds of images, including -animated GIFs, into ANSI/Unicode character output that can be displayed in a -terminal. - -It is highly configurable, with support for alpha transparency and multiple -color modes and color spaces, combining selectable ranges of Unicode -characters to produce the desired output. +Chafa is a command-line utility that converts image data, including +animated GIFs, into graphics formats or ANSI/Unicode character art suitable +for display in a terminal. It has broad feature support, allowing it to be +used on devices ranging from historical teleprinters to modern terminal +emulators and everything in between. The core functionality is provided by a C library with a public, well-documented API. +Both library and frontend tools are covered by the Lesser GPL license, +version 3 or later (LGPLv3+). + For the most up-to-date information, please see https://hpjansson.org/chafa/ Installing with package manager @@ -24,10 +25,13 @@ Brew .......... brew install chafa Debian ........ apt install chafa Fedora ........ dnf install chafa +FreeBSD ....... pkg install chafa Gentoo ........ emerge media-gfx/chafa Guix .......... guix install chafa Kali Linux .... apt install chafa -openSUSE ...... zypper ar -f obs://graphics graphics && zypper in chafa +MacPorts ...... port install chafa +OpenBSD ....... pkg_add chafa +openSUSE ...... zypper in chafa Ubuntu ........ apt install chafa See https://hpjansson.org/chafa/download/ for more. @@ -56,7 +60,12 @@ You will need GCC, make, Autoconf, Automake, Libtool and the GLib development package installed to compile Chafa from its git repository. If you want to build the command-line tool `chafa` and not just the library, -you will additionally need the ImageMagick development packages. +you will additionally need development packages for: + +* FreeType2 +* ImageMagick +* libjpeg (optional) +* libwebp (optional) If you want to build documentation, you will also need gtk-doc. diff -Nru chafa-1.2.1/README.md chafa-1.12.4/README.md --- chafa-1.2.1/README.md 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/README.md 2022-11-12 01:18:35.000000000 +0000 @@ -11,7 +11,7 @@ Master Build Status   -1.2 Build Status +1.12 Build Status @@ -22,20 +22,21 @@

AboutGalleryPackagesDevelopment

-Chafa is a command-line utility that converts all kinds of images, including -animated GIFs, into ANSI/Unicode character output that can be displayed in a -terminal. - -It is highly configurable, with support for alpha transparency and multiple -color modes and color spaces, combining selectable ranges of Unicode -characters to produce the desired output. +Chafa is a command-line utility that converts image data, including +animated GIFs, into graphics formats or ANSI/Unicode character art suitable +for display in a terminal. It has broad feature support, allowing it to be +used on devices ranging from historical teleprinters to modern terminal +emulators and everything in between. The core functionality is provided by a C library with a public, well-documented API. -It has [official web pages](https://hpjansson.org/chafa/) and [C API -documentation](https://hpjansson.org/chafa/ref/) online. Check out the -[gallery](https://hpjansson.org/chafa/gallery/) for screenshots. +Both library and frontend tools are covered by the Lesser GPL license, +version 3 or later (LGPLv3+). + +The [official web pages](https://hpjansson.org/chafa/) and [C API +documentation](https://hpjansson.org/chafa/ref/) can be found online. Check +out the [gallery](https://hpjansson.org/chafa/gallery/) for screenshots. ## Installing @@ -47,7 +48,13 @@ You will need GCC, make, Autoconf, Automake, Libtool and the GLib development package installed to compile Chafa from its git repository. If you want to build the command-line tool `chafa` and not just the library, -you will additionally need the ImageMagick development packages. +you will additionally need development packages for: + +* FreeType2. Often packaged as `libfreetype6-dev` or `freetype2-devel`. +* libjpeg (optional). Look for `libjpeg-dev`, `libjpeg62-devel` or `libjpeg8-devel`. +* librsvg (optional). Look for `librsvg2-dev` or `librsvg-devel`. +* libtiff (optional). Look for `libtiff5-dev` or `libtiff-devel`. +* libwebp (optional). Look for `libwebp-dev` or `libwebp-devel`. If you want to build documentation, you will also need gtk-doc. diff -Nru chafa-1.2.1/SECURITY.md chafa-1.12.4/SECURITY.md --- chafa-1.2.1/SECURITY.md 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/SECURITY.md 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,38 @@ +# Security Policies and Procedures + +This document outlines security procedures and general policies for Chafa. + +## Reporting a Bug + +We are grateful for the testing and analysis carried out by the community. All +bug reports are taken seriously. + +Normally, bugs can be filed directly in the public GitHub issue tracker, but if +you believe there is a security impact, please contact the lead maintainer at +his e-mail address instead. + +We will most likely respond within 48 hours, but since Chafa is a volunteer +project, please allow up to a week for those rare times we're away from the +keyboard or general connectivity. + +When a fix is published, you will receive credit under your real name or bug +tracker handle in the NEWS document and possibly elsewhere (GitHub, blog post, +etc). If you prefer to remain anonymous or pseudonymous, you should mention +this in your e-mail. + +## Disclosure Policy + +The maintainer will coordinate the fix and release process, involving the +following steps: + + * Confirm the problem and determine the affected versions. + * Audit code to find any potential similar problems. + * Prepare fixes for all releases still under maintenance. These fixes will be + released as fast as possible. + +You may be asked to provide further information in pursuit of a fix. + +## Comments on this Policy + +If you have suggestions on how this process could be improved, please submit an +issue or pull request. diff -Nru chafa-1.2.1/tests/chafa-tool-bad-test.sh chafa-1.12.4/tests/chafa-tool-bad-test.sh --- chafa-1.2.1/tests/chafa-tool-bad-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-bad-test.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,10 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +for format in $formats; do + for size in $sizes; do + run_cmd_all_bad_files "$tool -f $format -c 16 --dither fs -s $size --threads 12 -d 0 --speed max" || exit $? + done +done diff -Nru chafa-1.2.1/tests/chafa-tool-cmode-test.sh chafa-1.12.4/tests/chafa-tool-cmode-test.sh --- chafa-1.2.1/tests/chafa-tool-cmode-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-cmode-test.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,12 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +for cmode in $cmodes; do + for size in $sizes; do + for n_threads in $thread_counts; do + run_cmd "$tool -f symbol -c $cmode -s $size --threads $n_threads --animate no" || exit $? + done + done +done diff -Nru chafa-1.2.1/tests/chafa-tool-format-test.sh chafa-1.12.4/tests/chafa-tool-format-test.sh --- chafa-1.2.1/tests/chafa-tool-format-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-format-test.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,12 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +for format in $formats; do + for size in $sizes; do + for n_threads in $thread_counts; do + run_cmd_all_safe_files "$tool -f $format -c full -s $size --threads $n_threads -d 0 --speed max" || exit $? + done + done +done diff -Nru chafa-1.2.1/tests/chafa-tool-gallery.sh chafa-1.12.4/tests/chafa-tool-gallery.sh --- chafa-1.2.1/tests/chafa-tool-gallery.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-gallery.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,119 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +tempdir="${top_builddir}/tests/.temp" + +xvfb=$(which Xvfb) +[ "x${xvfb}" = "x" ] && echo "Missing Xvfb" && exit 1 + +gterm_name=xfce4-terminal +gterm=$(which ${gterm_name}) +[ "x${gterm}" = "x" ] && echo "Missing ${gterm_name}" && exit 1 + +montage=$(which montage) +[ "x${montage}" = "x" ] && echo "Missing montage" && exit 1 + +# Terminal parameters +font='xos4 Terminus 8' +geometry=60x40 +screen_size=1280x1024 + +# Chafa parameter lists +color_modes="tc 240 16 8 2 none" +color_spaces="rgb din99d" +color_extractors="average median" +dither_types="none ordered diffusion" +symbol_selectors="+0 all wide" +work_factors="1 5 9" + +# Input filename. Must be in data/good/. +file="taxic.jpg" + +function gen_screenshot +{ + FNAME="$1" + + echo -- Running -e "bash -c \"${tool} -s ${geometry} -w ${5} -c ${2} --symbols ${3} --color-space ${4} \ +${top_srcdir}/tests/data/good/${FNAME} ; sleep 0.5 ; import -window chafa out/out-${2}-${3}-${5}-${4}.png -trim\"" + + HOME="${tempdir}" XDG_CONFIG_HOME="${HOME}/.config" DISPLAY=:99 ${gterm} \ + -T chafa \ + --disable-server \ + --geometry ${geometry}+0+0 \ + --font "$font" \ + --hide-menubar \ + --hide-toolbar \ + --hide-borders \ + --hide-scrollbar \ + -e "bash -c \"${tool} -s ${geometry} -w ${5} -c ${2} --symbols ${3} --color-space ${4} \ +${top_srcdir}/tests/data/good/${FNAME}; sleep 0.5 ; import -window chafa ${tempdir}/out/${2}-${3}-${5}-${4}.png -trim >/dev/null 2>&1\"" >/dev/null 2>&1 +} + +trap "" EXIT SIGQUIT SIGSTOP SIGTERM + +# Make sure we don't blow away any old .temp dir +rm -Rf "${tempdir}/.config" +rm -Rf "${tempdir}/out" +rmdir "${tempdir}" + +mkdir -p "${tempdir}/out" + +mkdir -p ${tempdir}/.config/xfce4/terminal +cat >${tempdir}/.config/xfce4/terminal/terminalrc </dev/null 2>&1 & +XVFB_PID=$! + +sleep 2 + +for cmode in ${color_modes}; do + for symbols in ${symbol_selectors}; do + for cspace in ${color_spaces}; do + for work in ${work_factors}; do + gen_screenshot "$file" "$cmode" "$symbols" "$cspace" "$work" + done + done + done +done + +kill ${XVFB_PID} + +rm -f "${top_builddir}/tests/chafa-tool-gallery.png" +${montage} -stroke white -background grey20 -geometry +4+4 -tile 6 -label '%f' \ + "${tempdir}/out/*.png" \ + "${top_builddir}/tests/chafa-tool-gallery.png" diff -Nru chafa-1.2.1/tests/chafa-tool-loader-test.sh chafa-1.12.4/tests/chafa-tool-loader-test.sh --- chafa-1.2.1/tests/chafa-tool-loader-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-loader-test.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,11 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +extensions="$(get_supported_loaders)" + +for ext in $extensions; do + [ "x${ext}" = "ximagemagick" ] && continue + run_cmd_single_file "$tool -f sixel --threads 12 --animate no" "good/pixel.$ext" || exit $? +done diff -Nru chafa-1.2.1/tests/chafa-tool-options-test.sh chafa-1.12.4/tests/chafa-tool-options-test.sh --- chafa-1.2.1/tests/chafa-tool-options-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-options-test.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,24 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +color_spaces="rgb din99d" +color_extractors="average median" +dither_types="none ordered diffusion" +symbol_selectors="none all wide 0..1ffff" + +for format in $formats; do + for color_space in $color_spaces; do + for color_extractor in $color_extractors; do + for dither_type in $dither_types; do + for main_symbols in $symbol_selectors; do + for fill_symbols in $symbol_selectors; do + run_cmd "$tool -f $format -c 240 -s 53 --threads 12 --animate no --color-space $color_space \ +--color-extractor $color_extractor --dither $dither_type --symbols $main_symbols --fill $fill_symbols" || exit $? + done + done + done + done + done +done diff -Nru chafa-1.2.1/tests/chafa-tool-pipe-test.sh chafa-1.12.4/tests/chafa-tool-pipe-test.sh --- chafa-1.2.1/tests/chafa-tool-pipe-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-pipe-test.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,6 @@ +#!/bin/sh + +[ "x${srcdir}" = "x" ] && srcdir="." +. "${srcdir}/chafa-tool-test-common.sh" + +run_cmd "$tool -f symbol -c full -s 63 --threads 12 --animate no < " || exit $? diff -Nru chafa-1.2.1/tests/chafa-tool-test-common.sh chafa-1.12.4/tests/chafa-tool-test-common.sh --- chafa-1.2.1/tests/chafa-tool-test-common.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/chafa-tool-test-common.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,64 @@ +#!/bin/sh + +run_cmd () { + cmd="$1 ${top_srcdir}/tests/data/good/card-32c-noalpha.png >/dev/null" + echo "$cmd" >&2 + sh -c "$cmd" || exit $? +} + +run_cmd_single_file () { + file="$2" + cmd="$1 ${top_srcdir}/tests/data/$file >/dev/null" + echo "$cmd" >&2 + sh -c "$cmd" || exit $? +} + +run_cmd_all_safe_files () { + # Only run on files for which we're guaranteed to have loaders. + # '$dir/*.{gif,png,xwd}' is a Bash-ism, so we can't use it. + dir="${top_srcdir}/tests/data/good" + cmd="$1 $dir/*.gif $dir/*.png $dir/*.xwd >/dev/null" + echo "$cmd" >&2 + sh -c "$cmd" || exit $? +} + +run_cmd_all_bad_files () { + # Since this only fails on crashes, it's fine to run it on absolutely + # everything (including unsupported formats, build files etc). + cmd="$1 ${top_srcdir}/tests/data/bad/* >/dev/null" + echo "$cmd" >&2 + sh -c "$cmd" + result=$? + + # For corrupt files, an exit value of 1 or 2 is a fine result, + # but other values are trouble (e.g. terminated by signal). + if test $result -gt 2; then exit $result; fi +} + +get_supported_loaders () { + sh -c "$tool --version" \ + | grep '^Loaders: ' \ + | sed 's/[^:]*: *\(.*\)/\1/' \ + | tr [:upper:] [:lower:] +} + +[ "x${top_srcdir}" = "x" ] && top_srcdir="${srcdir}/.." +[ "x${top_builddir}" = "x" ] && top_builddir=".." +[ "x$1" = "xlong" ] && long="yes" + +tool="${top_builddir}/tools/chafa/chafa" + +if [ "x$long" = "xyes" ]; then + formats="symbol sixel kitty iterm" + cmodes="none 2 8 16/8 16 240 256 full" + sizes="$(seq 1 100)" + thread_counts="$(seq 1 32) 61" +else + # The 13x13 size is specifically required to trigger the normalization + # overflow (see commit 60d7718f9d8fa591d3d69079fe583913c58c19d9). + + formats="symbol sixel kitty iterm" + cmodes="none 2 16/8 16 256 full" + sizes="1 3 13x13 133" + thread_counts="1 2 3 12 32 61" +fi Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/libnsgif-lzw-oob.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/libnsgif-lzw-oob.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/libnsgif-uninitialized-frame-fields.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/libnsgif-uninitialized-frame-fields.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/lodepng-adam7-mystery-over-read.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/lodepng-adam7-mystery-over-read.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/lodepng-zero-length-literal.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/lodepng-zero-length-literal.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/lodepng-zlib-big-alloc.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/lodepng-zlib-big-alloc.png differ diff -Nru chafa-1.2.1/tests/data/bad/Makefile.am chafa-1.12.4/tests/data/bad/Makefile.am --- chafa-1.2.1/tests/data/bad/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/data/bad/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,12 @@ +EXTRA_DIST = \ + libnsgif-lzw-oob.gif \ + libnsgif-uninitialized-frame-fields.gif \ + lodepng-adam7-mystery-over-read.png \ + lodepng-zero-length-literal.png \ + lodepng-zlib-big-alloc.png \ + no-frame-data.gif \ + pixops-normalize-signed-overflow.gif \ + smolscale-unaligned-1.xwd \ + smolscale-unaligned-2.xwd \ + smolscale-unaligned-3.xwd \ + smolscale-unaligned-4.xwd diff -Nru chafa-1.2.1/tests/data/bad/no-frame-data.gif chafa-1.12.4/tests/data/bad/no-frame-data.gif --- chafa-1.2.1/tests/data/bad/no-frame-data.gif 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/data/bad/no-frame-data.gif 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1 @@ +GIF89a; \ No newline at end of file Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/pixops-normalize-signed-overflow.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/pixops-normalize-signed-overflow.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/smolscale-unaligned-1.xwd and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/smolscale-unaligned-1.xwd differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/smolscale-unaligned-2.xwd and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/smolscale-unaligned-2.xwd differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/smolscale-unaligned-3.xwd and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/smolscale-unaligned-3.xwd differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/bad/smolscale-unaligned-4.xwd and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/bad/smolscale-unaligned-4.xwd differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/anim.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/anim.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/anim-local-cmaps.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/anim-local-cmaps.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/card-32c-alpha.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/card-32c-alpha.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/card-32c-noalpha.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/card-32c-noalpha.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/card-full-alpha.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/card-full-alpha.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/card-full-noalpha.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/card-full-noalpha.png differ diff -Nru chafa-1.2.1/tests/data/good/Makefile.am chafa-1.12.4/tests/data/good/Makefile.am --- chafa-1.2.1/tests/data/good/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/data/good/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,19 @@ +EXTRA_DIST = \ + anim-local-cmaps.gif \ + anim.gif \ + card-32c-alpha.png \ + card-32c-noalpha.png \ + card-full-alpha.png \ + card-full-noalpha.png \ + noise-32x32-indexed.png \ + noise-32x32.gif \ + noise-32x32.png \ + noise-32x32.xwd \ + pixel.gif \ + pixel.jpeg \ + pixel.png \ + pixel.svg \ + pixel.tiff \ + pixel.webp \ + pixel.xwd \ + taxic.jpg Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/noise-32x32.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/noise-32x32.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/noise-32x32-indexed.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/noise-32x32-indexed.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/noise-32x32.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/noise-32x32.png differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/noise-32x32.xwd and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/noise-32x32.xwd differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/pixel.gif and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/pixel.gif differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/pixel.jpeg and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/pixel.jpeg differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/pixel.png and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/pixel.png differ diff -Nru chafa-1.2.1/tests/data/good/pixel.svg chafa-1.12.4/tests/data/good/pixel.svg --- chafa-1.2.1/tests/data/good/pixel.svg 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/data/good/pixel.svg 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,5 @@ + + + + + Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/pixel.tiff and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/pixel.tiff differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/pixel.webp and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/pixel.webp differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/pixel.xwd and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/pixel.xwd differ Binary files /tmp/tmpnsi0m7qe/fx3IhyIqfE/chafa-1.2.1/tests/data/good/taxic.jpg and /tmp/tmpnsi0m7qe/Aref8rR4Qr/chafa-1.12.4/tests/data/good/taxic.jpg differ diff -Nru chafa-1.2.1/tests/data/Makefile.am chafa-1.12.4/tests/data/Makefile.am --- chafa-1.2.1/tests/data/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/data/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1 @@ +SUBDIRS = bad good diff -Nru chafa-1.2.1/tests/example.c chafa-1.12.4/tests/example.c --- chafa-1.2.1/tests/example.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/example.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,63 @@ +#include +#include + +#define PIX_WIDTH 3 +#define PIX_HEIGHT 3 +#define N_CHANNELS 4 + +int +main (int argc, char *argv []) +{ + const guint8 pixels [PIX_WIDTH * PIX_HEIGHT * N_CHANNELS] = + { + 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff + }; + ChafaSymbolMap *symbol_map; + ChafaCanvasConfig *config; + ChafaCanvas *canvas; + GString *gs; + + /* Specify the symbols we want */ + symbol_map = chafa_symbol_map_new (); + chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_ALL); + + /* Set up a configuration with the symbols and the canvas size in characters */ + config = chafa_canvas_config_new (); + chafa_canvas_config_set_geometry (config, 40, 20); + chafa_canvas_config_set_symbol_map (config, symbol_map); + + /* Create canvas */ + canvas = chafa_canvas_new (config); + + /* Draw pixels and build ANSI string */ + + /* Test a deprecated function */ + chafa_canvas_set_contents_rgba8 (canvas, + pixels, + PIX_WIDTH, + PIX_HEIGHT, + PIX_WIDTH * N_CHANNELS); + + chafa_canvas_draw_all_pixels (canvas, + CHAFA_PIXEL_RGBA8_UNASSOCIATED, + pixels, + PIX_WIDTH, + PIX_HEIGHT, + PIX_WIDTH * N_CHANNELS); + + gs = chafa_canvas_build_ansi (canvas); + + /* Print the string */ + fwrite (gs->str, sizeof (char), gs->len, stdout); + fputc ('\n', stdout); + + /* Free resources */ + g_string_free (gs, TRUE); + chafa_canvas_unref (canvas); + chafa_canvas_config_unref (config); + chafa_symbol_map_unref (symbol_map); + + return 0; +} diff -Nru chafa-1.2.1/tests/Makefile.am chafa-1.12.4/tests/Makefile.am --- chafa-1.2.1/tests/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,44 @@ +SUBDIRS = data + +AM_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) +LDADD = $(GLIB_LIBS) $(top_builddir)/chafa/libchafa.la + +## --- Backend tests --- + +noinst_PROGRAMS = \ + term-info-test + +term_info_test_SOURCES = \ + term-info-test.c + +## --- Frontend tests --- + +TOOL_CHECKS = \ + chafa-tool-bad-test.sh \ + chafa-tool-cmode-test.sh \ + chafa-tool-format-test.sh \ + chafa-tool-loader-test.sh \ + chafa-tool-options-test.sh \ + chafa-tool-pipe-test.sh + +TESTS = \ + term-info-test \ + $(TOOL_CHECKS) + +AM_TESTS_ENVIRONMENT = \ + export top_builddir=$(top_builddir) \ + export top_srcdir=$(top_srcdir) \ + ; + +## --- General --- + +## Include $(top_builddir)/chafa to get generated chafaconfig.h. + +AM_CPPFLAGS = \ + -I$(top_srcdir)/chafa \ + -I$(top_builddir)/chafa + +EXTRA_DIST = \ + $(TOOL_CHECKS) \ + chafa-tool-gallery.sh \ + chafa-tool-test-common.sh diff -Nru chafa-1.2.1/tests/ncurses.c chafa-1.12.4/tests/ncurses.c --- chafa-1.2.1/tests/ncurses.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/ncurses.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,217 @@ +/* Example program that shows how to use a Chafa canvas with ncurses. + * + * To build: + * + * gcc ncurses.c $(pkg-config --libs --cflags chafa) $(ncursesw6-config --libs --cflags) -o ncurses + */ + +#include +#include +#include + +/* Parameters for gradient pixmap. It will be scaled automatically to fit the canvas, + * so this just needs to be big enough to avoid it getting too blurry. The number + * of channels is always four, corresponding to CHAFA_PIXEL_RGBA8_UNASSOCIATED. */ +#define PIXMAP_WIDTH 1024 +#define PIXMAP_HEIGHT 1024 +#define PIXMAP_N_CHANNELS 4 + +/* Terminal size detected in main loop */ +static int screen_width, screen_height; + +static ChafaCanvasMode +detect_canvas_mode (void) +{ + /* COLORS is a global variable defined by ncurses. It depends on termcap + * for the terminal specified in TERM. In order to test the various modes, you + * could try running this program with either of these: + * + * TERM=xterm + * TERM=xterm-16color + * TERM=xterm-256color + * TERM=xterm-direct + */ + return COLORS >= (1 << 24) ? CHAFA_CANVAS_MODE_TRUECOLOR + : COLORS >= (1 << 8) ? CHAFA_CANVAS_MODE_INDEXED_240 + : COLORS >= (1 << 4) ? CHAFA_CANVAS_MODE_INDEXED_16 + : COLORS >= (1 << 3) ? CHAFA_CANVAS_MODE_INDEXED_8 + : CHAFA_CANVAS_MODE_FGBG; +} + +static ChafaCanvas * +create_canvas (void) +{ + ChafaSymbolMap *symbol_map; + ChafaCanvasConfig *config; + ChafaCanvas *canvas; + ChafaCanvasMode mode = detect_canvas_mode (); + + /* Specify the symbols we want: Box drawing and block elements are both + * useful and widely supported. */ + + symbol_map = chafa_symbol_map_new (); + chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_SPACE); + chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_BLOCK); + chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_BORDER); + + /* Set up a configuration with the symbols and the canvas size in characters */ + + config = chafa_canvas_config_new (); + chafa_canvas_config_set_canvas_mode (config, mode); + chafa_canvas_config_set_symbol_map (config, symbol_map); + + /* Reserve one row below canvas for status text */ + chafa_canvas_config_set_geometry (config, screen_width, screen_height - 1); + + /* Apply tweaks for low-color modes */ + + if (mode == CHAFA_CANVAS_MODE_INDEXED_240) + { + /* We get better color fidelity using DIN99d in 240-color mode. + * This is not needed in 16-color mode because it uses an extra + * preprocessing step instead, which usually performs better. */ + chafa_canvas_config_set_color_space (config, CHAFA_COLOR_SPACE_DIN99D); + } + + if (mode == CHAFA_CANVAS_MODE_FGBG) + { + /* Enable dithering in monochromatic mode so gradients become + * somewhat legible. */ + chafa_canvas_config_set_dither_mode (config, CHAFA_DITHER_MODE_ORDERED); + } + + /* Create canvas */ + + canvas = chafa_canvas_new (config); + + chafa_symbol_map_unref (symbol_map); + chafa_canvas_config_unref (config); + return canvas; +} + +static void +paint_canvas (ChafaCanvas *canvas) +{ + guint8 *pixmap; + int x, y; + + /* Generate a gradient pixmap */ + + pixmap = g_malloc (PIXMAP_WIDTH * PIXMAP_HEIGHT * PIXMAP_N_CHANNELS); + + for (y = 0; y < PIXMAP_HEIGHT; y++) + { + for (x = 0; x < PIXMAP_WIDTH; x++) + { + guint8 *pixel = pixmap + (y * PIXMAP_WIDTH + x) * PIXMAP_N_CHANNELS; + pixel [0] = (x * 255) / PIXMAP_WIDTH; + pixel [1] = (y * 255) / PIXMAP_HEIGHT; + pixel [2] = 0; + pixel [3] = 255; + } + } + + /* Paint it to the canvas */ + + chafa_canvas_draw_all_pixels (canvas, + CHAFA_PIXEL_RGBA8_UNASSOCIATED, + pixmap, + PIXMAP_WIDTH, + PIXMAP_HEIGHT, + PIXMAP_WIDTH * PIXMAP_N_CHANNELS); + + g_free (pixmap); +} + +static void +canvas_to_ncurses (ChafaCanvas *canvas) +{ + ChafaCanvasMode mode = detect_canvas_mode (); + int pair = 256; /* Reserve lower pairs for application in direct-color mode */ + int x, y; + + for (y = 0; y < screen_height - 1; y++) + { + for (x = 0; x < screen_width; x++) + { + wchar_t wc [2]; + cchar_t cch; + int fg, bg; + + /* wchar_t is 32-bit in glibc, but this may not work on e.g. Windows */ + wc [0] = chafa_canvas_get_char_at (canvas, x, y); + wc [1] = 0; + + if (mode == CHAFA_CANVAS_MODE_TRUECOLOR) + { + chafa_canvas_get_colors_at (canvas, x, y, &fg, &bg); + init_extended_pair (pair, fg, bg); + } + else if (mode == CHAFA_CANVAS_MODE_FGBG) + { + pair = 0; + } + else + { + /* In indexed color mode, we've probably got enough pairs + * to just let ncurses allocate and reuse as needed. */ + chafa_canvas_get_raw_colors_at (canvas, x, y, &fg, &bg); + pair = alloc_pair (fg, bg); + } + + setcchar (&cch, wc, A_NORMAL, -1, &pair); + mvadd_wch (y, x, &cch); + pair++; + } + } +} + +static void +show_image (void) +{ + ChafaCanvas *canvas; + + canvas = create_canvas (); + + paint_canvas (canvas); + canvas_to_ncurses (canvas); + mvprintw (screen_height - 1, 0, "%d colors detected. Try resizing, or press any key to exit.", COLORS); + + chafa_canvas_unref (canvas); +} + +int +main (int argc, char *argv []) +{ + int running = TRUE; + + /* Set up locale to get proper Unicode */ + + setlocale (LC_ALL, ""); + + /* Start interactive ncurses session */ + + initscr (); + start_color (); + use_default_colors (); + raw (); + keypad (stdscr, TRUE); + noecho (); + curs_set (0); + + /* Keep running until a key is pressed. Handle terminal resize. */ + + while (running) + { + clear (); + getmaxyx (stdscr, screen_height, screen_width); + show_image (); + refresh (); + + if (getch () != KEY_RESIZE) + running = FALSE; + } + + endwin (); + return 0; +} diff -Nru chafa-1.2.1/tests/postinstall.sh chafa-1.12.4/tests/postinstall.sh --- chafa-1.2.1/tests/postinstall.sh 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/postinstall.sh 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,6 @@ +#!/bin/sh + +set -evx + +cc -g example.c $(pkg-config --libs --cflags chafa) -o example +./example diff -Nru chafa-1.2.1/tests/term-info-test.c chafa-1.12.4/tests/term-info-test.c --- chafa-1.2.1/tests/term-info-test.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tests/term-info-test.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,65 @@ +#include "config.h" + +#include +#include + +static void +formatting_test (void) +{ + ChafaTermInfo *ti; + gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 12 + 1]; + gchar *out = buf; + + ti = chafa_term_info_new (); + + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT, "soft-reset", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_CURSOR_UP, "cursor-up-%1", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_CURSOR_TO_POS, "%1-cursor-to-pos-%2", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT, "%1%2-fg-direct-%3", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT, "%1-bg-direct%2%3-", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT, "%1%2-fgbg-%3,%4%5-%6", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FG_16, "aix%1,", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_BG_16, "aix%1,", NULL); + chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16, "aix-%1-%2,", NULL); + + out = chafa_term_info_emit_reset_terminal_soft (ti, out); + out = chafa_term_info_emit_cursor_up (ti, out, 9876); + out = chafa_term_info_emit_cursor_to_pos (ti, out, 1234, 0); + out = chafa_term_info_emit_set_color_fg_direct (ti, out, 41, 0, 244); + out = chafa_term_info_emit_set_color_bg_direct (ti, out, 0, 100, 99); + out = chafa_term_info_emit_set_color_fgbg_direct (ti, out, 1, 199, 99, 0, 0, 9); + out = chafa_term_info_emit_set_color_fg_16 (ti, out, 0); + out = chafa_term_info_emit_set_color_fg_16 (ti, out, 8); + out = chafa_term_info_emit_set_color_bg_16 (ti, out, 0); + out = chafa_term_info_emit_set_color_bg_16 (ti, out, 8); + out = chafa_term_info_emit_set_color_fgbg_16 (ti, out, 0, 0); + out = chafa_term_info_emit_set_color_fgbg_16 (ti, out, 8, 8); + + *out = '\0'; + + chafa_term_info_unref (ti); + + g_assert_cmpstr (buf, ==, + "soft-reset" + "cursor-up-9876" + "1235-cursor-to-pos-1" + "410-fg-direct-244" + "0-bg-direct10099-" + "1199-fgbg-99,00-9" + "aix30," + "aix90," + "aix40," + "aix100," + "aix-30-40," + "aix-90-100,"); +} + +int +main (int argc, char *argv []) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/term-info/formatting", formatting_test); + + return g_test_run (); +} diff -Nru chafa-1.2.1/TODO chafa-1.12.4/TODO --- chafa-1.2.1/TODO 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/TODO 2022-11-12 01:18:35.000000000 +0000 @@ -7,37 +7,54 @@ Minor Features/UX ----------------- -- HTML output. -- Symbols: Math. -- Accept image on standard input. - Accept -o, --output to write to file. -- Verbose output. Show file names. +- Verbose output. Show file names. Call it --annotate. - Add a --test option to print a test page. - Add a --show-symbols op to print matching symbols. -- Add a --ping-pong option for animations. - Strv building API in addition to GString? - If FG color is transparent, see if we can use an inverted symbol and swap with BG. - Avoid using transparent foreground due to XFCE Terminal (other terminals?) weird handling with background picture set? - Except in FGBG modes. -- Optionally use FG colors only, no BG or inverse. - Test with more terminals. - - iTerm? - - Other OSX terminals? - PuTTY on Windows 8, 10? - Windows 7 fonts support half, solid, some borders. + - Terminology. + - Emulate tycat. + - Emacs ansi-term. + - Emacs shell (TERM=dumb). - Come up with some kind of support matrix. -- Support Uxxxx symbol specification in symbol map and UI. -- Optional double-width symbol support. - - Many geometric shapes/math symbols need this. - - Process two cells at a time and compare summed error to corresponding - sum of the two cells for double-width symbols. +- More symbols/symbol aliases: + - CP437. +- Control sequences: + - Screen buffer: CSI ?1047l (normal) and CSI ?1047h (alternate). Or use + CSI ?47l and CSI ?47h ? +- Output to retro art formats: + - RexPaint. + - lvllvl.com. + - CharPad/CharPad Pro. + - Marq's PETSCII Editor (http://www.kameli.net/marq/?page_id=2717). + - PabloDraw. + - ANSILove (direct to .ANS, other formats?). + - Others? +- More image loaders: + - BMP. The GIMP has a fairly complete decoder. + - XPM. https://en.wikipedia.org/wiki/X_PixMap + - PBM, PGM, PPM. https://en.wikipedia.org/wiki/Netpbm +- Run image decoders (and Chafa backend?) in sandboxed subprocess. +- "Auto" modes for ChafaCanvasMode and ChafaPixelMode. Major features -------------- -- Lossy/lossless intra-frame compression. +- Selection from multiple internal named font bitmap sets (IBM, C64, etc). +- Custom palettes. + - External (e.g. in GIMP format, or extract from image) or named internal. + - Using different palettes for BG and FG allows for retro modes like the + C64's Extended Color Character Mode (ECM). +- Pixmap export (raw image buffer from backend, PNG or TIFF export in frontend). +- Lossy/lossless intra-frame compression. Data rate regulated: - By desired output size. - By maximum desired per-cell error. - By total error? @@ -49,48 +66,34 @@ - Optimization: Keep a rect or region of changed area. - Multiply previous symbol's new error with weight to increase or decrease stability (prevent flicker)? -- Allow getting and setting character cells (individual cells and rects). - - Raw mode (indexed/etc) and RGB conversion mode. - Drawing context with clip rect/region, etc. - Potentially a context stack. - Getting into NCurses territory... -- Sixel support? Could be handled purely in ImageMagick, but maybe we could do - better. - Video playback. - Interactive UI (may need to be in separate tool). Optimization ------------ -- Avoid ImageMagick for remaining common formats like PNG, JPEG and directly - use more efficient decoders instead. -- Speed up alpha transparency code path. -- Speed up 240/256 color mapping for DIN99d. - Preload next image/frame in delay phase. - Don't calculate error if we're only using a single symbol (e.g. vhalf). -Cleanup -------- - -- Use a ChafaColorPair struct internally instead of an array. - The Fine Material ----------------- -- Gallery of examples. - Tips. - For scrolling, use e.g. chafa input.jpg -s 200 | less -S - Rate-controlled playback with e.g. cat input.txt | pv -qL 100k - Playback with awk + proper inter-frame delay. - X11 applications in terminal - 1) Xvfb :99 -ac -fbdir t -screen 0 1440x900x24 & - 2) DISPLAY=:99 lament -root & - 3) chafa --watch t/Xvfb_screen0 + $ Xvfb :99 -ac -fbdir t -screen 0 1440x900x24 & + $ DISPLAY=:99 lament -root & + $ chafa --watch t/Xvfb_screen0 - gnome-shell in terminal - - XDG_SESSION_TYPE=x11 DISPLAY=:99 gnome-shell + $ XDG_SESSION_TYPE=x11 DISPLAY=:99 gnome-shell - Run as different user. - Using (unreleased) ffmpeg driver: - - ./ffmpeg -i movie.mkv -pix_fmt rgba -f chafa -color 16 -symbols vhalf,space -fill ascii - - -- Better stylesheets for man page, library reference manual. - - Maybe just redo the web site with Jekyll or something. + $ ./ffmpeg -i movie.mkv -pix_fmt rgba -f chafa -color 16 -symbols vhalf,space -fill ascii - + - Compact listing of icons: + $ montage -tile 6x -background none -geometry 64x64+1+1 /usr/share/icons/Mint-X/*/16/*.png - \ + | chafa -s 80 diff -Nru chafa-1.2.1/tools/chafa/chafa.c chafa-1.12.4/tools/chafa/chafa.c --- chafa-1.2.1/tools/chafa/chafa.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/chafa.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -22,24 +22,39 @@ #include #include /* strspn, strlen, strcmp, strncmp, memset */ #include /* setlocale */ -#include /* ioctl */ +#ifdef HAVE_SYS_IOCTL_H +# include /* ioctl */ +#endif #include /* open */ #include /* stat */ #include /* open */ #include /* STDOUT_FILENO */ -#include /* sigaction */ -#include /* tcgetattr, tcsetattr */ - -#ifdef HAVE_WAND_MAGICKWAND_H -# include -#else /* HAVE_MAGICKWAND_MAGICKWAND_H */ -# include +#ifdef HAVE_SIGACTION +# include /* sigaction */ #endif +#include /* exit */ +#ifdef HAVE_TERMIOS_H +# include /* tcgetattr, tcsetattr */ +#endif + +#include #include -#include "gif-loader.h" +#include "font-loader.h" +#include "media-loader.h" #include "named-colors.h" -#include "xwd-loader.h" + +/* Include after glib.h for G_OS_WIN32 */ +#ifdef G_OS_WIN32 +# ifdef HAVE_WINDOWS_H +# include +# endif +# include +#endif + +#define ANIM_FPS_MAX 100000.0 +#define FILE_DURATION_DEFAULT 0.0 +#define SCALE_MAX 9999.0 typedef struct { @@ -47,11 +62,14 @@ gboolean show_help; gboolean show_version; + gboolean skip_processing; GList *args; ChafaCanvasMode mode; + ChafaColorExtractor color_extractor; ChafaColorSpace color_space; ChafaDitherMode dither_mode; + ChafaPixelMode pixel_mode; gint dither_grain_width; gint dither_grain_height; gdouble dither_intensity; @@ -63,29 +81,179 @@ gboolean verbose; gboolean invert; gboolean preprocess; + gboolean polite; gboolean stretch; gboolean zoom; gboolean watch; + gboolean fg_only; + gboolean animate; + gboolean center; gint width, height; + gint cell_width, cell_height; + gint margin_bottom, margin_right; + gdouble scale; gdouble font_ratio; gint work_factor; + gint optimization_level; + gint n_threads; + ChafaOptimizations optimizations; guint32 fg_color; gboolean fg_color_set; guint32 bg_color; gboolean bg_color_set; gdouble transparency_threshold; + gboolean transparency_threshold_set; gdouble file_duration_s; + + /* If > 0.0, override the framerate specified by the input file. */ + gdouble anim_fps; + + /* Applied after FPS is determined. If final value >= ANIM_FPS_MAX, + * eliminate interframe delay altogether. */ + gdouble anim_speed_multiplier; + + /* Automatically set if terminal size is detected and there is + * zero bottom margin. */ + gboolean have_parking_row; + + ChafaTermInfo *term_info; } GlobalOptions; +typedef struct +{ + gint width_cells, height_cells; + gint width_pixels, height_pixels; +} +TermSize; + static GlobalOptions options; -static gboolean interrupted_by_user; +static TermSize detected_term_size; +static gboolean using_detected_size = FALSE; +static volatile sig_atomic_t interrupted_by_user = FALSE; + +#ifdef HAVE_TERMIOS_H +static struct termios saved_termios; +#endif + +#ifdef G_OS_WIN32 +static UINT saved_console_output_cp; +static UINT saved_console_input_cp; +#endif +#ifdef HAVE_SIGACTION static void sigint_handler (G_GNUC_UNUSED int sig) { interrupted_by_user = TRUE; } +#endif + +static void +interruptible_usleep (gint us) +{ + while (us > 0 && !interrupted_by_user) + { + gint sleep_us = MIN (us, 50000); + g_usleep (sleep_us); + us -= sleep_us; + } +} + +#ifdef G_OS_WIN32 + +/* We must determine if stdout is redirected to a file, and if so, use a + * different set of I/O functions. */ +static gboolean win32_stdout_is_file = FALSE; + +static gboolean +safe_WriteConsoleA (HANDLE chd, const gchar *data, gsize len) +{ + gsize total_written = 0; + + if (chd == INVALID_HANDLE_VALUE) + return FALSE; + + while (total_written < len) + { + DWORD n_written = 0; + + if (win32_stdout_is_file) + { + /* WriteFile() and fwrite() seem to work equally well despite various + * claims that the former does poorly in a UTF-8 environment. The + * resulting files look good in my tests, but note that catting them + * out with 'type' introduces lots of artefacts. */ +#if 0 + if (!WriteFile (chd, data, len - total_written, &n_written, NULL)) + return FALSE; +#else + if ((n_written = fwrite (data, 1, len - total_written, stdout)) < 1) + return FALSE; +#endif + } + else + { + if (!WriteConsoleA (chd, data, len - total_written, &n_written, NULL)) + return FALSE; + } + + data += n_written; + total_written += n_written; + } + + return TRUE; +} + +#endif + +static gboolean +write_to_stdout (gconstpointer buf, gsize len) +{ + if (len == 0) + return TRUE; + +#ifdef G_OS_WIN32 + { + const gchar *p0, *p1, *end; + gsize total_written = 0; + + /* In order for UTF-8 to be handled correctly, we need to use WriteConsoleA() + * on MS Windows. We also convert line feeds to DOS-style CRLF as we go. */ + + /* TODO: Check if it's a console handle, and if not, use WriteFile instead */ + + HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); + + for (p0 = buf, end = p0 + len; + chd != INVALID_HANDLE_VALUE && total_written < len; + p0 = p1) + { + p1 = memchr (p0, '\n', end - p0); + if (!p1) + p1 = end; + + if (!safe_WriteConsoleA (chd, p0, p1 - p0)) + break; + + total_written += p1 - p0; + + if (p1 != end) + { + if (!safe_WriteConsoleA (chd, "\r\n", 2)) + break; + + p1++; + total_written += 1; + } + } + + return total_written == len ? TRUE : FALSE; + } +#else + return fwrite (buf, 1, len, stdout) == len ? TRUE : FALSE; +#endif +} static guchar get_hex_byte (const gchar *str) @@ -116,6 +284,20 @@ return b; } +static gint +count_dash_strings (GList *l) +{ + gint n = 0; + + for ( ; l; l = g_list_next (l)) + { + if (!strcmp (l->data, "-")) + n++; + } + + return n; +} + static gboolean parse_color (const gchar *str, guint32 *col_out, GError **error) { @@ -172,8 +354,9 @@ } static const gchar copyright_notice [] = - "Copyright (C) 2018 Hans Petter Jansson et al.\n" - "Incl. libnsgif copyright (C) 2004 Richard Wilson, copyright (C) 2008 Sean Fox\n\n" + "Copyright (C) 2018-2022 Hans Petter Jansson et al.\n" + "Incl. libnsgif copyright (C) 2004 Richard Wilson, copyright (C) 2008 Sean Fox\n" + "Incl. LodePNG copyright (C) 2005-2018 Lode Vandevenne\n\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"; @@ -182,22 +365,36 @@ { gchar *builtin_features_str = chafa_describe_features (chafa_get_builtin_features ()); gchar *supported_features_str = chafa_describe_features (chafa_get_supported_features ()); + gchar **loaders = get_loader_names (); + gchar *loaders_joined = g_strjoinv (" ", loaders); - g_printerr ("Chafa version %s\n\nFeatures: %s\nApplying: %s\n\n%s\n", - CHAFA_VERSION, - chafa_get_builtin_features () ? builtin_features_str : "none", - chafa_get_supported_features () ? supported_features_str : "none", - copyright_notice); + g_print ("Chafa version %s\n\nLoaders: %s\nFeatures: %s\nApplying: %s\n\n%s\n", + CHAFA_VERSION, + loaders_joined, + chafa_get_builtin_features () ? builtin_features_str : "none", + chafa_get_supported_features () ? supported_features_str : "none", + copyright_notice); g_free (builtin_features_str); g_free (supported_features_str); + g_strfreev (loaders); + g_free (loaders_joined); +} + +static void +print_brief_summary (void) +{ + g_printerr ("%s: You must specify input files as arguments or pipe a file to stdin.\n" + "Try '%s --help' for more information.\n", + options.executable_name, + options.executable_name); } static void print_summary (void) { const gchar *summary = - " Chafa (Character Art Facsimile) image-to-text converter.\n\n" + " Chafa (Character Art Facsimile) terminal graphics and character art generator.\n\n" "Options:\n" @@ -205,10 +402,15 @@ " --version Show version.\n" " -v, --verbose Be verbose.\n\n" + " --animate=BOOL Whether to allow animation [on, off]. Defaults to on.\n" + " When off, will show a still frame from each animation.\n" " --bg=COLOR Background color of display (color name or hex).\n" + " -C, --center=BOOL Center images [on, off]. Defaults to off.\n" " --clear Clear screen before processing each file.\n" - " -c, --colors=MODE Set output color mode; one of [none, 2, 16, 240, 256,\n" - " full]. Defaults to full (24-bit).\n" + " -c, --colors=MODE Set output color mode; one of [none, 2, 8, 16/8, 16, 240,\n" + " 256, full]. Defaults to best guess.\n" + " --color-extractor=EXTR Method for extracting color from an area\n" + " [average, median]. Average is the default.\n" " --color-space=CS Color space used for quantization; one of [rgb, din99d].\n" " Defaults to rgb, which is faster but less accurate.\n" " --dither=DITHER Set output dither mode; one of [none, ordered,\n" @@ -219,42 +421,73 @@ " Defaults to 1.0.\n" " -d, --duration=SECONDS The time to show each file. If showing a single file,\n" " defaults to zero for a still image and infinite for an\n" - " animation. For multiple files, defaults to 3.0. Animations\n" + " animation. For multiple files, defaults to zero. Animations\n" " will always be played through at least once.\n" " --fg=COLOR Foreground color of display (color name or hex).\n" + " --fg-only Leave the background color untouched. This produces\n" + " character-cell output using foreground colors only.\n" " --fill=SYMS Specify character symbols to use for fill/gradients.\n" " Defaults to none. See below for full usage.\n" " --font-ratio=W/H Target font's width/height ratio. Can be specified as\n" " a real number or a fraction. Defaults to 1/2.\n" + " -f, --format=FORMAT Set output format; one of [iterm, kitty, sixels,\n" + " symbols]. Iterm, kitty and sixels yield much higher\n" + " quality but enjoy limited support. Symbols mode yields\n" + " beautiful character art.\n" + " --glyph-file=FILE Load glyph information from FILE, which can be any\n" + " font file supported by FreeType (TTF, PCF, etc).\n" " --invert Invert video. For display with bright backgrounds in\n" " color modes 2 and none. Swaps --fg and --bg.\n" + " --margin-bottom=NUM When terminal size is detected, reserve at least NUM\n" + " rows at the bottom as a safety margin. Can be used to\n" + " prevent images from scrolling out. Defaults to 1.\n" + " --margin-right=NUM When terminal size is detected, reserve at least NUM\n" + " columns on the right-hand side as a safety margin. Defaults\n" + " to 0.\n" + " -O, --optimize=NUM Compress the output by using control sequences\n" + " intelligently [0-9]. 0 disables, 9 enables every\n" + " available optimization. Defaults to 5, except for when\n" + " used with \"-c none\", where it defaults to 0.\n" + " --polite=BOOL Polite mode [on, off]. Defaults to on. Turning this off\n" + " may enhance presentation and prevent interference from\n" + " other programs, but risks leaving the terminal in an\n" + " altered state (rude).\n" " -p, --preprocess=BOOL Image preprocessing [on, off]. Defaults to on with 16\n" " colors or lower, off otherwise.\n" + " --scale=NUM Scale image, respecting terminal's maximum dimensions. 1.0\n" + " approximates original pixel dimensions. Specify \"max\" to\n" + " use all available space. Defaults to 1.0 for pixel graphics\n" + " and 4.0 for symbols.\n" " -s, --size=WxH Set maximum output dimensions in columns and rows. By\n" " default this will be the size of your terminal, or 80x25\n" " if size detection fails.\n" + " --speed=SPEED Set the speed animations will play at. This can be\n" + " either a unitless multiplier, or a real number followed\n" + " by \"fps\" to apply a specific framerate.\n" " --stretch Stretch image to fit output dimensions; ignore aspect.\n" - " Implies --zoom.\n" + " Implies --scale max.\n" " --symbols=SYMS Specify character symbols to employ in final output.\n" " See below for full usage and a list of symbol classes.\n" + " --threads=NUM Maximum number of CPU threads to use. If left unspecified\n" + " or negative, this will equal available CPU cores.\n" " -t, --threshold=NUM Threshold above which full transparency will be used\n" " [0.0 - 1.0].\n" " --watch Watch a single input file, redisplaying it whenever its\n" " contents change. Will run until manually interrupted\n" " or, if --duration is set, until it expires.\n" " -w, --work=NUM How hard to work in terms of CPU and memory [1-9]. 1 is the\n" - " cheapest, 9 is the most accurate. Defaults to 5.\n" - " --zoom Allow scaling up beyond one character per pixel.\n\n" + " cheapest, 9 is the most accurate. Defaults to 5.\n\n" " Accepted classes for --symbols and --fill are [all, none, space, solid,\n" " stipple, block, border, diagonal, dot, quad, half, hhalf, vhalf, inverted,\n" - " braille, technical, geometric, ascii, extra]. Some symbols belong to multiple\n" - " classes, e.g. diagonals are also borders. You can specify a list of classes\n" - " separated by commas, or prefix them with + and - to add or remove symbols\n" - " relative to the existing set. The ordering is significant.\n\n" + " braille, technical, geometric, ascii, legacy, sextant, wedge, wide, narrow,\n" + " extra]. Some symbols belong to multiple classes, e.g. diagonals are also\n" + " borders. You can specify a list of classes separated by commas, or prefix them\n" + " with + and - to add or remove symbols relative to the existing set. The\n" + " ordering is significant.\n\n" - " The default symbol set is all-stipple-braille-ascii+space-extra-inverted for\n" - " all modes except \"none\", which uses all-stipple-braille-ascii+space-extra.\n\n" + " The default symbol set is block+border+space-wide-inverted for all modes\n" + " except \"none\", which uses block+border+space-wide (including inverse symbols).\n\n" "Examples:\n" @@ -266,9 +499,9 @@ " solid block symbol:\n\n" " $ chafa -c none --symbols block+border-solid in.png\n"; - g_printerr ("Usage:\n %s [OPTION...] [FILE...]\n\n%s\n", - options.executable_name, - summary); + g_print ("Usage:\n %s [OPTION...] [FILE...]\n\n%s\n", + options.executable_name, + summary); } static gboolean @@ -280,6 +513,11 @@ options.mode = CHAFA_CANVAS_MODE_FGBG; else if (!g_ascii_strcasecmp (value, "2")) options.mode = CHAFA_CANVAS_MODE_FGBG_BGFG; + else if (!g_ascii_strcasecmp (value, "8")) + options.mode = CHAFA_CANVAS_MODE_INDEXED_8; + else if (!g_ascii_strcasecmp (value, "16-8") + || !g_ascii_strcasecmp (value, "16/8")) + options.mode = CHAFA_CANVAS_MODE_INDEXED_16_8; else if (!g_ascii_strcasecmp (value, "16")) options.mode = CHAFA_CANVAS_MODE_INDEXED_16; else if (!g_ascii_strcasecmp (value, "240")) @@ -289,12 +527,33 @@ else if (!g_ascii_strcasecmp (value, "full") || !g_ascii_strcasecmp (value, "rgb") || !g_ascii_strcasecmp (value, "tc") + || !g_ascii_strcasecmp (value, "direct") + || !g_ascii_strcasecmp (value, "directcolor") || !g_ascii_strcasecmp (value, "truecolor")) options.mode = CHAFA_CANVAS_MODE_TRUECOLOR; else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Colors must be one of [none, 2, 16, 240, 256, full]."); + "Colors must be one of [none, 2, 8, 16/8, 16, 240, 256, full]."); + result = FALSE; + } + + return result; +} + +static gboolean +parse_color_extractor_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result = TRUE; + + if (!g_ascii_strcasecmp (value, "average")) + options.color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; + else if (!g_ascii_strcasecmp (value, "median")) + options.color_extractor = CHAFA_COLOR_EXTRACTOR_MEDIAN; + else + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Color extractor must be one of [average, median]."); result = FALSE; } @@ -343,21 +602,22 @@ return result; } +/* FIXME: Make it parse negative values too, and use sscanf() return values properly */ static gboolean -parse_font_ratio_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +parse_fraction_or_real (const gchar *str, gdouble *real_out) { - gboolean result = TRUE; + gboolean success = FALSE; gdouble ratio = -1.0; gint width = -1, height = -1; gint o = 0; - sscanf (value, "%d/%d%n", &width, &height, &o); + sscanf (str, "%d/%d%n", &width, &height, &o); if (width < 0 || height < 0) - sscanf (value, "%d:%d%n", &width, &height, &o); + sscanf (str, "%d:%d%n", &width, &height, &o); if (width < 0 || height < 0) - sscanf (value, "%lf%n", &ratio, &o); + sscanf (str, "%lf%n", &ratio, &o); - if (o != 0 && value [o] != '\0') + if (o != 0 && str [o] != '\0') { width = -1; height = -1; @@ -368,19 +628,58 @@ if (width > 0 && height > 0) ratio = width / (gdouble) height; + if (ratio < 0.0) + goto out; + + *real_out = ratio; + success = TRUE; + out: - if (ratio <= 0.0) + return success; +} + +static gboolean +parse_font_ratio_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gdouble ratio = -1.0; + gboolean success = FALSE; + + if (!parse_fraction_or_real (value, &ratio) || ratio <= 0.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Font ratio must be specified as a real number or fraction."); - result = FALSE; + "Font ratio must be specified as a positive real number or fraction."); + goto out; } - else + + options.font_ratio = ratio; + success = TRUE; + +out: + return success; +} + +static gboolean +parse_scale_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gdouble scale = -1.0; + gboolean success = FALSE; + + if (!strcasecmp (value, "max") || !strcasecmp (value, "fill")) + { + scale = SCALE_MAX; + } + else if (!parse_fraction_or_real (value, &scale) || scale <= 0.0) { - options.font_ratio = ratio; + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Scale must be specified as a positive real number or fraction."); + goto out; } - return result; + options.scale = scale; + success = TRUE; + +out: + return success; } static gboolean @@ -397,6 +696,44 @@ } static gboolean +parse_format_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result = TRUE; + ChafaPixelMode pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; + + if (!strcasecmp (value, "symbol") || !strcasecmp (value, "symbols") + || !strcasecmp (value, "ansi")) + { + pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; + } + else if (!strcasecmp (value, "sixel") || !strcasecmp (value, "sixels")) + { + pixel_mode = CHAFA_PIXEL_MODE_SIXELS; + } + else if (!strcasecmp (value, "kitty")) + { + pixel_mode = CHAFA_PIXEL_MODE_KITTY; + } + else if (!strcasecmp (value, "iterm") + || !strcasecmp (value, "iterm2")) + { + pixel_mode = CHAFA_PIXEL_MODE_ITERM2; + } + else + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Output format given as '%s'. Must be one of [iterm, kitty, sixels, symbols].", + value); + result = FALSE; + } + + if (result) + options.pixel_mode = pixel_mode; + + return result; +} + +static gboolean parse_size_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; @@ -485,213 +822,575 @@ } static gboolean -parse_preprocess_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +parse_glyph_file_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { - gboolean result = TRUE; + gboolean result = FALSE; + FileMapping *file_mapping; + FontLoader *font_loader; + gunichar c; + gpointer c_bitmap; + gint width, height; - if (!g_ascii_strcasecmp (value, "on") - || !g_ascii_strcasecmp (value, "yes")) - { - options.preprocess = TRUE; - } - else if (!g_ascii_strcasecmp (value, "off") - || !g_ascii_strcasecmp (value, "no")) - { - options.preprocess = FALSE; - } - else + file_mapping = file_mapping_new (value); + if (!file_mapping) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Preprocessing must be one of [on, off]."); - result = FALSE; + "Unable to open glyph file '%s'.", value); + goto out; } - return result; -} - -static gboolean -parse_color_str (const gchar *value, guint32 *col_out, const gchar *error_message, GError **error) -{ - const NamedColor *named_color; - guint32 col = 0x000000; - gboolean result = TRUE; + font_loader = font_loader_new_from_mapping (file_mapping); + file_mapping = NULL; /* Font loader owns it now */ - named_color = find_color_by_name (value); - if (named_color) - { - col = (named_color->color [0] << 16) - | (named_color->color [1] << 8) - | (named_color->color [2]); - } - else if (!parse_color (value, &col, NULL)) + if (!font_loader) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - error_message, value); - result = FALSE; + "Unable to load glyph file '%s'.", value); + goto out; } - if (result) - *col_out = col; + while (font_loader_get_next_glyph (font_loader, &c, &c_bitmap, &width, &height)) + { + chafa_symbol_map_add_glyph (options.symbol_map, c, + CHAFA_PIXEL_RGBA8_PREMULTIPLIED, c_bitmap, + width, height, width * 4); + chafa_symbol_map_add_glyph (options.fill_symbol_map, c, + CHAFA_PIXEL_RGBA8_PREMULTIPLIED, c_bitmap, + width, height, width * 4); + g_free (c_bitmap); + } - return result; -} + font_loader_destroy (font_loader); -static gboolean -parse_fg_color_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) -{ - options.fg_color_set = parse_color_str (value, &options.fg_color, "Unrecognized foreground color '%s'.", error); - return options.fg_color_set; -} + result = TRUE; -static gboolean -parse_bg_color_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) -{ - options.bg_color_set = parse_color_str (value, &options.bg_color, "Unrecognized background color '%s'.", error); - return options.bg_color_set; +out: + if (file_mapping) + file_mapping_destroy (file_mapping); + return result; } static void -get_tty_size (void) +dump_glyph (gunichar c, const guint8 *pix, gint width, gint height, gint rowstride) { - struct winsize w; - - if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) < 0) - return; - - if (w.ws_col > 0) - options.width = w.ws_col; + gint x, y; + const guint8 *p; + gchar buf [16]; + gint len; - /* We subtract one line for the user's prompt */ - if (w.ws_row > 2) - options.height = w.ws_row - 1; + len = g_unichar_to_utf8 (c, buf); + g_assert (len >= 1 && len < 16); + buf [len] = '\0'; + fprintf (stdout, + " {\n" + " /* [%s] */\n" + " CHAFA_SYMBOL_TAG_,\n" + " 0x%x,\n" + " CHAFA_SYMBOL_OUTLINE_%s (", + buf, + c, + (width == 8 && height == 8) ? "8X8" : + (width == 16 && height == 8) ? "16X8" : + "STRANGE_SIZE"); - /* If .ws_xpixel and .ws_ypixel were filled out, we could in theory - * calculate aspect information for the font used. Unfortunately, - * I have yet to find an environment where these fields get set to - * anything other than zero. */ -} + for (y = 0; y < height; y++) + { + p = pix + y * rowstride; -static struct termios saved_termios; + fputs ("\n \"", stdout); -static void -tty_options_init (void) -{ - struct termios t; + for (x = 0; x < width; x++) + { + fputc (*p < 0x80 ? ' ' : 'X' , stdout); + p += 4; + } - if (!options.is_interactive) - return; + fputs ("\"", stdout); + } - tcgetattr (STDIN_FILENO, &saved_termios); - t = saved_termios; - t.c_lflag &= ~ECHO; - tcsetattr (STDIN_FILENO, TCSANOW, &t); + fputs (")\n },\n", stdout); } -static void -tty_options_deinit (void) +/* Undocumented functionality; dumps a font file so the glyphs can be tweaked and turned + * into builtins. */ +static gboolean +parse_dump_glyph_file_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { - if (!options.is_interactive) - return; + gboolean result = FALSE; + FileMapping *file_mapping; + FontLoader *font_loader; + ChafaSymbolMap *temp_map = NULL; + gunichar c; + gpointer c_bitmap; + gint width, height, rowstride; - tcsetattr (STDIN_FILENO, TCSANOW, &saved_termios); -} + options.skip_processing = TRUE; -/* I really would've preferred to use termcap, but termcap contents - * and the TERM env var are often unreliable/unrepresentative, so - * instead we have this. - * - * If you're getting poor results, I'd love to hear about it so it can - * be improved. */ -static ChafaCanvasMode -detect_canvas_mode (void) -{ - const gchar *term; - const gchar *colorterm; - const gchar *vte_version; - const gchar *tmux; - ChafaCanvasMode mode = CHAFA_CANVAS_MODE_INDEXED_240; - - term = g_getenv ("TERM"); - if (!term) term = ""; - - colorterm = g_getenv ("COLORTERM"); - if (!colorterm) colorterm = ""; - - vte_version = g_getenv ("VTE_VERSION"); - if (!vte_version) vte_version = ""; - - tmux = g_getenv ("TMUX"); - if (!tmux) tmux = ""; - - /* Some terminals set COLORTERM=truecolor. However, this env var can - * make its way into environments where truecolor is not desired - * (e.g. screen sessions), so check it early on and override it later. */ - if (!g_ascii_strcasecmp (colorterm, "truecolor") - || !g_ascii_strcasecmp (colorterm, "gnome-terminal") - || !g_ascii_strcasecmp (colorterm, "xfce-terminal")) - mode = CHAFA_CANVAS_MODE_TRUECOLOR; + file_mapping = file_mapping_new (value); + if (!file_mapping) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Unable to open glyph file '%s'.", value); + goto out; + } - /* In a modern VTE we can rely on VTE_VERSION. It's a great terminal emulator - * which supports truecolor. */ - if (strlen (vte_version) > 0) - mode = CHAFA_CANVAS_MODE_TRUECOLOR; + font_loader = font_loader_new_from_mapping (file_mapping); + file_mapping = NULL; /* Font loader owns it now */ - /* Terminals that advertise 256 colors usually support truecolor too, - * (VTE, xterm) although some (xterm) may quantize to an indexed palette - * regardless. */ - if (!strcmp (term, "xterm-256color") - || !strcmp (term, "xterm-kitty")) - mode = CHAFA_CANVAS_MODE_TRUECOLOR; + if (!font_loader) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Unable to load glyph file '%s'.", value); + goto out; + } - /* mlterm's truecolor support seems to be broken; it looks like a color - * allocation issue. */ - if (!strcmp (term, "mlterm")) - mode = CHAFA_CANVAS_MODE_INDEXED_240; + temp_map = chafa_symbol_map_new (); - /* rxvt 256-color really is 256 colors only */ - if (!strcmp (term, "rxvt-unicode-256color")) - mode = CHAFA_CANVAS_MODE_INDEXED_240; + while (font_loader_get_next_glyph (font_loader, &c, &c_bitmap, &width, &height)) + { + gpointer pix; - /* Regular rxvt supports 16 colors at most */ - if (!strcmp (term, "rxvt-unicode")) - mode = CHAFA_CANVAS_MODE_INDEXED_16; + /* By adding and then querying the glyph, we get the benefit of Chafa's + * internal scaling and postprocessing. */ - /* 'screen' does not like truecolor at all, but 256 colors works fine. - * Sometimes we'll see the outer terminal appended to the TERM string, - * like so: screen.xterm-256color */ - if (!strncmp (term, "screen", 6)) - { - mode = CHAFA_CANVAS_MODE_INDEXED_240; + chafa_symbol_map_add_glyph (temp_map, c, + CHAFA_PIXEL_RGBA8_PREMULTIPLIED, c_bitmap, + width, height, width * 4); + g_free (c_bitmap); - /* 'tmux' also sets TERM=screen, but it supports truecolor codes. - * You may have to add the following to .tmux.conf to prevent - * remapping to 256 colors: - * - * tmux set-option -ga terminal-overrides ",screen-256color:Tc" */ - if (strlen (tmux) > 0) - mode = CHAFA_CANVAS_MODE_TRUECOLOR; + chafa_symbol_map_get_glyph (temp_map, c, + CHAFA_PIXEL_RGBA8_PREMULTIPLIED, &pix, + &width, &height, &rowstride); + dump_glyph (c, pix, width, height, rowstride); + g_free (pix); } - /* If TERM is "linux", we're probably on the Linux console, which supports - * 16 colors only. It also sets COLORTERM=1. - * - * https://github.com/torvalds/linux/commit/cec5b2a97a11ade56a701e83044d0a2a984c67b4 - * - * In theory we could emit truecolor codes and let the console remap, - * but we get better results if we do the conversion ourselves, since we - * can apply preprocessing and exotic color spaces. */ - if (!strcmp (term, "linux")) - mode = CHAFA_CANVAS_MODE_INDEXED_16; + font_loader_destroy (font_loader); - return mode; -} + result = TRUE; + +out: + if (file_mapping) + file_mapping_destroy (file_mapping); + if (temp_map) + chafa_symbol_map_unref (temp_map); + return result; +} + +static gboolean +parse_boolean_token (const gchar *token, gboolean *value_out) +{ + gboolean success = FALSE; + + if (!g_ascii_strcasecmp (token, "on") + || !g_ascii_strcasecmp (token, "yes") + || !g_ascii_strcasecmp (token, "true")) + { + *value_out = TRUE; + } + else if (!g_ascii_strcasecmp (token, "off") + || !g_ascii_strcasecmp (token, "no") + || !g_ascii_strcasecmp (token, "false")) + { + *value_out = FALSE; + } + else + { + goto out; + } + + success = TRUE; + +out: + return success; +} + +static gboolean +parse_animate_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result; + + result = parse_boolean_token (value, &options.animate); + if (!result) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Animate mode must be one of [on, off]."); + + return result; +} + +static gboolean +parse_center_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result; + + result = parse_boolean_token (value, &options.center); + if (!result) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Centering mode must be one of [on, off]."); + + return result; +} + +static gboolean +parse_preprocess_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result; + + result = parse_boolean_token (value, &options.preprocess); + if (!result) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Preprocessing must be one of [on, off]."); + + return result; +} + +static gboolean +parse_polite_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result; + + result = parse_boolean_token (value, &options.polite); + if (!result) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Polite mode must be one of [on, off]."); + + return result; +} + +static gboolean +parse_anim_speed_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + gboolean result = FALSE; + gdouble d; + + if (!g_ascii_strcasecmp (value, "max") + || !g_ascii_strcasecmp (value, "maximum")) + { + options.anim_fps = G_MAXDOUBLE; + result = TRUE; + } + else + { + gchar *endptr; + + d = g_strtod (value, &endptr); + if (endptr == value || d <= 0.0) + goto out; + + while (g_ascii_isspace (*endptr)) + endptr++; + + if (!g_ascii_strcasecmp (endptr, "fps")) + { + options.anim_fps = d; + result = TRUE; + } + else if (*endptr == '\0') + { + options.anim_speed_multiplier = d; + result = TRUE; + } + + /* If there's unknown junk at the end, fail. */ + } + +out: + if (!result) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Animation speed must be either \"max\", a real multiplier, or a real framerate followed by \"fps\". It must be greater than zero."); + + return result; +} + +static gboolean +parse_color_str (const gchar *value, guint32 *col_out, const gchar *error_message, GError **error) +{ + const NamedColor *named_color; + guint32 col = 0x000000; + gboolean result = TRUE; + + named_color = find_color_by_name (value); + if (named_color) + { + col = (named_color->color [0] << 16) + | (named_color->color [1] << 8) + | (named_color->color [2]); + } + else if (!parse_color (value, &col, NULL)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + error_message, value); + result = FALSE; + } + + if (result) + *col_out = col; + + return result; +} + +static gboolean +parse_fg_color_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + options.fg_color_set = parse_color_str (value, &options.fg_color, "Unrecognized foreground color '%s'.", error); + return options.fg_color_set; +} + +static gboolean +parse_bg_color_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) +{ + options.bg_color_set = parse_color_str (value, &options.bg_color, "Unrecognized background color '%s'.", error); + return options.bg_color_set; +} + +static void +get_tty_size (TermSize *term_size_out) +{ + TermSize term_size; + + term_size.width_cells + = term_size.height_cells + = term_size.width_pixels + = term_size.height_pixels + = -1; + +#ifdef G_OS_WIN32 + { + HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csb_info; + + if (chd != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo (chd, &csb_info)) + { + term_size.width_cells = csb_info.srWindow.Right - csb_info.srWindow.Left + 1; + term_size.height_cells = csb_info.srWindow.Bottom - csb_info.srWindow.Top + 1; + } + } +#elif defined(HAVE_SYS_IOCTL_H) + { + struct winsize w; + gboolean have_winsz = FALSE; + + /* FIXME: Use tcgetwinsize() when it becomes more widely available. + * See: https://www.austingroupbugs.net/view.php?id=1151#c3856 */ + + if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) >= 0 + || ioctl (STDERR_FILENO, TIOCGWINSZ, &w) >= 0 + || ioctl (STDIN_FILENO, TIOCGWINSZ, &w) >= 0) + have_winsz = TRUE; + +# ifdef HAVE_CTERMID + if (!have_winsz) + { + const gchar *term_path; + gint fd = -1; + + term_path = ctermid (NULL); + if (term_path) + fd = g_open (term_path, O_RDONLY); + + if (fd >= 0) + { + if (ioctl (fd, TIOCGWINSZ, &w) >= 0) + have_winsz = TRUE; + + g_close (fd, NULL); + } + } +# endif /* HAVE_CTERMID */ + + if (have_winsz) + { + term_size.width_cells = w.ws_col; + term_size.height_cells = w.ws_row; + term_size.width_pixels = w.ws_xpixel; + term_size.height_pixels = w.ws_ypixel; + } + } +#endif /* HAVE_SYS_IOCTL_H */ + + if (term_size.width_cells <= 0) + term_size.width_cells = -1; + if (term_size.height_cells <= 2) + term_size.height_cells = -1; + + /* If .ws_xpixel and .ws_ypixel are filled out, we can calculate + * aspect information for the font used. Sixel-capable terminals + * like mlterm set these fields, but most others do not. */ + + if (term_size.width_pixels >= 32768 || term_size.height_pixels >= 32768) + { + /* https://github.com/hpjansson/chafa/issues/62 */ + + g_printerr ("%s: Terminal reports strange pixel dimensions of %dx%d.\n" + "%s: Disregarding so as to avoid unreasonably large allocation.\n" + "%s: This is sometimes caused by older versions of the 'fish' shell.\n" + "%s: See https://github.com/hpjansson/chafa/issues/62 for details.\n", + options.executable_name, + (gint) term_size.width_pixels, + (gint) term_size.height_pixels, + options.executable_name, + options.executable_name, + options.executable_name); + + term_size.width_pixels = -1; + term_size.height_pixels = -1; + } + else if (term_size.width_pixels <= 0 || term_size.height_pixels <= 0) + { + term_size.width_pixels = -1; + term_size.height_pixels = -1; + } + + *term_size_out = term_size; +} + +static void +tty_options_init (void) +{ +#ifdef G_OS_WIN32 + { + HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); + + saved_console_output_cp = GetConsoleOutputCP (); + saved_console_input_cp = GetConsoleCP (); + + /* Enable ANSI escape sequence parsing etc. on MS Windows command prompt */ + if (chd != INVALID_HANDLE_VALUE) + { + if (!SetConsoleMode (chd, + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING + | DISABLE_NEWLINE_AUTO_RETURN)) + win32_stdout_is_file = TRUE; + } + + /* Set UTF-8 code page output */ + SetConsoleOutputCP (65001); + + /* Set UTF-8 code page input, for good measure */ + SetConsoleCP (65001); + } +#endif + + if (!options.polite) + { + gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX]; + gchar *p0; + +#ifdef HAVE_TERMIOS_H + if (options.is_interactive) + { + struct termios t; + + tcgetattr (STDIN_FILENO, &saved_termios); + t = saved_termios; + t.c_lflag &= ~ECHO; + tcsetattr (STDIN_FILENO, TCSANOW, &t); + } +#endif + + if (options.mode != CHAFA_CANVAS_MODE_FGBG) + { + p0 = chafa_term_info_emit_disable_cursor (options.term_info, buf); + write_to_stdout (buf, p0 - buf); + } + + /* Most terminals should have sixel scrolling enabled by default, so we're + * not going to disable it again later. */ + if (options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) + { + p0 = chafa_term_info_emit_enable_sixel_scrolling (options.term_info, buf); + write_to_stdout (buf, p0 - buf); + } + } +} + +static void +tty_options_deinit (void) +{ +#ifdef G_OS_WIN32 + SetConsoleOutputCP (saved_console_output_cp); + SetConsoleCP (saved_console_input_cp); +#endif + + if (!options.polite) + { + if (options.mode != CHAFA_CANVAS_MODE_FGBG) + { + gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX]; + gchar *p0; + + p0 = chafa_term_info_emit_enable_cursor (options.term_info, buf); + write_to_stdout (buf, p0 - buf); + } + +#ifdef HAVE_TERMIOS_H + if (options.is_interactive) + { + tcsetattr (STDIN_FILENO, TCSANOW, &saved_termios); + } +#endif + } +} + +static void +detect_terminal (ChafaTermInfo **term_info_out, ChafaCanvasMode *mode_out, ChafaPixelMode *pixel_mode_out) +{ + ChafaCanvasMode mode; + ChafaPixelMode pixel_mode; + ChafaTermInfo *term_info; + ChafaTermInfo *fallback_info; + gchar **envp; + + envp = g_get_environ (); + term_info = chafa_term_db_detect (chafa_term_db_get_default (), envp); + + if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT)) + mode = CHAFA_CANVAS_MODE_TRUECOLOR; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_256) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_256)) + mode = CHAFA_CANVAS_MODE_INDEXED_240; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_16) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_16)) + mode = CHAFA_CANVAS_MODE_INDEXED_16; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_RESET_ATTRIBUTES)) + mode = CHAFA_CANVAS_MODE_FGBG_BGFG; + else + mode = CHAFA_CANVAS_MODE_FGBG; + + if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1)) + pixel_mode = CHAFA_PIXEL_MODE_KITTY; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS) + && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_END_SIXELS)) + pixel_mode = CHAFA_PIXEL_MODE_SIXELS; + else + pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; + + /* Make sure we have fallback sequences in case the user forces + * a mode that's technically unsupported by the terminal. */ + fallback_info = chafa_term_db_get_fallback_info (chafa_term_db_get_default ()); + chafa_term_info_supplement (term_info, fallback_info); + chafa_term_info_unref (fallback_info); + + *term_info_out = term_info; + *mode_out = mode; + *pixel_mode_out = pixel_mode; + + g_strfreev (envp); +} static gboolean parse_options (int *argc, char **argv []) { GError *error = NULL; GOptionContext *context; - gboolean result = TRUE; + gboolean result = FALSE; const GOptionEntry option_entries [] = { /* Note: The descriptive blurbs here are never shown to the user */ @@ -699,28 +1398,45 @@ { "help", 'h', 0, G_OPTION_ARG_NONE, &options.show_help, "Show help", NULL }, { "version", '\0', 0, G_OPTION_ARG_NONE, &options.show_version, "Show version", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options.verbose, "Be verbose", NULL }, + { "animate", '\0', 0, G_OPTION_ARG_CALLBACK, parse_animate_arg, "Animate", NULL }, { "bg", '\0', 0, G_OPTION_ARG_CALLBACK, parse_bg_color_arg, "Background color of display", NULL }, + { "center", 'C', 0, G_OPTION_ARG_CALLBACK, parse_center_arg, "Center", NULL }, { "clear", '\0', 0, G_OPTION_ARG_NONE, &options.clear, "Clear", NULL }, { "colors", 'c', 0, G_OPTION_ARG_CALLBACK, parse_colors_arg, "Colors (none, 2, 16, 256, 240 or full)", NULL }, + { "color-extractor", '\0', 0, G_OPTION_ARG_CALLBACK, parse_color_extractor_arg, "Color extractor (average or median)", NULL }, { "color-space", '\0', 0, G_OPTION_ARG_CALLBACK, parse_color_space_arg, "Color space (rgb or din99d)", NULL }, { "dither", '\0', 0, G_OPTION_ARG_CALLBACK, parse_dither_arg, "Dither", NULL }, { "dither-grain",'\0', 0, G_OPTION_ARG_CALLBACK, parse_dither_grain_arg, "Dither grain", NULL }, { "dither-intensity", '\0', 0, G_OPTION_ARG_DOUBLE, &options.dither_intensity, "Dither intensity", NULL }, + { "dump-glyph-file", '\0', 0, G_OPTION_ARG_CALLBACK, parse_dump_glyph_file_arg, "Dump glyph file", NULL }, { "duration", 'd', 0, G_OPTION_ARG_DOUBLE, &options.file_duration_s, "Duration", NULL }, { "fg", '\0', 0, G_OPTION_ARG_CALLBACK, parse_fg_color_arg, "Foreground color of display", NULL }, + { "fg-only", '\0', 0, G_OPTION_ARG_NONE, &options.fg_only, "Foreground only", NULL }, { "fill", '\0', 0, G_OPTION_ARG_CALLBACK, parse_fill_arg, "Fill symbols", NULL }, + { "format", 'f', 0, G_OPTION_ARG_CALLBACK, parse_format_arg, "Format of output pixel data (iterm, kitty, sixels or symbols)", NULL }, { "font-ratio", '\0', 0, G_OPTION_ARG_CALLBACK, parse_font_ratio_arg, "Font ratio", NULL }, + { "glyph-file", '\0', 0, G_OPTION_ARG_CALLBACK, parse_glyph_file_arg, "Glyph file", NULL }, { "invert", '\0', 0, G_OPTION_ARG_NONE, &options.invert, "Invert foreground/background", NULL }, + { "margin-bottom", '\0', 0, G_OPTION_ARG_INT, &options.margin_bottom, "Bottom margin", NULL }, + { "margin-right", '\0', 0, G_OPTION_ARG_INT, &options.margin_right, "Right margin", NULL }, + { "optimize", 'O', 0, G_OPTION_ARG_INT, &options.optimization_level, "Optimization", NULL }, + { "polite", '\0', 0, G_OPTION_ARG_CALLBACK, parse_polite_arg, "Polite", NULL }, { "preprocess", 'p', 0, G_OPTION_ARG_CALLBACK, parse_preprocess_arg, "Preprocessing", NULL }, { "work", 'w', 0, G_OPTION_ARG_INT, &options.work_factor, "Work factor", NULL }, + { "scale", '\0', 0, G_OPTION_ARG_CALLBACK, parse_scale_arg, "Scale", NULL }, { "size", 's', 0, G_OPTION_ARG_CALLBACK, parse_size_arg, "Output size", NULL }, + { "speed", '\0', 0, G_OPTION_ARG_CALLBACK, parse_anim_speed_arg, "Animation speed", NULL }, { "stretch", '\0', 0, G_OPTION_ARG_NONE, &options.stretch, "Stretch image to fix output dimensions", NULL }, { "symbols", '\0', 0, G_OPTION_ARG_CALLBACK, parse_symbols_arg, "Output symbols", NULL }, + { "threads", '\0', 0, G_OPTION_ARG_INT, &options.n_threads, "Number of threads", NULL }, { "threshold", 't', 0, G_OPTION_ARG_DOUBLE, &options.transparency_threshold, "Transparency threshold", NULL }, { "watch", '\0', 0, G_OPTION_ARG_NONE, &options.watch, "Watch a file's contents", NULL }, + /* Deprecated: Equivalent to --scale max */ { "zoom", '\0', 0, G_OPTION_ARG_NONE, &options.zoom, "Allow scaling up beyond one character per pixel", NULL }, - { NULL } + { 0 } }; + ChafaCanvasMode canvas_mode; + ChafaPixelMode pixel_mode; memset (&options, 0, sizeof (options)); @@ -733,73 +1449,263 @@ /* Defaults */ options.symbol_map = chafa_symbol_map_new (); - chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_ALL); - chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_STIPPLE); - chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_BRAILLE); - chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_ASCII); - chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_EXTRA); +#ifdef G_OS_WIN32 + chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_HALF); + chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_BORDER); + chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_SPACE); + chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_SOLID); +#else + chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_BLOCK); + chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_BORDER); chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_SPACE); +#endif + chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_WIDE); options.fill_symbol_map = chafa_symbol_map_new (); options.is_interactive = isatty (STDIN_FILENO) && isatty (STDOUT_FILENO); - options.mode = detect_canvas_mode (); + detect_terminal (&options.term_info, &canvas_mode, &pixel_mode); + options.mode = CHAFA_CANVAS_MODE_MAX; /* Unset */ + options.pixel_mode = pixel_mode; options.dither_mode = CHAFA_DITHER_MODE_NONE; - options.dither_grain_width = 4; - options.dither_grain_height = 4; + options.dither_grain_width = -1; /* Unset */ + options.dither_grain_height = -1; /* Unset */ options.dither_intensity = 1.0; + options.animate = TRUE; + options.center = FALSE; + options.polite = TRUE; options.preprocess = TRUE; + options.fg_only = FALSE; + options.color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; options.color_space = CHAFA_COLOR_SPACE_RGB; - options.width = 80; - options.height = 25; - options.font_ratio = 1.0 / 2.0; + options.width = -1; /* Unset */ + options.height = -1; /* Unset */ + options.font_ratio = -1.0; /* Unset */ + options.margin_bottom = -1; /* Unset */ + options.margin_right = -1; /* Unset */ + options.scale = -1.0; /* Unset */ options.work_factor = 5; + options.optimization_level = G_MININT; /* Unset */ + options.n_threads = -1; options.fg_color = 0xffffff; options.bg_color = 0x000000; - options.transparency_threshold = -1.0; + options.transparency_threshold = G_MAXDOUBLE; /* Unset */ options.file_duration_s = G_MAXDOUBLE; - get_tty_size (); + options.anim_fps = -1.0; + options.anim_speed_multiplier = 1.0; if (!g_option_context_parse (context, argc, argv, &error)) { g_printerr ("%s: %s\n", options.executable_name, error->message); - return FALSE; + goto out; } - if (options.work_factor < 1 || options.work_factor > 9) + /* If help or version info was requested, print it and bail out */ + + if (options.show_help) { - g_printerr ("%s: Work factor must be in the range [1-9].\n", options.executable_name); - return FALSE; + print_summary (); + options.skip_processing = TRUE; } if (options.show_version) { print_version (); - return TRUE; + options.skip_processing = TRUE; } - if (*argc < 2) + /* Some options preclude normal arg processing */ + + if (options.skip_processing) { - print_summary (); - return FALSE; + result = TRUE; + goto out; } - options.args = collect_variable_arguments (argc, argv, 1); + /* Detect terminal geometry */ - if (options.show_help) + get_tty_size (&detected_term_size); + + if (detected_term_size.width_cells > 0 + && detected_term_size.height_cells > 0 + && detected_term_size.width_pixels > 0 + && detected_term_size.height_pixels > 0) { - print_summary (); - return FALSE; + options.cell_width = detected_term_size.width_pixels / detected_term_size.width_cells; + options.cell_height = detected_term_size.height_pixels / detected_term_size.height_cells; + + if (options.font_ratio <= 0.0 + && options.cell_width > 0 + && options.cell_height > 0) + { + options.font_ratio = (gfloat) options.cell_width / (gfloat) options.cell_height; + } + } + + /* Assign detected or default dimensions if none specified */ + + if (options.width < 0 && options.height < 0) + { + using_detected_size = TRUE; + } + + if (options.margin_bottom < 0) + { + options.margin_bottom = 1; /* Default */ + } + + /* Sixel output always leaves the cursor below the image, so force a bottom + * margin of at least one even if the user wanted less. */ + if (options.margin_bottom < 1 + && options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) + options.margin_bottom = 1; + + if (options.margin_right < 0) + { + options.margin_right = 0; /* Default */ + + /* Kitty leaves the cursor in the column trailing the last row of the + * image. However, if the image is as wide as the terminal, the cursor + * will wrap to the first column of the following row, making us lose + * track of its position. + * + * This is not a problem when instructed to clear the terminal, as we + * use absolute positioning then. + * + * If needed, trim one column from the image to make the cursor position + * predictable. */ + if (options.pixel_mode == CHAFA_PIXEL_MODE_KITTY + && using_detected_size + && !(options.clear && options.margin_bottom >= 2)) + { + options.margin_right = 1; + } + } + + if (using_detected_size) + { + options.width = detected_term_size.width_cells; + options.height = detected_term_size.height_cells; + + if (options.width < 0 && options.height < 0) + { + options.width = 80; + options.height = 25; + } + + options.width = (options.width > options.margin_right) + ? options.width - options.margin_right : 1; + + options.height = (options.height > options.margin_bottom) + ? options.height - options.margin_bottom : 1; + } + + options.have_parking_row = (using_detected_size && options.margin_bottom == 0) ? FALSE : TRUE; + + /* Now we've established the pixel mode, apply dependent defaults */ + + if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) + { + /* Character cell defaults */ + + if (options.mode == CHAFA_CANVAS_MODE_MAX) + options.mode = canvas_mode; + if (options.dither_grain_width < 0) + options.dither_grain_width = 4; + if (options.dither_grain_height < 0) + options.dither_grain_height = 4; + if (options.font_ratio <= 0.0) + options.font_ratio = 1.0 / 2.0; + if (options.scale <= 0.0) + options.scale = 4.0; + } + else + { + /* iTerm2/Kitty/sixel defaults */ + + if (options.mode == CHAFA_CANVAS_MODE_MAX) + options.mode = CHAFA_CANVAS_MODE_TRUECOLOR; + if (options.dither_grain_width < 0) + options.dither_grain_width = 1; + if (options.dither_grain_height < 0) + options.dither_grain_height = 1; + if (options.font_ratio <= 0.0) + options.font_ratio = 1.0 / 2.0; + if (options.scale <= 0.0) + options.scale = 1.0; + } + + if (options.work_factor < 1 || options.work_factor > 9) + { + g_printerr ("%s: Work factor must be in the range [1-9].\n", options.executable_name); + goto out; + } + + if (options.transparency_threshold == G_MAXDOUBLE) + options.transparency_threshold = 0.5; + else + options.transparency_threshold_set = TRUE; + + if (options.transparency_threshold < 0.0 || options.transparency_threshold > 1.0) + { + g_printerr ("%s: Transparency threshold %.1lf is not in the range [0.0-1.0].\n", options.executable_name, options.transparency_threshold); + goto out; + } + + /* Collect filenames and validate count and correct usage of stdin */ + + if (*argc > 1) + { + options.args = collect_variable_arguments (argc, argv, 1); + } + else if (!isatty (STDIN_FILENO)) + { + /* Receiving data through a pipe, and no file arguments. Act as if + * invoked with "chafa -". */ + + options.args = g_list_append (NULL, g_strdup ("-")); + } + else + { + /* No arguments, no pipe, and no cry for help. */ + print_brief_summary (); + goto out; + } + + if (count_dash_strings (options.args) > 1) + { + g_printerr ("%s: Dash '-' to pipe from standard input can be used at most once.\n", + options.executable_name); + goto out; + } + + if (options.watch) + { + if (g_list_length (options.args) != 1) + { + g_printerr ("%s: Can only use --watch with exactly one file.\n", options.executable_name); + goto out; + } + + if (!strcmp (options.args->data, "-")) + { + g_printerr ("%s: Can only use --watch with a filename, not a pipe.\n", options.executable_name); + goto out; + } } - if (options.watch && g_list_length (options.args) != 1) + if (options.zoom) { - g_printerr ("%s: Can only use --watch with exactly one file.\n", options.executable_name); - return FALSE; + g_printerr ("%s: Warning: --zoom is deprecated, use --scale max instead.\n", + options.executable_name); + options.scale = SCALE_MAX; } - /* --stretch implies --zoom */ - options.zoom |= options.stretch; + /* --stretch implies --scale max (same as --zoom) */ + if (options.stretch) + { + options.scale = SCALE_MAX; + } if (options.invert) { @@ -810,10 +1716,14 @@ options.fg_color = temp_color; } - if (options.file_duration_s == G_MAXDOUBLE && options.args && options.args->next) - { - /* The default duration when we have multiple files */ - options.file_duration_s = 3.0; + if (options.file_duration_s == G_MAXDOUBLE + && (!options.is_interactive + || (options.args && options.args->next))) + { + /* Apply a zero default duration when we have multiple files or it looks + * like we're part of a pipe; we don't want to get stuck if the user is + * trying to e.g. batch convert files */ + options.file_duration_s = FILE_DURATION_DEFAULT; } /* Since FGBG mode can't use escape sequences to invert, it really @@ -822,608 +1732,395 @@ if (options.mode != CHAFA_CANVAS_MODE_FGBG && !options.symbols_specified) chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_INVERTED); + /* If optimization level is unset, enable optimizations. However, we + * leave them off for FGBG mode, since control sequences may be + * unexpected in this mode unless explicitly asked for. */ + if (options.optimization_level == G_MININT) + options.optimization_level = (options.mode == CHAFA_CANVAS_MODE_FGBG) ? 0 : 5; + + if (options.optimization_level < 0 || options.optimization_level > 9) + { + g_printerr ("%s: Optimization level %d is not in the range [0-9].\n", + options.executable_name, options.optimization_level); + goto out; + } + + /* Translate optimization level to flags */ + + options.optimizations = CHAFA_OPTIMIZATION_NONE; + + if (options.optimization_level >= 1) + options.optimizations |= CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES; + if (options.optimization_level >= 6) + options.optimizations |= CHAFA_OPTIMIZATION_REPEAT_CELLS; + if (options.optimization_level >= 7) + options.optimizations |= CHAFA_OPTIMIZATION_SKIP_CELLS; + + chafa_set_n_threads (options.n_threads); + + result = TRUE; + +out: g_option_context_free (context); return result; } -static void -auto_orient_image (MagickWand *image) +#define PAD_SPACES_MAX 4096 + +static gboolean +write_pad_spaces (gint n) { -#ifdef HAVE_MAGICK_AUTO_ORIENT_IMAGE - MagickAutoOrientImage (image); -#else - PixelWand *pwand = 0; + gchar buf [PAD_SPACES_MAX]; + gint i; + + g_assert (n >= 0); + + n = MIN (n, PAD_SPACES_MAX); + for (i = 0; i < n; i++) + buf [i] = ' '; - switch (MagickGetImageOrientation (image)) + return write_to_stdout (buf, n); +} + +/* Write out the image data, possibly centering it */ +static gboolean +write_image (const gchar *data, gsize len, gint dest_width) +{ + gint left_space; + gboolean result = FALSE; + + left_space = options.center ? (detected_term_size.width_cells - dest_width) / 2 : 0; + + /* Indent top left corner: Common for all modes */ + if (left_space > 0) { - case UndefinedOrientation: - case TopLeftOrientation: - default: - break; - case TopRightOrientation: - MagickFlopImage (image); - break; - case BottomRightOrientation: - pwand = NewPixelWand (); - MagickRotateImage (image, pwand, 180.0); - case BottomLeftOrientation: - MagickFlipImage (image); - break; - case LeftTopOrientation: - MagickTransposeImage (image); - break; - case RightTopOrientation: - pwand = NewPixelWand (); - MagickRotateImage (image, pwand, 90.0); - break; - case RightBottomOrientation: - MagickTransverseImage (image); - break; - case LeftBottomOrientation: - pwand = NewPixelWand (); - MagickRotateImage (image, pwand, 270.0); - break; + if (!write_pad_spaces (left_space)) + goto out; + } + + if (left_space <= 0 || options.pixel_mode != CHAFA_PIXEL_MODE_SYMBOLS) + { + if (!write_to_stdout (data, len)) + goto out; } + else + { + const gchar *end, *p0, *p1; - if (pwand) - DestroyPixelWand (pwand); + /* Indent subsequent rows: Symbols mode only */ - MagickSetImageOrientation (image, TopLeftOrientation); -#endif + for (p0 = data, end = data + len; p0 < end; p0 = p1) + { + p1 = memchr (p0, '\n', end - p0); + p1 = p1 ? (p1 + 1) : end; + + if (!write_to_stdout (p0, p1 - p0)) + goto out; + + if (p1 != end) + { + if (!write_pad_spaces (left_space)) + goto out; + } + } + } + + result = TRUE; + +out: + return result; } static GString * build_string (ChafaPixelType pixel_type, const guint8 *pixels, gint src_width, gint src_height, gint src_rowstride, - gint dest_width, gint dest_height) + gint dest_width, gint dest_height, + gboolean is_animation) { - ChafaSymbolMap *symbol_map; ChafaCanvasConfig *config; ChafaCanvas *canvas; GString *gs; - symbol_map = chafa_symbol_map_new (); config = chafa_canvas_config_new (); chafa_canvas_config_set_geometry (config, dest_width, dest_height); chafa_canvas_config_set_canvas_mode (config, options.mode); + chafa_canvas_config_set_pixel_mode (config, options.pixel_mode); chafa_canvas_config_set_dither_mode (config, options.dither_mode); chafa_canvas_config_set_dither_grain_size (config, options.dither_grain_width, options.dither_grain_height); chafa_canvas_config_set_dither_intensity (config, options.dither_intensity); + chafa_canvas_config_set_color_extractor (config, options.color_extractor); chafa_canvas_config_set_color_space (config, options.color_space); chafa_canvas_config_set_fg_color (config, options.fg_color); chafa_canvas_config_set_bg_color (config, options.bg_color); chafa_canvas_config_set_preprocessing_enabled (config, options.preprocess); - if (options.transparency_threshold >= 0.0) + chafa_canvas_config_set_fg_only_enabled (config, options.fg_only); + + if (is_animation + && options.pixel_mode == CHAFA_PIXEL_MODE_KITTY + && !options.transparency_threshold_set) + chafa_canvas_config_set_transparency_threshold (config, 1.0f); + else if (options.transparency_threshold >= 0.0) chafa_canvas_config_set_transparency_threshold (config, options.transparency_threshold); + if (options.cell_width > 0 && options.cell_height > 0) + chafa_canvas_config_set_cell_geometry (config, options.cell_width, options.cell_height); + chafa_canvas_config_set_symbol_map (config, options.symbol_map); chafa_canvas_config_set_fill_symbol_map (config, options.fill_symbol_map); /* Work switch takes values [1..9], we normalize to [0.0..1.0] to * get the work factor. */ - chafa_canvas_config_set_work_factor (config, (options.work_factor - 1) / 8.0); + chafa_canvas_config_set_work_factor (config, (options.work_factor - 1) / 8.0f); + + chafa_canvas_config_set_optimizations (config, options.optimizations); canvas = chafa_canvas_new (config); chafa_canvas_draw_all_pixels (canvas, pixel_type, pixels, src_width, src_height, src_rowstride); - gs = chafa_canvas_build_ansi (canvas); + gs = chafa_canvas_print (canvas, options.term_info); chafa_canvas_unref (canvas); chafa_canvas_config_unref (config); - chafa_symbol_map_unref (symbol_map); return gs; } static void -process_image (MagickWand *wand, gint *dest_width_out, gint *dest_height_out) +pixel_to_cell_dimensions (gdouble scale, + gint cell_width, gint cell_height, + gint width, gint height, + gint *width_out, gint *height_out) { - gint src_width, src_height; - gint dest_width, dest_height; + /* Scale can't be zero or negative */ + scale = MAX (scale, 0.00001); - auto_orient_image (wand); + /* Zero or negative cell dimensions -> presumably unknown, use 8x8 */ + if (cell_width < 1) + cell_width = 8; + if (cell_height < 1) + cell_height = 8; - src_width = MagickGetImageWidth (wand); - src_height = MagickGetImageHeight (wand); - - dest_width = options.width; - dest_height = options.height; - - chafa_calc_canvas_geometry (src_width, - src_height, - &dest_width, - &dest_height, - options.font_ratio, - options.zoom, - options.stretch); - - if (dest_width_out) - *dest_width_out = dest_width; - if (dest_height_out) - *dest_height_out = dest_height; -} - -typedef struct -{ - GString *gs; - gint dest_width, dest_height; - gint delay_ms; -} -GroupFrame; - -typedef struct -{ - GList *frames; -} -Group; - -static void -group_build (Group *group, MagickWand *wand) -{ - memset (group, 0, sizeof (*group)); - - for (MagickResetIterator (wand); !interrupted_by_user; ) - { - GroupFrame *frame; - - if (!MagickNextImage (wand)) - break; - - frame = g_new0 (GroupFrame, 1); - - process_image (wand, &frame->dest_width, &frame->dest_height); - frame->delay_ms = MagickGetImageDelay (wand) * 10; - if (frame->delay_ms == 0) - frame->delay_ms = 50; - - /* String representation is built on demand and cached */ - - group->frames = g_list_prepend (group->frames, frame); - } - - group->frames = g_list_reverse (group->frames); -} - -static void -group_build_gif (Group *group, GifLoader *loader) -{ - memset (group, 0, sizeof (*group)); - - for (gif_loader_first_frame (loader); !interrupted_by_user; ) + if (width_out) { - GroupFrame *frame; - - frame = g_new0 (GroupFrame, 1); - - frame->dest_width = -1; - frame->dest_height = -1; - frame->delay_ms = -1; - - /* String representation is built on demand and cached */ - - group->frames = g_list_prepend (group->frames, frame); - - if (!gif_loader_next_frame (loader)) - break; + *width_out = (gdouble) width * scale / cell_width + 0.5; + *width_out = MAX (*width_out, 1); } - group->frames = g_list_reverse (group->frames); -} - -static void -group_build_xwd (Group *group) -{ - GroupFrame *frame; - - memset (group, 0, sizeof (*group)); - - frame = g_new0 (GroupFrame, 1); - frame->dest_width = -1; - frame->dest_height = -1; - frame->delay_ms = -1; - - group->frames = g_list_prepend (group->frames, frame); -} - -static void -group_clear (Group *group) -{ - GList *l; - - for (l = group->frames; l; l = g_list_next (l)) + if (height_out) { - GroupFrame *frame = l->data; - - if (frame->gs) - g_string_free (frame->gs, TRUE); - g_free (frame); + *height_out = (gdouble) height * scale / cell_width + 0.5; + *height_out = MAX (*height_out, 1); } - - g_list_free (group->frames); - memset (group, 0, sizeof (*group)); } -static void -interruptible_usleep (gint us) +typedef enum { - while (us > 0 && !interrupted_by_user) - { - gint sleep_us = MIN (us, 50000); - g_usleep (sleep_us); - us -= sleep_us; - } + FILE_FAILED, + FILE_WAS_STILL, + FILE_WAS_ANIMATION } +RunResult; -/* FIXME: This function is ripe for refactoring, probably to something with - * a heap-allocated context. */ -static gboolean -run_magickwand (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) +static RunResult +run_generic (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) { - MagickWand *wand = NULL; gboolean is_animation = FALSE; gdouble anim_elapsed_s = 0.0; GTimer *timer; - Group group = { NULL }; - GList *l; gint loop_n = 0; - FileMapping *file_mapping; - XwdLoader *xwd_loader; + MediaLoader *media_loader; + gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 2 + 3]; + GString *gs; + gchar *p0; + RunResult result = FILE_FAILED; + GError *error = NULL; timer = g_timer_new (); - /* Try XWD fast path first */ - - file_mapping = file_mapping_new (filename); - xwd_loader = xwd_loader_new_from_mapping (file_mapping); - if (!xwd_loader) + media_loader = media_loader_new (filename, &error); + if (!media_loader) { - PixelWand *color; - - file_mapping_destroy (file_mapping); - file_mapping = NULL; - - wand = NewMagickWand (); - - color = NewPixelWand (); - PixelSetColor (color, "none"); - MagickSetBackgroundColor (wand, color); - DestroyPixelWand (color); - - if (MagickReadImage (wand, filename) < 1) - { - gchar *error_str = NULL; - ExceptionType severity; - gchar *try_filename; - gint r; - - error_str = MagickGetException (wand, &severity); - - /* Try backup strategy for XWD. It's a file type we want to support - * due to the fun implications with Xvfb etc. The filenames in use - * tend to have no extension, and the file magic isn't very definite, - * so ImageMagick doesn't know what to do on its own. */ - try_filename = g_strdup_printf ("XWD:%s", filename); - r = MagickReadImage (wand, try_filename); - g_free (try_filename); - - if (r < 1) - { - if (!quiet) - g_printerr ("%s: Error loading '%s': %s\n", - options.executable_name, - filename, - error_str); - MagickRelinquishMemory (error_str); - goto out; - } - } + if (!quiet) + g_printerr ("%s: Failed to open '%s': %s\n", + options.executable_name, + filename, + error->message); + goto out; } if (interrupted_by_user) goto out; - if (xwd_loader) - { - group_build_xwd (&group); - } - else /* wand */ - { - is_animation = MagickGetNumberImages (wand) > 1 ? TRUE : FALSE; - - if (is_animation) - { - MagickWand *wand2 = MagickCoalesceImages (wand); - wand = DestroyMagickWand (wand); - wand = wand2; - } - - if (interrupted_by_user) - goto out; - - group_build (&group, wand); - - if (interrupted_by_user) - goto out; - } + is_animation = options.animate ? media_loader_get_is_animation (media_loader) : FALSE; + result = is_animation ? FILE_WAS_ANIMATION : FILE_WAS_STILL; do { + gboolean have_frame; + /* Outer loop repeats animation if desired */ - if (wand) - MagickResetIterator (wand); + media_loader_goto_first_frame (media_loader); - for (l = group.frames; - l && !interrupted_by_user && (loop_n == 0 || anim_elapsed_s < options.file_duration_s); - l = g_list_next (l)) + for (have_frame = TRUE; + have_frame && !interrupted_by_user && (loop_n == 0 || anim_elapsed_s < options.file_duration_s); + have_frame = media_loader_goto_next_frame (media_loader)) { - GroupFrame *frame = l->data; - gint elapsed_ms, remain_ms; - - if (wand) - MagickNextImage (wand); + gdouble elapsed_ms, remain_ms; + gint delay_ms; + ChafaPixelType pixel_type; + gint src_width, src_height, src_rowstride; + gint virt_src_width, virt_src_height; + gint dest_width, dest_height; + const guint8 *pixels; g_timer_start (timer); - if (!frame->gs) + pixels = media_loader_get_frame_data (media_loader, + &pixel_type, + &src_width, + &src_height, + &src_rowstride); + /* FIXME: This shouldn't happen -- but if it does, our + * options for handling it gracefully here aren't great. + * Needs refactoring. */ + if (!pixels) + break; + + delay_ms = media_loader_get_frame_delay (media_loader); + + /* Hack to work around the fact that chafa_calc_canvas_geometry() doesn't + * support arbitrary scaling. Instead, we manipulate the source size to + * achieve the desired effect. */ + if (using_detected_size && options.scale < SCALE_MAX - 0.1) { - ChafaPixelType pixel_type; - gint src_width, src_height, src_rowstride; - const guint8 *pixels; + pixel_to_cell_dimensions (options.scale, + options.cell_width, options.cell_height, + src_width, src_height, + &virt_src_width, &virt_src_height); + } + else + { + virt_src_width = src_width; + virt_src_height = src_height; + } - if (xwd_loader) - { - pixels = xwd_loader_get_image_data (xwd_loader, - &pixel_type, - &src_width, - &src_height, - &src_rowstride); - - /* FIXME: This shouldn't happen -- but if it does, our - * options for handling it gracefully here aren't great. - * Needs refactoring. */ - if (!pixels) - break; - - frame->dest_width = options.width; - frame->dest_height = options.height; - chafa_calc_canvas_geometry (src_width, - src_height, - &frame->dest_width, - &frame->dest_height, - options.font_ratio, - options.zoom, - options.stretch); - } - else /* wand */ - { - src_width = MagickGetImageWidth (wand); - src_height = MagickGetImageHeight (wand); - src_rowstride = src_width * 4; - - pixels = g_malloc (src_height * src_rowstride); - MagickExportImagePixels (wand, - 0, 0, - src_width, src_height, - "RGBA", - CharPixel, - (void *) pixels); - pixel_type = CHAFA_PIXEL_RGBA8_UNASSOCIATED; - } + dest_width = options.width; + dest_height = options.height; - frame->gs = build_string (pixel_type, pixels, - src_width, src_height, src_rowstride, - frame->dest_width, frame->dest_height); + chafa_calc_canvas_geometry (virt_src_width, + virt_src_height, + &dest_width, + &dest_height, + options.font_ratio, + options.scale >= SCALE_MAX - 0.1 ? TRUE : FALSE, + options.stretch); + + gs = build_string (pixel_type, pixels, + src_width, src_height, src_rowstride, + dest_width, dest_height, + is_animation); - if (!xwd_loader) - g_free ((void *) pixels); - } + p0 = buf; if (options.clear) { if (is_first_frame) { /* Clear */ - printf ("\x1b[2J"); + p0 = chafa_term_info_emit_clear (options.term_info, p0); } /* Home cursor between frames */ - printf ("\x1b[0f"); + p0 = chafa_term_info_emit_cursor_to_top_left (options.term_info, p0); } else if (!is_first_frame) { - /* Cursor up N steps */ - printf ("\x1b[%dA", frame->dest_height); + /* Cursor to col 0 and up N steps */ + *(p0++) = '\r'; + p0 = chafa_term_info_emit_cursor_up (options.term_info, p0, dest_height - (options.have_parking_row ? 0 : 1)); } /* Put a blank line between files in non-clear mode */ if (is_first_frame && !options.clear && !is_first_file) - fputc ('\n', stdout); - - fwrite (frame->gs->str, sizeof (gchar), frame->gs->len, stdout); - fputc ('\n', stdout); - fflush (stdout); - - if (is_animation) { - /* Account for time spent converting and printing frame */ - elapsed_ms = g_timer_elapsed (timer, NULL) * 1000.0; - remain_ms = frame->delay_ms - elapsed_ms; - remain_ms = MAX (remain_ms, 0); - interruptible_usleep (remain_ms * 1000); - - anim_elapsed_s += MAX (elapsed_ms, frame->delay_ms) / 1000.0; + if (!options.have_parking_row) + *(p0++) = '\n'; + *(p0++) = '\n'; } - is_first_frame = FALSE; - } - - loop_n++; - } - while (options.is_interactive && is_animation && !interrupted_by_user - && !options.watch && anim_elapsed_s < options.file_duration_s); - -out: - if (wand) - DestroyMagickWand (wand); - if (xwd_loader) - xwd_loader_destroy (xwd_loader); - group_clear (&group); - g_timer_destroy (timer); - - return is_animation; -} - -static gboolean -run_gif (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean *success_out) -{ - FileMapping *mapping = NULL; - GifLoader *loader = NULL; - const guint8 *pixels; - gboolean success = FALSE; - gboolean is_animation = FALSE; - gdouble anim_elapsed_s = 0.0; - GTimer *timer; - Group group = { NULL }; - GList *l; - gint loop_n = 0; - - timer = g_timer_new (); - - mapping = file_mapping_new (filename); - if (!mapping) - goto out; - - loader = gif_loader_new_from_mapping (mapping); - if (!loader) - goto out; - - /* Loader owns the mapping now */ - mapping = NULL; - - success = TRUE; - - if (interrupted_by_user) - goto out; - - is_animation = gif_loader_get_n_frames (loader) > 1 ? TRUE : FALSE; - - group_build_gif (&group, loader); - - gif_loader_first_frame (loader); - - do - { - /* Outer loop repeats animation if desired */ + if (!write_to_stdout (buf, p0 - buf)) + goto out; - for (l = group.frames; - l && !interrupted_by_user && (loop_n == 0 || anim_elapsed_s < options.file_duration_s); - l = g_list_next (l)) - { - GroupFrame *frame = l->data; - gint elapsed_ms, remain_ms; + if (!write_image (gs->str, gs->len, dest_width)) + goto out; - g_timer_start (timer); + g_string_free (gs, TRUE); - if (!frame->gs) + /* No linefeed after frame in sixel mode */ + if (options.have_parking_row + && (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS + || options.pixel_mode == CHAFA_PIXEL_MODE_KITTY + || options.pixel_mode == CHAFA_PIXEL_MODE_ITERM2)) { - gint src_width, src_height; - - pixels = gif_loader_get_frame_data (loader, &frame->delay_ms); - if (!pixels) + if (!write_to_stdout ("\n", 1)) goto out; - - frame->delay_ms *= 10; - if (frame->delay_ms == 0) - frame->delay_ms = 50; - - gif_loader_get_geometry (loader, &src_width, &src_height); - frame->dest_width = options.width; - frame->dest_height = options.height; - chafa_calc_canvas_geometry (src_width, - src_height, - &frame->dest_width, - &frame->dest_height, - options.font_ratio, - options.zoom, - options.stretch); - - frame->gs = build_string (CHAFA_PIXEL_RGBA8_UNASSOCIATED, pixels, - src_width, src_height, src_width * 4, - frame->dest_width, frame->dest_height); - - gif_loader_next_frame (loader); - } - - if (options.clear) - { - if (is_first_frame) - { - /* Clear */ - printf ("\x1b[2J"); - } - - /* Home cursor between frames */ - printf ("\x1b[0f"); } - else if (!is_first_frame) + else if (options.center && options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) { - /* Cursor up N steps */ - printf ("\x1b[%dA", frame->dest_height); + /* If image was centered in sixel mode, cursor must be brought + * back to left margin manually */ + if (!write_to_stdout ("\r", 1)) + goto out; } - /* Put a blank line between files in non-clear mode */ - if (is_first_frame && !options.clear && !is_first_file) - fputc ('\n', stdout); - - fwrite (frame->gs->str, sizeof (gchar), frame->gs->len, stdout); - fputc ('\n', stdout); - fflush (stdout); + if (fflush (stdout) != 0) + goto out; if (is_animation) { /* Account for time spent converting and printing frame */ elapsed_ms = g_timer_elapsed (timer, NULL) * 1000.0; - remain_ms = frame->delay_ms - elapsed_ms; - remain_ms = MAX (remain_ms, 0); - interruptible_usleep (remain_ms * 1000); - anim_elapsed_s += MAX (elapsed_ms, frame->delay_ms) / 1000.0; + if (options.anim_fps > 0.0) + remain_ms = 1000.0 / options.anim_fps; + else + remain_ms = delay_ms; + remain_ms /= options.anim_speed_multiplier; + remain_ms = MAX (remain_ms - elapsed_ms, 0); + + if (remain_ms > 0.0001 && 1000.0 / (gdouble) remain_ms < ANIM_FPS_MAX) + interruptible_usleep (remain_ms * 1000); + + anim_elapsed_s += MAX (elapsed_ms, delay_ms) / 1000.0; } is_first_frame = FALSE; + + if (!is_animation) + break; } loop_n++; } - while (options.is_interactive && is_animation && !interrupted_by_user + while (is_animation && !interrupted_by_user && !options.watch && anim_elapsed_s < options.file_duration_s); out: - if (loader) - gif_loader_destroy (loader); - group_clear (&group); - if (mapping) - file_mapping_destroy (mapping); + if (media_loader) + media_loader_destroy (media_loader); g_timer_destroy (timer); - if (success_out) - *success_out = success; - return is_animation; + if (error) + g_error_free (error); + + return result; } -static gboolean +static RunResult run (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) { - gboolean is_animation; - gboolean success = FALSE; - - is_animation = run_gif (filename, is_first_file, is_first_frame, &success); - if (success) - return is_animation; - - is_animation = run_magickwand (filename, is_first_file, is_first_frame, quiet); - return is_animation; + return run_generic (filename, is_first_file, is_first_frame, quiet); } static int @@ -1433,7 +2130,6 @@ gboolean is_first_frame = TRUE; tty_options_init (); - MagickWandGenesis (); timer = g_timer_new (); for ( ; !interrupted_by_user; ) @@ -1462,7 +2158,6 @@ } g_timer_destroy (timer); - MagickWandTerminus (); tty_options_deinit (); return 0; } @@ -1471,45 +2166,65 @@ run_all (GList *filenames) { GList *l; + gint n_processed = 0; + gint n_failed = 0; + /* This can only happen with --help and --version, so no error */ if (!filenames) return 0; tty_options_init (); - MagickWandGenesis (); for (l = filenames; l && !interrupted_by_user; l = g_list_next (l)) { gchar *filename = l->data; - gboolean was_animation; + RunResult result; + + result = run (filename, l->prev ? FALSE : TRUE, TRUE, FALSE); - was_animation = run (filename, l->prev ? FALSE : TRUE, TRUE, FALSE); + n_processed++; + if (result == FILE_FAILED) + n_failed++; - if (!was_animation && options.file_duration_s != G_MAXDOUBLE) + if (result == FILE_WAS_STILL && options.file_duration_s != G_MAXDOUBLE) { interruptible_usleep (options.file_duration_s * 1000000.0); } } - MagickWandTerminus (); + /* Emit linefeed after last image when cursor was not in parking row */ + if (!options.have_parking_row) + write_to_stdout ("\n", 1); + tty_options_deinit (); - return 0; + return (n_processed - n_failed < 1) ? 2 : (n_failed > 0) ? 1 : 0; } static void proc_init (void) { +#ifdef HAVE_SIGACTION struct sigaction sa = { 0 }; + sa.sa_handler = sigint_handler; + sa.sa_flags = SA_RESETHAND; + + sigaction (SIGINT, &sa, NULL); +#endif + +#ifdef G_OS_WIN32 + setmode (fileno (stdin), O_BINARY); + setmode (fileno (stdout), O_BINARY); +#endif + /* Must do this early. Buffer size probably doesn't matter */ setvbuf (stdout, NULL, _IOFBF, 32768); setlocale (LC_ALL, ""); - sa.sa_handler = sigint_handler; - sa.sa_flags = SA_RESETHAND; - - sigaction (SIGINT, &sa, NULL); + /* Chafa may create and destroy GThreadPools multiple times while rendering + * an image. This reduces thread churn and saves a decent amount of CPU. */ + g_thread_pool_set_max_unused_threads (-1); } int @@ -1520,7 +2235,7 @@ proc_init (); if (!parse_options (&argc, &argv)) - exit (1); + exit (2); ret = options.watch ? run_watch (options.args->data) @@ -1530,5 +2245,7 @@ chafa_symbol_map_unref (options.symbol_map); if (options.fill_symbol_map) chafa_symbol_map_unref (options.fill_symbol_map); + if (options.term_info) + chafa_term_info_unref (options.term_info); return ret; } diff -Nru chafa-1.2.1/tools/chafa/file-mapping.c chafa-1.12.4/tools/chafa/file-mapping.c --- chafa-1.2.1/tools/chafa/file-mapping.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/file-mapping.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2019 Hans Petter Jansson +/* Copyright (C) 2019-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -24,11 +24,24 @@ #include #include #include +#include +#include + +#ifdef G_OS_WIN32 +# include +# include +#endif #ifdef HAVE_MMAP # include #endif +#ifdef HAVE_GETRANDOM +# include +#endif + +#include + #include "file-mapping.h" #ifdef HAVE_MMAP @@ -38,15 +51,43 @@ # endif #endif +/* MS Windows needs files to be explicitly opened as O_BINARY. However, this + * flag is not always defined in our cross builds. */ +#ifndef O_BINARY +# ifdef _O_BINARY +# define O_BINARY _O_BINARY +# else +# define O_BINARY 0 +# endif +#endif + +/* Streams bigger than this must be cached in a file */ +#define FILE_MEMORY_CACHE_MAX (4 * 1024 * 1024) + +/* Size of buffer used for copying stdin to file */ +#define COPY_BUFFER_SIZE 8192 + +/* A contiguous magic string can't be longer than this */ +#define MAGIC_LENGTH_MAX 1024 + struct FileMapping { gchar *path; gpointer data; gsize length; gint fd; + guint failed : 1; guint is_mmapped : 1; }; +static gboolean +file_is_stdin (FileMapping *file_mapping) +{ + return file_mapping->path + && file_mapping->path [0] == '-' + && file_mapping->path [1] == '\0' ? TRUE : FALSE; +} + static gsize safe_read (gint fd, void *buf, gsize len) { @@ -57,6 +98,7 @@ { unsigned int nread; int iread; + int saved_errno; /* Passing nread > INT_MAX to read is implementation defined in POSIX * 1003.1, therefore despite the unsigned argument portable code must @@ -68,6 +110,7 @@ nread = (unsigned int)/*SAFE*/len; iread = read (fd, buffer, nread); + saved_errno = errno; if (iread == -1) { @@ -75,7 +118,7 @@ * bytes read because of EINTR, yet it still returns -1 otherwise end * of file cannot be distinguished. */ - if (errno != EINTR) + if (saved_errno != EINTR) { /* I.e. a permanent failure */ return 0; @@ -94,12 +137,73 @@ ntotal += (unsigned int)/*SAFE*/iread; } else + { return ntotal; + } } return ntotal; /* len == 0 */ } +static gboolean +safe_write (gint fd, gconstpointer buf, gsize len) +{ + const guint8 *buffer = buf; + gboolean success = FALSE; + + while (len > 0) + { + guint to_write; + gint n_written; + gint saved_errno; + + if (len > INT_MAX) + to_write = INT_MAX; + else + to_write = (unsigned int) len; + + n_written = write (fd, buffer, to_write); + saved_errno = errno; + + if (n_written == -1) + { + if (saved_errno != EINTR) + goto out; + } + else if (n_written < 0) + { + /* Not a valid 'write' result */ + goto out; + } + else if (n_written > 0) + { + /* Continue writing until permanent failure or entire buffer written */ + buffer += n_written; + len -= (unsigned int) n_written; + } + } + + success = TRUE; + +out: + return success; +} + +static gboolean +safe_copy (gint src_fd, gint dest_fd) +{ + guint8 buf [COPY_BUFFER_SIZE]; + gsize n_read; + + while ((n_read = safe_read (src_fd, buf, COPY_BUFFER_SIZE)) > 0) + { + if (!safe_write (dest_fd, buf, n_read)) + return FALSE; + } + + return TRUE; +} + static void free_file_data (FileMapping *file_mapping) { @@ -114,17 +218,187 @@ } if (file_mapping->fd >= 0) - close (file_mapping->fd); + g_close (file_mapping->fd, NULL); file_mapping->fd = -1; file_mapping->data = NULL; file_mapping->is_mmapped = FALSE; } +static guint64 +get_random_u64 (void) +{ + guint64 u64 = 0; + guint32 *u32p = (guint32 *) &u64; + gint len = 0; + GTimeVal tv; + +#ifdef HAVE_GETRANDOM + len = getrandom ((void *) &u64, sizeof (guint64), GRND_NONBLOCK); +#endif + + if (!u64 || len < (gint) sizeof (guint64)) + { + gpointer p; + + /* FIXME: Not paranoid enough */ + + /* FIXME: g_get_current_time() was deprecated in GLib 2.62. Switch to + * g_get_real_time () sometime, and require GLib >= 2.28. */ + + g_get_current_time (&tv); + g_random_set_seed (tv.tv_sec ^ tv.tv_usec); + u32p [0] ^= g_random_int (); + g_get_current_time (&tv); + g_random_set_seed (tv.tv_sec ^ tv.tv_usec); + u32p [1] ^= g_random_int (); + + p = g_thread_self (); + u64 ^= (guint64) p; + } + + return u64; +} + +static gint +open_temp_file_in_path (const gchar *base_path) +{ + gchar *cache_path; + gint fd; + + if (!base_path) + return -1; + + cache_path = g_strdup_printf ("%s%schafa-%016" G_GINT64_MODIFIER "x", + base_path, + G_DIR_SEPARATOR_S, + get_random_u64 ()); + + /* Create the file and unlink it, so it goes away when we exit */ + fd = g_open (cache_path, O_CREAT | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR); + if (fd >= 0) + { + /* You can't unlink an open file on MS Windows, so this will fail there */ + g_unlink (cache_path); + } + + g_free (cache_path); + + return fd; +} + +static gint +open_temp_file (void) +{ + gint fd; + + fd = open_temp_file_in_path (g_get_user_cache_dir ()); + if (fd < 0) + fd = open_temp_file_in_path (g_get_tmp_dir ()); + + return fd; +} + +static gint +cache_stdin (FileMapping *file_mapping, GError **error) +{ + gpointer buf = NULL; + gint stdin_fd = fileno (stdin); /* Can't use STDIN_FILENO on Windows */ + size_t n_read; + gint cache_fd = -1; + gint success = FALSE; + + if (stdin_fd < 0) + goto out; + +#ifdef G_OS_WIN32 + setmode (stdin_fd, O_BINARY); +#endif + + /* Read from stdin */ + + buf = g_malloc (FILE_MEMORY_CACHE_MAX); + n_read = safe_read (stdin_fd, buf, FILE_MEMORY_CACHE_MAX); + if (n_read < 1) + goto out; + + /* If the stream didn't fit in memory, save it all to a file instead. + * We can mmap it later. Or read it back in, ha ha. */ + + if (n_read == FILE_MEMORY_CACHE_MAX) + { + cache_fd = open_temp_file (); + if (cache_fd >= 0) + { + if (!safe_write (cache_fd, buf, n_read)) + goto out; + g_free (buf); + buf = NULL; + + if (!safe_copy (stdin_fd, cache_fd)) + goto out; + } + } + else + { + file_mapping->data = g_realloc (buf, n_read); + file_mapping->length = n_read; + buf = NULL; + } + + success = TRUE; + +out: + if (!success) + { + if (cache_fd >= 0) + g_close (cache_fd, NULL); + cache_fd = -1; + g_free (buf); + + if (error && !*error) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Could not cache input stream"); + } + } + + return cache_fd; +} + static gint -open_file (FileMapping *file_mapping) +open_file (FileMapping *file_mapping, GError **error) { - return open (file_mapping->path, O_RDONLY); + gint fd; + + if (file_is_stdin (file_mapping)) + { + fd = cache_stdin (file_mapping, error); + } + else + { + fd = g_open (file_mapping->path, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR); + if (fd < 0) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + "%s", strerror (errno)); + } + } + + return fd; +} + +static gboolean +ensure_open_file (FileMapping *file_mapping) +{ + if (file_mapping->data || file_mapping->fd >= 0) + return TRUE; + + file_mapping->fd = open_file (file_mapping, NULL); + if (file_mapping->data || file_mapping->fd >= 0) + return TRUE; + + return FALSE; } static guint8 * @@ -172,39 +446,46 @@ struct stat sbuf; gint t; + if (file_mapping->failed) + return FALSE; if (file_mapping->data) return TRUE; if (file_mapping->fd < 0) - file_mapping->fd = open_file (file_mapping); + file_mapping->fd = open_file (file_mapping, NULL); - if (file_mapping->fd < 0) - goto out; + /* If the data arrived over a pipe and was reasonably small, open_file () will + * populate the data fields instead of the fd. */ + if (!file_mapping->data) + { + if (file_mapping->fd < 0) + goto out; - t = fstat (file_mapping->fd, &sbuf); + t = fstat (file_mapping->fd, &sbuf); - if (!t) - { - file_mapping->length = sbuf.st_size; + if (!t) + { + file_mapping->length = sbuf.st_size; #ifdef HAVE_MMAP - /* Try memory mapping first */ - file_mapping->data = mmap (NULL, - file_mapping->length, - PROT_READ, - MAP_SHARED | MAP_NORESERVE, - file_mapping->fd, - 0); + /* Try memory mapping first */ + file_mapping->data = mmap (NULL, + file_mapping->length, + PROT_READ, + MAP_SHARED | MAP_NORESERVE, + file_mapping->fd, + 0); #endif - } + } - if (file_mapping->data) - { - file_mapping->is_mmapped = TRUE; - } - else - { - file_mapping->data = read_file (file_mapping->fd, &file_mapping->length); - file_mapping->is_mmapped = FALSE; + if (file_mapping->data) + { + file_mapping->is_mmapped = TRUE; + } + else + { + file_mapping->data = read_file (file_mapping->fd, &file_mapping->length); + file_mapping->is_mmapped = FALSE; + } } if (!file_mapping->data) @@ -213,6 +494,12 @@ result = TRUE; out: + + if (!result) + { + file_mapping->failed = TRUE; + } + return result; } @@ -236,6 +523,31 @@ g_free (file_mapping); } +gboolean +file_mapping_open_now (FileMapping *file_mapping, GError **error) +{ + if (file_mapping->data || file_mapping->fd >= 0) + return TRUE; + + file_mapping->fd = open_file (file_mapping, error); + if (file_mapping->data || file_mapping->fd >= 0) + return TRUE; + + if (error && !*error) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Open/map failed"); + } + + return FALSE; +} + +const gchar * +file_mapping_get_path (FileMapping *file_mapping) +{ + return file_mapping->path; +} + gconstpointer file_mapping_get_data (FileMapping *file_mapping, gsize *length_out) { @@ -251,8 +563,19 @@ gboolean file_mapping_taste (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length) { - if (file_mapping->fd < 0) - file_mapping->fd = open_file (file_mapping); + if (!ensure_open_file (file_mapping)) + return FALSE; + + if (file_mapping->data) + { + if (ofs + length <= file_mapping->length) + { + memcpy (out, ((const gchar *) file_mapping->data) + ofs, length); + return TRUE; + } + + return FALSE; + } if (file_mapping->fd < 0) return FALSE; @@ -266,31 +589,60 @@ return TRUE; } +gssize +file_mapping_read (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length) +{ + if (!ensure_open_file (file_mapping)) + return FALSE; + + if (file_mapping->data) + { + if (ofs <= (gssize) file_mapping->length) + { + gssize seg_len = MIN (length, file_mapping->length - ofs); + memcpy (out, ((const gchar *) file_mapping->data) + ofs, seg_len); + return seg_len; + } + + return -1; + } + + /* FIXME: Shouldn't happen */ + if (file_mapping->fd < 0) + return -1; + + if (lseek (file_mapping->fd, ofs, SEEK_SET) != ofs) + return -1; + + return safe_read (file_mapping->fd, out, length); +} + gboolean file_mapping_has_magic (FileMapping *file_mapping, goffset ofs, gconstpointer data, gsize length) { - gchar *buf; + gchar buf [MAGIC_LENGTH_MAX]; + + g_assert (length <= MAGIC_LENGTH_MAX); + + if (!ensure_open_file (file_mapping)) + return FALSE; if (file_mapping->data) { if (ofs + length <= file_mapping->length - && !memcmp ((const guint8 *) data + ofs, data, length)) + && !memcmp ((const guint8 *) file_mapping->data + ofs, data, length)) return TRUE; return FALSE; } - if (file_mapping->fd < 0) - file_mapping->fd = open_file (file_mapping); - + /* FIXME: Shouldn't happen */ if (file_mapping->fd < 0) return FALSE; if (lseek (file_mapping->fd, ofs, SEEK_SET) != ofs) return FALSE; - buf = alloca (length); - if (safe_read (file_mapping->fd, buf, length) != length) return FALSE; diff -Nru chafa-1.2.1/tools/chafa/file-mapping.h chafa-1.12.4/tools/chafa/file-mapping.h --- chafa-1.2.1/tools/chafa/file-mapping.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/file-mapping.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2019 Hans Petter Jansson +/* Copyright (C) 2019-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -29,7 +29,12 @@ FileMapping *file_mapping_new (const gchar *path); void file_mapping_destroy (FileMapping *file_mapping); +gboolean file_mapping_open_now (FileMapping *file_mapping, GError **error); + +const gchar *file_mapping_get_path (FileMapping *file_mapping); + gboolean file_mapping_taste (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length); +gssize file_mapping_read (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length); gconstpointer file_mapping_get_data (FileMapping *file_mapping, gsize *length_out); gboolean file_mapping_has_magic (FileMapping *file_mapping, goffset ofs, diff -Nru chafa-1.2.1/tools/chafa/font-loader.c chafa-1.12.4/tools/chafa/font-loader.c --- chafa-1.2.1/tools/chafa/font-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/font-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,537 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H + +#include + +#include "font-loader.h" + +#define DEBUG(x) + +#define REQ_WIDTH_DEFAULT 15 +#define REQ_HEIGHT_DEFAULT 8 + +/* The font is read in two passes; once for narrow (single-cell) symbols, + * and once for wide (double-cell) ones. This allows us to use a different + * resolution for each -- 8x8 vs 16x8. */ +typedef enum +{ + FONT_PASS_NARROW, + FONT_PASS_WIDE +} +FontPass; + +struct FontLoader +{ + /* General / I/O */ + FileMapping *mapping; + const guint8 *file_data; + size_t file_data_len; + FT_Library ft_lib; + FT_Face ft_face; + + /* Cell size that provides a good fit for font, pre-scaled */ + gint font_width; + gint font_height; + + /* Baseline offset, vertical from top */ + gint baseline_ofs; + + /* Iterator */ + FontPass pass; + FT_ULong glyph_charcode; + gint n_glyphs_read; +}; + +/* With 256 bins we get a histogram for integer values [-128 .. 127]. This + * is more than enough for the sizes we'll be getting, which should be in the + * 0..16 range, give or take a little. Values outside the histogram's range + * will be silently discarded. */ + +#define SMALL_HISTOGRAM_N_BINS 256 + +typedef struct +{ + gint count [SMALL_HISTOGRAM_N_BINS]; + gint first_bin; + gint n_values; +} +SmallHistogram; + +static void +small_histogram_init (SmallHistogram *hist) +{ + memset (hist, 0, sizeof (*hist)); + hist->first_bin = - (SMALL_HISTOGRAM_N_BINS / 2); +} + +static void +small_histogram_add (SmallHistogram *hist, gint value) +{ + gint bin_index; + + bin_index = value - hist->first_bin; + if (bin_index < 0 || bin_index >= SMALL_HISTOGRAM_N_BINS) + return; + + hist->count [bin_index]++; + hist->n_values++; +} + +static gint +small_histogram_get_quantile (SmallHistogram *hist, gint dividend, gint divisor) +{ + gint i; + gint n = 0; + + g_return_val_if_fail (dividend <= divisor, 0); + + for (i = 0; i < SMALL_HISTOGRAM_N_BINS; i++) + { + n += hist->count [i]; + if (n >= (hist->n_values * dividend) / divisor) + break; + } + + return hist->first_bin + i; +} + +static void +small_histogram_get_range (SmallHistogram *hist, gint *min_out, gint *max_out) +{ + gint min, max; + + min = small_histogram_get_quantile (hist, 1, 8); + max = small_histogram_get_quantile (hist, 7, 8); + + if (min_out) + *min_out = min; + if (max_out) + *max_out = max; +} + +static FontLoader * +font_loader_new (void) +{ + return g_new0 (FontLoader, 1); +} + +/* Get a bit from a rendered FreeType glyph bitmap. Going out of bounds is allowed + * and will return zero. + * + * Returns: 1 for inked bits or 0 for uninked. */ +static guint +get_bitmap_bit (const FontLoader *loader, const FT_GlyphSlot slot, + gint i, gint j) +{ + const FT_Bitmap *bm; + gint x, y; + + bm = &slot->bitmap; + x = i - (gint) slot->bitmap_left - (loader->font_width - (slot->advance.x >> 6)) / 2; + y = j - (loader->font_height - (gint) slot->bitmap_top) + (loader->font_height - loader->baseline_ofs); + + if (x < 0 || x >= (gint) bm->width || y < 0 || y >= (gint) bm->rows) + return 0; + + /* MSB first */ + return (bm->buffer [y * bm->pitch + (x / 8)] >> (7 - (x % 8))) & 1; +} + +/* Get the 7th octile values for glyph width, height and baseline. This means 87.5% of + * the glyphs will have values equal to or lower than the returned value. Discarding + * the upper 12.5% prevents outliers from affecting the result. */ +static gboolean +measure_glyphs (FontLoader *loader, gint *width_out, gint *height_out, gint *baseline_out) +{ + FT_ULong glyph_charcode; + FT_UInt glyph_index; + FT_GlyphSlot slot; + SmallHistogram x_adv_hist; + SmallHistogram asc_hist; + SmallHistogram desc_hist; + gint asc_max, desc_max; + gboolean success = FALSE; + + small_histogram_init (&x_adv_hist); + small_histogram_init (&asc_hist); + small_histogram_init (&desc_hist); + + for (glyph_charcode = FT_Get_First_Char (loader->ft_face, &glyph_index); + glyph_index != 0; + glyph_charcode = FT_Get_Next_Char (loader->ft_face, glyph_charcode, &glyph_index)) + { + if (!g_unichar_isprint (glyph_charcode) + || g_unichar_ismark (glyph_charcode)) + continue; + + /* Skip glyphs that are not relevant to this pass */ + if ((loader->pass == FONT_PASS_NARROW && g_unichar_iswide (glyph_charcode)) + || (loader->pass == FONT_PASS_WIDE && !g_unichar_iswide (glyph_charcode))) + continue; + + /* FIXME: No need to render? */ + if (FT_Load_Glyph (loader->ft_face, glyph_index, FT_LOAD_RENDER | FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO)) + continue; + + slot = loader->ft_face->glyph; + + small_histogram_add (&x_adv_hist, slot->advance.x / 64 > 0 + ? slot->advance.x / 64 : slot->bitmap_left + (gint) slot->bitmap.width); + small_histogram_add (&asc_hist, slot->bitmap_top); + small_histogram_add (&desc_hist, (gint) slot->bitmap.rows - (gint) slot->bitmap_top); + } + + if (x_adv_hist.n_values == 0) + goto out; + + small_histogram_get_range (&x_adv_hist, NULL, width_out); + small_histogram_get_range (&asc_hist, NULL, &asc_max); + small_histogram_get_range (&desc_hist, NULL, &desc_max); + + if (height_out) + *height_out = asc_max + desc_max; + if (baseline_out) + *baseline_out = asc_max; + + success = TRUE; + +out: + return success; +} + +/* Find a pixel size that produces rendered symbols matching our ideal + * size as closely as possible. Due to variable-width fonts, precise + * font sizes being unavailable, etc. we do this by probing and + * simple statistical analysis. + * + * We start with an initial guess and change our request in increments + * until we either hit our desired size or exceed it. Then we back off + * once. This is done in both dimensions simultaneously. */ +static gboolean +find_best_pixel_size_scalable (FontLoader *loader, gint target_width) +{ + gboolean success = FALSE; + gint req_width = REQ_WIDTH_DEFAULT; + gint req_height = REQ_HEIGHT_DEFAULT; + gint width = 0, height = 0, baseline; + gint width_chg = 0, height_chg = 0; + + while ((width != target_width && width_chg != 3) + || (height != CHAFA_SYMBOL_HEIGHT_PIXELS && height_chg != 3)) + { + if (FT_Set_Pixel_Sizes (loader->ft_face, req_width, req_height)) + goto out; + + if (!measure_glyphs (loader, &width, &height, &baseline)) + goto out; + + if (width < target_width) + { req_width++; width_chg |= 1; } + if (width > target_width) + { req_width--; width_chg |= 2; } + + if (height < CHAFA_SYMBOL_HEIGHT_PIXELS) + { req_height++; height_chg |= 1; } + if (height > CHAFA_SYMBOL_HEIGHT_PIXELS) + { req_height--; height_chg |= 2; } + } + + /* If we can't get the exact size we want, make sure we get something + * slightly bigger instead of slightly smaller. */ + + while (height < CHAFA_SYMBOL_HEIGHT_PIXELS) + { + req_height++; + if (FT_Set_Pixel_Sizes (loader->ft_face, req_width, req_height)) + goto out; + + if (!measure_glyphs (loader, &width, &height, &baseline)) + goto out; + } + + loader->font_width = width; + loader->font_height = height; + loader->baseline_ofs = baseline; + success = TRUE; + +out: + return success; +} + +/* See the description of find_best_pixel_size_scalable() for the overall + * strategy used here. */ +static gboolean +find_best_pixel_size_fixed (FontLoader *loader, gint target_width) +{ + gboolean success = FALSE; + const FT_Bitmap_Size *avsz = loader->ft_face->available_sizes; + gint best_width = 0, best_height = 0, best_baseline = 0; + gint i; + + if (!loader->ft_face->available_sizes) + goto out; + + for (i = 0; i < loader->ft_face->num_fixed_sizes; i++) + { + gint width, height, baseline; + + if (FT_Set_Pixel_Sizes (loader->ft_face, avsz [i].width, avsz [i].height)) + continue; + + if (!measure_glyphs (loader, &width, &height, &baseline)) + goto out; + + /* Prefer strikes bigger than and as close as possible to actual target size */ + if (((best_width < target_width || best_height < CHAFA_SYMBOL_HEIGHT_PIXELS) + && (width >= best_width && height >= best_height)) + || ((best_width > target_width || best_height > CHAFA_SYMBOL_HEIGHT_PIXELS) + && (width >= target_width && height >= CHAFA_SYMBOL_HEIGHT_PIXELS) + && (width < best_width || height < best_height))) + { + best_width = width; + best_height = height; + best_baseline = baseline; + } + } + + if (best_width == 0 || best_height == 0) + goto out; + + if (FT_Set_Pixel_Sizes (loader->ft_face, best_width, best_height)) + goto out; + + loader->font_width = best_width; + loader->font_height = best_height; + loader->baseline_ofs = best_baseline; + + success = TRUE; + +out: + return success; +} + +static gboolean +begin_pass (FontLoader *loader, FontPass pass) +{ + loader->pass = pass; + + if (pass == FONT_PASS_NARROW) + { + if (!find_best_pixel_size_scalable (loader, CHAFA_SYMBOL_WIDTH_PIXELS) + && !find_best_pixel_size_fixed (loader, CHAFA_SYMBOL_WIDTH_PIXELS)) + return FALSE; + } + else if (pass == FONT_PASS_WIDE) + { + if (!find_best_pixel_size_scalable (loader, CHAFA_SYMBOL_WIDTH_PIXELS * 2) + && !find_best_pixel_size_fixed (loader, CHAFA_SYMBOL_WIDTH_PIXELS * 2)) + return FALSE; + } + else + { + g_assert_not_reached (); + } + + return TRUE; +} + +static gboolean +next_pass (FontLoader *loader) +{ + loader->n_glyphs_read = 0; + + if (loader->pass == FONT_PASS_NARROW) + return begin_pass (loader, FONT_PASS_WIDE); + + return FALSE; +} + +FontLoader * +font_loader_new_from_mapping (FileMapping *mapping) +{ + FontLoader *loader = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (mapping != NULL, NULL); + + loader = font_loader_new (); + loader->mapping = mapping; + + loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); + if (!loader->file_data) + goto out; + + if (FT_Init_FreeType (&loader->ft_lib) != 0) + goto out; + + if (FT_New_Memory_Face (loader->ft_lib, + loader->file_data, + loader->file_data_len, + 0, /* face index */ + &loader->ft_face)) + goto out; + + if (!begin_pass (loader, FONT_PASS_NARROW) + && !begin_pass (loader, FONT_PASS_WIDE)) + goto out; + + success = TRUE; + +out: + if (!success) + { + if (loader) + { + font_loader_destroy (loader); + loader = NULL; + } + } + + return loader; +} + +void +font_loader_destroy (FontLoader *loader) +{ + if (loader->mapping) + file_mapping_destroy (loader->mapping); + + if (loader->ft_face) + FT_Done_Face (loader->ft_face); + + if (loader->ft_lib) + FT_Done_FreeType (loader->ft_lib); + + g_free (loader); +} + +static void +generate_glyph (const FontLoader *loader, const FT_GlyphSlot slot, + gpointer *glyph_out, gint *width_out, gint *height_out) +{ + guint8 *glyph_data; + gint i, j; + const guint8 val [2] = { 0x00, 0xff }; + + glyph_data = g_malloc (loader->font_width * loader->font_height * 4); + + for (j = 0; j < loader->font_height; j++) + { + for (i = 0; i < loader->font_width; i++) + { + guint b = get_bitmap_bit (loader, slot, i, j); + + if (b) + { + DEBUG (g_printerr ("XX")); + } + else + { + DEBUG (g_printerr ("..")); + } + + glyph_data [(j * loader->font_width + i) * 4] = val [b]; + glyph_data [(j * loader->font_width + i) * 4 + 1] = val [b]; + glyph_data [(j * loader->font_width + i) * 4 + 2] = val [b]; + glyph_data [(j * loader->font_width + i) * 4 + 3] = val [b]; + } + + DEBUG (g_printerr ("\n")); + } + + DEBUG (g_printerr ("\n")); + + *glyph_out = glyph_data; + *width_out = loader->font_width; + *height_out = loader->font_height; +} + +/* Load a glyph to an RGBA8 output buffer of fixed size CHAFA_SYMBOL_WIDTH_PIXELS * + * CHAFA_SYMBOL_HEIGHT_PIXELS. Each pixel will be set to either 0xffffffff (inked) + * or 0x00000000 (uninked). */ +gboolean +font_loader_get_next_glyph (FontLoader *loader, gunichar *char_out, + gpointer *glyph_out, gint *width_out, gint *height_out) +{ + FT_GlyphSlot slot; + gboolean success = FALSE; + FT_UInt glyph_index = 0; + + slot = loader->ft_face->glyph; + + while (!glyph_index) + { + if (loader->n_glyphs_read == 0) + { + loader->glyph_charcode = FT_Get_First_Char (loader->ft_face, &glyph_index); + } + else + { + loader->glyph_charcode = FT_Get_Next_Char (loader->ft_face, loader->glyph_charcode, &glyph_index); + } + + if (!glyph_index) + { + if (next_pass (loader)) + continue; + break; + } + + loader->n_glyphs_read++; + + /* Skip glyphs that are not relevant to this pass */ + if (!g_unichar_isprint (loader->glyph_charcode) + || g_unichar_ismark (loader->glyph_charcode) + || (loader->pass == FONT_PASS_NARROW && g_unichar_iswide (loader->glyph_charcode)) + || (loader->pass == FONT_PASS_WIDE && !g_unichar_iswide (loader->glyph_charcode))) + glyph_index = 0; + } + + if (!glyph_index) + goto out; + + if (FT_Load_Glyph (loader->ft_face, glyph_index, FT_LOAD_RENDER | FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO)) + goto out; + + generate_glyph (loader, slot, glyph_out, width_out, height_out); + *char_out = loader->glyph_charcode; + + DEBUG (g_printerr ("Loaded symbol %04x: %dx%d -> %dx%d (ofs %d,%d bmsize %dx%d xadv %d/64=%d)\n", + *char_out, + loader->font_width, loader->font_height, + *width_out, *height_out, + slot->bitmap_left, slot->bitmap_top, + slot->bitmap.width, slot->bitmap.rows, + (gint) slot->advance.x, + (gint) slot->advance.x >> 6)); + + success = TRUE; + +out: + return success; +} diff -Nru chafa-1.2.1/tools/chafa/font-loader.h chafa-1.12.4/tools/chafa/font-loader.h --- chafa-1.2.1/tools/chafa/font-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/font-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2019-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __FONT_LOADER_H__ +#define __FONT_LOADER_H__ + +#include +#include "file-mapping.h" + +G_BEGIN_DECLS + +typedef struct FontLoader FontLoader; + +FontLoader *font_loader_new_from_mapping (FileMapping *mapping); +void font_loader_destroy (FontLoader *loader); + +gboolean font_loader_get_next_glyph (FontLoader *loader, gunichar *char_out, + gpointer *glyph_out, gint *width_out, gint *height_out); + +G_END_DECLS + +#endif /* __FONT_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/gif-loader.c chafa-1.12.4/tools/chafa/gif-loader.c --- chafa-1.2.1/tools/chafa/gif-loader.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/gif-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -28,6 +28,7 @@ #include #include +#include #include #include "gif-loader.h" @@ -44,18 +45,32 @@ gint current_frame_index; guint gif_is_initialized : 1; guint frame_is_decoded : 1; + guint frame_is_success : 1; }; static void * bitmap_create (int width, int height) { - /* ensure a stupidly large bitmap is not created */ - if (((long long) width * (long long) height) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL)) + if ((width * (gint64) height) > (MAX_IMAGE_BYTES / BYTES_PER_PIXEL)) return NULL; return g_malloc0 (width * height * BYTES_PER_PIXEL); } +static gboolean +maybe_decode_frame (GifLoader *loader) +{ + gif_result code; + + if (loader->frame_is_decoded) + return loader->frame_is_success; + + code = gif_decode_frame (&loader->gif, loader->current_frame_index); + loader->frame_is_success = (code == GIF_OK ? TRUE : FALSE); + + return loader->frame_is_success; +} + static void bitmap_set_opaque (void *bitmap, bool opaque) { @@ -117,7 +132,8 @@ g_return_val_if_fail (mapping != NULL, NULL); - if (!file_mapping_has_magic (mapping, 0, "GIF89a", 6)) + if (!file_mapping_has_magic (mapping, 0, "GIF89a", 6) + && !file_mapping_has_magic (mapping, 0, "GIF87a", 6)) goto out; loader = gif_loader_new (); @@ -169,47 +185,63 @@ g_free (loader); } -void -gif_loader_get_geometry (GifLoader *loader, gint *width_out, gint *height_out) -{ - g_return_if_fail (loader != NULL); - g_return_if_fail (loader->gif_is_initialized); - - *width_out = loader->gif.width; - *height_out = loader->gif.height; -} - -gint -gif_loader_get_n_frames (GifLoader *loader) +gboolean +gif_loader_get_is_animation (GifLoader *loader) { g_return_val_if_fail (loader != NULL, 0); g_return_val_if_fail (loader->gif_is_initialized, 0); - return loader->gif.frame_count; + return loader->gif.frame_count > 1 ? TRUE : FALSE; } -const guint8 * -gif_loader_get_frame_data (GifLoader *loader, gint *post_frame_delay_hs_out) +gconstpointer +gif_loader_get_frame_data (GifLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); g_return_val_if_fail (loader->gif_is_initialized, NULL); - if (!loader->frame_is_decoded) - { - gif_result code = gif_decode_frame (&loader->gif, loader->current_frame_index); - if (code != GIF_OK) - return NULL; - } + if (!maybe_decode_frame (loader)) + return NULL; - loader->frame_is_decoded = TRUE; + if (width_out) + *width_out = loader->gif.width; + if (height_out) + *height_out = loader->gif.height; + if (pixel_type_out) + *pixel_type_out = CHAFA_PIXEL_RGBA8_UNASSOCIATED; + if (rowstride_out) + *rowstride_out = loader->gif.width * 4; - if (post_frame_delay_hs_out) - *post_frame_delay_hs_out = loader->gif.frames [loader->current_frame_index].frame_delay; return loader->gif.frame_image; } +gint +gif_loader_get_frame_delay (GifLoader *loader) +{ + gint frame_delay_ms; + + g_return_val_if_fail (loader != NULL, 0); + g_return_val_if_fail (loader->gif_is_initialized, 0); + + if (!maybe_decode_frame (loader)) + return 0; + + frame_delay_ms = loader->gif.frames [loader->current_frame_index].frame_delay; + + /* Convert from centiseconds to milliseconds */ + frame_delay_ms *= 10; + + /* It's common for GIF animations to omit the frame delays. If it looks like that's + * what's happening, go with a 20fps default. */ + if (frame_delay_ms == 0) + frame_delay_ms = 50; + + return frame_delay_ms; +} + void -gif_loader_first_frame (GifLoader *loader) +gif_loader_goto_first_frame (GifLoader *loader) { g_return_if_fail (loader != NULL); g_return_if_fail (loader->gif_is_initialized); @@ -219,10 +251,11 @@ loader->current_frame_index = 0; loader->frame_is_decoded = FALSE; + loader->frame_is_success = FALSE; } gboolean -gif_loader_next_frame (GifLoader *loader) +gif_loader_goto_next_frame (GifLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); g_return_val_if_fail (loader->gif_is_initialized, FALSE); diff -Nru chafa-1.2.1/tools/chafa/gif-loader.h chafa-1.12.4/tools/chafa/gif-loader.h --- chafa-1.2.1/tools/chafa/gif-loader.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/gif-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -30,13 +30,14 @@ GifLoader *gif_loader_new_from_mapping (FileMapping *mapping); void gif_loader_destroy (GifLoader *loader); -void gif_loader_get_geometry (GifLoader *loader, gint *width_out, gint *height_out); -gint gif_loader_get_n_frames (GifLoader *loader); +gboolean gif_loader_get_is_animation (GifLoader *loader); -const guint8 *gif_loader_get_frame_data (GifLoader *loader, gint *post_frame_delay_hs_out); +gconstpointer gif_loader_get_frame_data (GifLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint gif_loader_get_frame_delay (GifLoader *loader); -void gif_loader_first_frame (GifLoader *loader); -gboolean gif_loader_next_frame (GifLoader *loader); +void gif_loader_goto_first_frame (GifLoader *loader); +gboolean gif_loader_goto_next_frame (GifLoader *loader); G_END_DECLS diff -Nru chafa-1.2.1/tools/chafa/im-loader.c chafa-1.12.4/tools/chafa/im-loader.c --- chafa-1.2.1/tools/chafa/im-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/im-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,301 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_WAND_MAGICKWAND_H +# include +#else /* HAVE_MAGICKWAND_MAGICKWAND_H */ +# include +#endif + +#include +#include "im-loader.h" + +struct ImLoader +{ + MagickWand *wand; + gpointer current_frame_data; +}; + +static void +clear_current_frame_data (ImLoader *loader) +{ + g_free (loader->current_frame_data); + loader->current_frame_data = NULL; +} + +static void +auto_orient_image (MagickWand *image) +{ +#ifdef HAVE_MAGICK_AUTO_ORIENT_IMAGE + MagickAutoOrientImage (image); +#else + PixelWand *pwand = NULL; + + switch (MagickGetImageOrientation (image)) + { + case UndefinedOrientation: + case TopLeftOrientation: + default: + break; + case TopRightOrientation: + MagickFlopImage (image); + break; + case BottomRightOrientation: + pwand = NewPixelWand (); + MagickRotateImage (image, pwand, 180.0); + break; + case BottomLeftOrientation: + MagickFlipImage (image); + break; + case LeftTopOrientation: + MagickTransposeImage (image); + break; + case RightTopOrientation: + pwand = NewPixelWand (); + MagickRotateImage (image, pwand, 90.0); + break; + case RightBottomOrientation: + MagickTransverseImage (image); + break; + case LeftBottomOrientation: + pwand = NewPixelWand (); + MagickRotateImage (image, pwand, 270.0); + break; + } + + if (pwand) + DestroyPixelWand (pwand); + + MagickSetImageOrientation (image, TopLeftOrientation); +#endif +} + +static gint active_count = 0; + +static void +active_count_inc (void) +{ + if (active_count == 0) + { + MagickWandGenesis (); + } + + active_count++; +} + +static void +active_count_dec (void) +{ + active_count--; + + if (active_count == 0) + { +#if 0 + /* FIXME: Do this once at program exit only */ + MagickWandTerminus (); +#endif + } +} + +ImLoader * +im_loader_new (const gchar *path) +{ + PixelWand *color; + ImLoader *loader; + gboolean is_animation; + gboolean success = FALSE; + + g_return_val_if_fail (path != NULL, NULL); + + active_count_inc (); + + loader = g_new0 (ImLoader, 1); + + loader->wand = NewMagickWand (); + + color = NewPixelWand (); + PixelSetColor (color, "none"); + MagickSetBackgroundColor (loader->wand, color); + DestroyPixelWand (color); + + if (MagickReadImage (loader->wand, path) < 1) + { + gchar *error_str = NULL; + ExceptionType severity; + gchar *try_path; + gint r; + + error_str = MagickGetException (loader->wand, &severity); + + /* Try backup strategy for XWD. It's a file type we want to support + * due to the fun implications with Xvfb etc. The paths in use + * tend to have no extension, and the file magic isn't very definite, + * so ImageMagick doesn't know what to do on its own. */ + try_path = g_strdup_printf ("XWD:%s", path); + r = MagickReadImage (loader->wand, try_path); + g_free (try_path); + + if (r < 1) + { +#if 0 + if (!quiet) + g_printerr ("%s: Error loading '%s': %s\n", + options.executable_name, + path, + error_str); +#endif + MagickRelinquishMemory (error_str); + goto out; + } + } + + is_animation = MagickGetNumberImages (loader->wand) > 1 ? TRUE : FALSE; + + if (is_animation) + { + MagickWand *wand2 = MagickCoalesceImages (loader->wand); + loader->wand = DestroyMagickWand (loader->wand); + loader->wand = wand2; + } + + MagickResetIterator (loader->wand); + MagickNextImage (loader->wand); /* ? */ + + success = TRUE; + +out: + if (!success) + { + im_loader_destroy (loader); + loader = NULL; + } + + return loader; +} + +void +im_loader_destroy (ImLoader *loader) +{ + clear_current_frame_data (loader); + + if (loader->wand) + { + DestroyMagickWand (loader->wand); + loader->wand = NULL; + } + + g_free (loader); + + active_count_dec (); +} + +gboolean +im_loader_get_is_animation (ImLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + return MagickGetNumberImages (loader->wand) > 1 ? TRUE : FALSE; +} + +gconstpointer +im_loader_get_frame_data (ImLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + gint width, height, rowstride; + + g_return_val_if_fail (loader != NULL, NULL); + + auto_orient_image (loader->wand); + + width = MagickGetImageWidth (loader->wand); + height = MagickGetImageHeight (loader->wand); + rowstride = width * 4; + + if (width < 1 || width >= (1 << 28) + || height < 1 || height >= (1 << 28) + || (width * (guint64) height >= (1 << 29))) + goto out; + + if (!loader->current_frame_data) + { + loader->current_frame_data = g_malloc (height * (guint64) rowstride); + MagickExportImagePixels (loader->wand, + 0, 0, + width, height, + "RGBA", + CharPixel, + (void *) loader->current_frame_data); + } + + if (pixel_type_out) + *pixel_type_out = CHAFA_PIXEL_RGBA8_UNASSOCIATED; + if (width_out) + *width_out = width; + if (height_out) + *height_out = height; + if (rowstride_out) + *rowstride_out = rowstride; + +out: + return loader->current_frame_data; +} + +gint +im_loader_get_frame_delay (ImLoader *loader) +{ + gint delay_ms; + + g_return_val_if_fail (loader != NULL, 0); + + delay_ms = MagickGetImageDelay (loader->wand) * 10; + if (delay_ms == 0) + delay_ms = 50; + + return delay_ms; +} + +void +im_loader_goto_first_frame (ImLoader *loader) +{ + g_return_if_fail (loader != NULL); + + clear_current_frame_data (loader); + MagickResetIterator (loader->wand); + MagickNextImage (loader->wand); /* ? */ +} + +gboolean +im_loader_goto_next_frame (ImLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + clear_current_frame_data (loader); + return MagickNextImage (loader->wand) ? TRUE : FALSE; +} diff -Nru chafa-1.2.1/tools/chafa/im-loader.h chafa-1.12.4/tools/chafa/im-loader.h --- chafa-1.2.1/tools/chafa/im-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/im-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __IM_LOADER_H__ +#define __IM_LOADER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct ImLoader ImLoader; + +ImLoader *im_loader_new (const gchar *path); +void im_loader_destroy (ImLoader *loader); + +gboolean im_loader_get_is_animation (ImLoader *loader); + +gconstpointer im_loader_get_frame_data (ImLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint im_loader_get_frame_delay (ImLoader *loader); + +void im_loader_goto_first_frame (ImLoader *loader); +gboolean im_loader_goto_next_frame (ImLoader *loader); + +G_END_DECLS + +#endif /* __IM_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/jpeg-loader.c chafa-1.12.4/tools/chafa/jpeg-loader.c --- chafa-1.2.1/tools/chafa/jpeg-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/jpeg-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,603 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include + +#include +#include + +#include +#include "jpeg-loader.h" + +/* ----------------------- * + * Global macros and types * + * ----------------------- */ + +#undef ENABLE_DEBUG + +#define BYTES_PER_PIXEL 3 +#define ROWSTRIDE_ALIGN 16 + +#define PAD_TO_N(p, n) (((p) + ((n) - 1)) & ~((unsigned) (n) - 1)) +#define ROWSTRIDE_PAD(rowstride) (PAD_TO_N ((rowstride), (ROWSTRIDE_ALIGN))) + +struct JpegLoader +{ + FileMapping *mapping; + const guint8 *file_data; + size_t file_data_len; + gpointer frame_data; + gint width, height, rowstride; +}; + +/* ------------------- * + * Rotation transforms * + * ------------------- */ + +typedef enum +{ + ROTATION_NONE = 0, + ROTATION_0 = 1, + ROTATION_0_MIRROR = 2, + ROTATION_180 = 3, + ROTATION_180_MIRROR = 4, + ROTATION_270_MIRROR = 5, + ROTATION_270 = 6, + ROTATION_90_MIRROR = 7, + ROTATION_90 = 8, + ROTATION_UNDEFINED = 9, + + ROTATION_MAX +} +RotationType; + +static RotationType +undo_rotation (RotationType rot) +{ + switch (rot) + { + case ROTATION_90: + rot = ROTATION_270; + break; + case ROTATION_270: + rot = ROTATION_90; + break; + default: + break; + } + + return rot; +} + +static void +transform (const guchar *src, gint src_pixstride, gint src_rowstride, + guchar *dest, gint dest_pixstride, gint dest_rowstride, + gint src_width, gint src_height, gint pixsize) +{ + const guchar *src_row = src; + const guchar *src_row_end = src + src_pixstride * src_width; + const guchar *src_end = src + src_rowstride * src_height; + guchar *dest_row = dest; + + while (src_row != src_end) + { + src = src_row; + dest = dest_row; + + while (src != src_row_end) + { + memcpy (dest, src, pixsize); + src += src_pixstride; + dest += dest_pixstride; + } + + src_row += src_rowstride; + src_row_end += src_rowstride; + dest_row += dest_rowstride; + } +} + +static void +rotate_frame (guchar **src, guint *width, guint *height, guint *rowstride, guint n_channels, + RotationType rot) +{ + gint src_width, src_height; + gint src_pixstride, src_rowstride; + guchar *dest, *dest_start; + gint dest_width, dest_height; + gint dest_pixstride, dest_rowstride, dest_trans_rowstride; + + g_assert (n_channels == 3 || n_channels == 4); + + if (rot <= ROTATION_NONE + || rot == ROTATION_0 + || rot >= ROTATION_UNDEFINED) + return; + + src_width = *width; + src_height = *height; + src_pixstride = n_channels; + src_rowstride = *rowstride; + + switch (rot) + { + case ROTATION_90: + case ROTATION_90_MIRROR: + case ROTATION_270: + case ROTATION_270_MIRROR: + dest_width = src_height; + dest_height = src_width; + break; + case ROTATION_0_MIRROR: + case ROTATION_180: + case ROTATION_180_MIRROR: + dest_width = src_width; + dest_height = src_height; + break; + default: + g_assert_not_reached (); + } + + dest_rowstride = ROWSTRIDE_PAD (dest_width * n_channels); + dest = g_malloc (dest_rowstride * dest_height); + + switch (rot) + { + case ROTATION_0_MIRROR: + dest_pixstride = -n_channels; + dest_trans_rowstride = dest_rowstride; + dest_start = dest + ((dest_width - 1) * n_channels); + break; + case ROTATION_90: + dest_pixstride = dest_rowstride; + dest_trans_rowstride = -n_channels; + dest_start = dest + ((dest_width - 1) * n_channels); + break; + case ROTATION_90_MIRROR: + dest_pixstride = -dest_rowstride; + dest_trans_rowstride = -n_channels; + dest_start = dest + ((dest_height - 1) * dest_rowstride) + ((dest_width - 1) * n_channels); + break; + case ROTATION_180: + dest_pixstride = -n_channels; + dest_trans_rowstride = -dest_rowstride; + dest_start = dest + ((dest_height - 1) * dest_rowstride) + ((dest_width - 1) * n_channels); + break; + case ROTATION_180_MIRROR: + dest_pixstride = n_channels; + dest_trans_rowstride = -dest_rowstride; + dest_start = dest + ((dest_height - 1) * dest_rowstride); + break; + case ROTATION_270: + dest_pixstride = -dest_rowstride; + dest_trans_rowstride = n_channels; + dest_start = dest + ((dest_height - 1) * dest_rowstride); + break; + case ROTATION_270_MIRROR: + dest_pixstride = dest_rowstride; + dest_trans_rowstride = n_channels; + dest_start = dest; + break; + default: + g_assert_not_reached (); + } + + transform (*src, src_pixstride, src_rowstride, + dest_start, dest_pixstride, dest_trans_rowstride, + src_width, src_height, n_channels); + + g_free (*src); + + *src = dest; + *width = dest_width; + *height = dest_height; + *rowstride = dest_rowstride; +} + +/* ----------------------- * + * Exif orientation reader * + * ----------------------- */ + +static guint16 +read_uint16 (const guchar *p, gboolean is_big_endian) +{ + return is_big_endian ? + ((p [0] << 8) | p [1]) : + ((p [1] << 8) | p [0]); +} + +static guint32 +read_uint32 (const guchar *p, gboolean is_big_endian) +{ + return is_big_endian ? + ((p [0] << 24) | (p [1] << 16) | (p [2] << 8) | p [3]) : + ((p [3] << 24) | (p [2] << 16) | (p [1] << 8) | p [0]); +} + +static RotationType +read_orientation (JpegLoader *loader) +{ + const guchar *p0, *end; + size_t len; + RotationType rot = ROTATION_NONE; + gboolean is_big_endian; + guint n, m, o; + + p0 = loader->file_data; + len = loader->file_data_len; + end = p0 + len; + + /* Assume we already checked the JPEG header. Now find the Exif header. */ + p0 += 2; + + for (;;) + { + if (p0 + 20 > end) + goto out; + + /* Get app type */ + if (read_uint16 (p0, TRUE) < 0xffdb) + goto out; + p0 += 2; + + /* Get marker length; note length field includes itself */ + n = read_uint16 (p0, TRUE); + if (n < 2 || p0 + n > end) + goto out; + + if (!memcmp (p0 + 2, "Exif\0\0", 6)) + { + p0 += 8; + break; + } + + /* Not an Exif marker; skip it */ + p0 += n; + } + + /* Get byte order */ + m = read_uint16 (p0, TRUE); + if (m == 0x4949) + is_big_endian = FALSE; + else if (m == 0x4d4d) + is_big_endian = TRUE; + else + goto out; + + /* Tag mark */ + if (read_uint16 (p0 + 2, is_big_endian) != 0x002a) + goto out; + + /* First IFD offset */ + m = read_uint32 (p0 + 4, is_big_endian); + if (m > 0xffff) + goto out; + if (p0 + m + 2 > end) + goto out; + if (m + 2 > n) + goto out; + + /* Number of directory entries in this IFD */ + o = read_uint16 (p0 + m, is_big_endian); + m += 2; + + for (;;) + { + guint16 tagnum; + + if (!o) + goto out; + if (p0 + m + 12 > end) + goto out; + if (m + 12 > n) + goto out; + + tagnum = read_uint16 (p0 + m, is_big_endian); + if (tagnum == 0x0112) + break; + + o--; + m += 12; + } + + m = read_uint16 (p0 + m + 8, is_big_endian); + if (m > 9) + goto out; + + rot = m; + +out: + return rot; +} + +/* ----------- * + * JPEG loader * + * ----------- */ + +/* --- Memory source --- */ + +/* libjpeg-turbo can provide jpeg_mem_src (), and usually does. However, we + * supply our own for strict conformance with libjpeg v6b. */ + +static void +init_source (G_GNUC_UNUSED j_decompress_ptr cinfo) +{ +} + +static boolean +fill_input_buffer (j_decompress_ptr cinfo) +{ + ERREXIT (cinfo, JERR_INPUT_EMPTY); + return TRUE; +} + +static void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + struct jpeg_source_mgr *src = (struct jpeg_source_mgr *) cinfo->src; + + if (num_bytes < 0) + return; + + num_bytes = MIN ((size_t) num_bytes, src->bytes_in_buffer); + + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; +} + +static void +term_source (G_GNUC_UNUSED j_decompress_ptr cinfo) +{ +} + +static void +my_jpeg_mem_src (j_decompress_ptr cinfo, const void *buffer, long nbytes) +{ + struct jpeg_source_mgr *src; + + if (cinfo->src == NULL) + { + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof (struct jpeg_source_mgr)); + } + + src = (struct jpeg_source_mgr *) cinfo->src; + src->init_source = init_source; + src->fill_input_buffer = fill_input_buffer; + src->skip_input_data = skip_input_data; + src->resync_to_restart = jpeg_resync_to_restart; /* Use default method */ + src->term_source = term_source; + src->bytes_in_buffer = nbytes; + src->next_input_byte = (JOCTET *) buffer; +} + +/* --- Error handler --- */ + +struct my_jpeg_error_mgr +{ + struct jpeg_error_mgr jerr; + jmp_buf setjmp_buffer; +}; + +static void +my_jpeg_error_exit (j_common_ptr cinfo) +{ + struct my_jpeg_error_mgr *my_jerr = (struct my_jpeg_error_mgr *) cinfo->err; + + longjmp (my_jerr->setjmp_buffer, 1); +} + +/* --- Magic probe --- */ + +static gboolean +have_any_apptype_magic (FileMapping *mapping) +{ + guchar magic [4] = { 0xff, 0xd8, 0xff, 0x00 }; + guchar n; + + for (n = 0xe0; n <= 0xef; n++) + { + magic [3] = n; + if (file_mapping_has_magic (mapping, 0, magic, 4)) + return TRUE; + } + + magic [3] = 0xdb; + return file_mapping_has_magic (mapping, 0, magic, 4); +} + +/* --- Loader --- */ + +static JpegLoader * +jpeg_loader_new (void) +{ + return g_new0 (JpegLoader, 1); +} + +JpegLoader * +jpeg_loader_new_from_mapping (FileMapping *mapping) +{ + guint width, height; + guint rowstride; + struct jpeg_decompress_struct cinfo; + struct my_jpeg_error_mgr my_jerr; + JpegLoader * volatile loader = NULL; + unsigned char * volatile frame_data = NULL; + volatile gboolean have_decompress = FALSE; + volatile gboolean success = FALSE; + + g_return_val_if_fail (mapping != NULL, NULL); + + /* Check magic */ + + if (!have_any_apptype_magic (mapping)) + goto out; + + loader = jpeg_loader_new (); + loader->mapping = mapping; + + /* Get file data */ + + loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); + if (!loader->file_data) + goto out; + + /* Prepare to decode */ + + cinfo.err = jpeg_std_error ((struct jpeg_error_mgr *) &my_jerr); + my_jerr.jerr.error_exit = my_jpeg_error_exit; + if (setjmp (my_jerr.setjmp_buffer)) + { +#ifdef ENABLE_DEBUG + static gchar jpeg_error_msg [JMSG_LENGTH_MAX]; + + (*cinfo.err->format_message) ((j_common_ptr) &cinfo, jpeg_error_msg); + g_printerr ("JPEG error: %s\n", jpeg_error_msg); +#endif + goto out; + } + + jpeg_create_decompress (&cinfo); + have_decompress = TRUE; + + my_jpeg_mem_src (&cinfo, loader->file_data, loader->file_data_len); + (void) jpeg_read_header (&cinfo, TRUE); + + cinfo.out_color_space = JCS_RGB; + cinfo.output_components = 3; + + jpeg_start_decompress (&cinfo); + + width = cinfo.output_width; + height = cinfo.output_height; + + if (width < 1 || width >= (1 << 28) + || height < 1 || height >= (1 << 28) + || (width * (guint64) height >= (1 << 29))) + goto out; + + rowstride = ROWSTRIDE_PAD (width * BYTES_PER_PIXEL); + frame_data = g_malloc (height * (guint64) rowstride); + + /* Decoding loop */ + + while (cinfo.output_scanline < height) + { + guchar *row_data = frame_data + cinfo.output_scanline * rowstride; + if (jpeg_read_scanlines (&cinfo, &row_data, 1) < 1) + goto out; + } + + /* Orientation and cleanup */ + + (void) jpeg_finish_decompress (&cinfo); + + rotate_frame ((guchar **) &frame_data, &width, &height, &rowstride, 3, + undo_rotation (read_orientation (loader))); + + loader->frame_data = frame_data; + loader->width = (gint) width; + loader->height = (gint) height; + loader->rowstride = (gint) rowstride; + + success = TRUE; + +out: + if (have_decompress) + jpeg_destroy_decompress (&cinfo); + + if (!success) + { + if (frame_data) + g_free (frame_data); + + if (loader) + { + g_free (loader); + loader = NULL; + } + } + + return loader; +} + +void +jpeg_loader_destroy (JpegLoader *loader) +{ + if (loader->mapping) + file_mapping_destroy (loader->mapping); + + if (loader->frame_data) + free (loader->frame_data); + + g_free (loader); +} + +gboolean +jpeg_loader_get_is_animation (JpegLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return FALSE; +} + +gconstpointer +jpeg_loader_get_frame_data (JpegLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + g_return_val_if_fail (loader != NULL, NULL); + + if (pixel_type_out) + *pixel_type_out = CHAFA_PIXEL_RGB8; + if (width_out) + *width_out = loader->width; + if (height_out) + *height_out = loader->height; + if (rowstride_out) + *rowstride_out = loader->rowstride; + + return loader->frame_data; +} + +gint +jpeg_loader_get_frame_delay (JpegLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return 0; +} + +void +jpeg_loader_goto_first_frame (JpegLoader *loader) +{ + g_return_if_fail (loader != NULL); +} + +gboolean +jpeg_loader_goto_next_frame (JpegLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + return FALSE; +} diff -Nru chafa-1.2.1/tools/chafa/jpeg-loader.h chafa-1.12.4/tools/chafa/jpeg-loader.h --- chafa-1.2.1/tools/chafa/jpeg-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/jpeg-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __JPEG_LOADER_H__ +#define __JPEG_LOADER_H__ + +#include +#include "file-mapping.h" + +G_BEGIN_DECLS + +typedef struct JpegLoader JpegLoader; + +JpegLoader *jpeg_loader_new_from_mapping (FileMapping *mapping); +void jpeg_loader_destroy (JpegLoader *loader); + +gboolean jpeg_loader_get_is_animation (JpegLoader *loader); + +gconstpointer jpeg_loader_get_frame_data (JpegLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint jpeg_loader_get_frame_delay (JpegLoader *loader); + +void jpeg_loader_goto_first_frame (JpegLoader *loader); +gboolean jpeg_loader_goto_next_frame (JpegLoader *loader); + +G_END_DECLS + +#endif /* __JPEG_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/Makefile.am chafa-1.12.4/tools/chafa/Makefile.am --- chafa-1.2.1/tools/chafa/Makefile.am 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/Makefile.am 2022-11-12 01:18:35.000000000 +0000 @@ -7,21 +7,82 @@ chafa.c \ file-mapping.c \ file-mapping.h \ + font-loader.c \ + font-loader.h \ gif-loader.c \ gif-loader.h \ + media-loader.c \ + media-loader.h \ + png-loader.c \ + png-loader.h \ named-colors.c \ named-colors.h \ xwd-loader.c \ xwd-loader.h -chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(MAGICKWAND_CFLAGS) -chafa_LDFLAGS = $(CHAFA_LDFLAGS) -chafa_LDADD = $(GLIB_LIBS) $(MAGICKWAND_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la +if HAVE_MAGICKWAND +chafa_SOURCES += \ + im-loader.c \ + im-loader.h +endif + +if HAVE_JPEG +chafa_SOURCES += \ + jpeg-loader.c \ + jpeg-loader.h +endif + +if HAVE_TIFF +chafa_SOURCES += \ + tiff-loader.c \ + tiff-loader.h +endif +if HAVE_SVG +chafa_SOURCES += \ + svg-loader.c \ + svg-loader.h endif -## -- General --- +if HAVE_WEBP +chafa_SOURCES += \ + webp-loader.c \ + webp-loader.h +endif + +# We can pass -rpath so the binary knows where to find libchafa.so when +# installed outside /usr (e.g. the default /usr/local). This affects Ubuntu. +# Resolved by running ldconfig. See Github issue #32. +# +# This is disabled by default. + +chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(MAGICKWAND_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(FREETYPE_CFLAGS) +if ENABLE_RPATH +chafa_LDFLAGS = $(CHAFA_LDFLAGS) -rpath $(libdir) +endif +chafa_LDADD = $(GLIB_LIBS) $(MAGICKWAND_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la $(WIN32_LDADD) + +# On Microsoft Windows, we compile a resource file with windres and link it in. +# This enables UTF-8 support in filenames, environment variables, etc. + +if IS_WIN32_BUILD +WIN32_LDADD = manifest.o +manifest.o: $(srcdir)/manifest.rc + $(WINDRES) -o $@ $(srcdir)/manifest.rc +else +WIN32_LDADD = +endif + +endif + +## --- General --- + +## Include $(top_builddir)/chafa to get generated chafaconfig.h. AM_CPPFLAGS = \ -I$(top_srcdir)/chafa \ - -I$(top_srcdir)/libnsgif + -I$(top_builddir)/chafa \ + -I$(top_srcdir)/libnsgif \ + -I$(top_srcdir)/lodepng + +EXTRA_DIST = manifest.rc ms-utf8.xml diff -Nru chafa-1.2.1/tools/chafa/manifest.rc chafa-1.12.4/tools/chafa/manifest.rc --- chafa-1.2.1/tools/chafa/manifest.rc 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/manifest.rc 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,2 @@ +#include +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "ms-utf8.xml" diff -Nru chafa-1.2.1/tools/chafa/media-loader.c chafa-1.12.4/tools/chafa/media-loader.c --- chafa-1.2.1/tools/chafa/media-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/media-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,320 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "file-mapping.h" +#include "gif-loader.h" +#include "im-loader.h" +#include "xwd-loader.h" +#include "jpeg-loader.h" +#include "media-loader.h" +#include "png-loader.h" +#include "svg-loader.h" +#include "tiff-loader.h" +#include "webp-loader.h" + +typedef enum +{ + LOADER_TYPE_GIF, + LOADER_TYPE_PNG, + LOADER_TYPE_XWD, + LOADER_TYPE_JPEG, + LOADER_TYPE_TIFF, + LOADER_TYPE_WEBP, + LOADER_TYPE_SVG, + LOADER_TYPE_IMAGEMAGICK, + + LOADER_TYPE_LAST +} +LoaderType; + +static const struct +{ + const gchar * const name; + gpointer (*new_from_mapping) (gpointer); + gpointer (*new_from_path) (gconstpointer); + void (*destroy) (gpointer); + gboolean (*get_is_animation) (gpointer); + void (*goto_first_frame) (gpointer); + gboolean (*goto_next_frame) (gpointer); + gconstpointer (*get_frame_data) (gpointer, gpointer, gpointer, gpointer, gpointer); + gint (*get_frame_delay) (gpointer); +} +loader_vtable [LOADER_TYPE_LAST] = +{ + [LOADER_TYPE_GIF] = + { + "GIF", + (gpointer (*)(gpointer)) gif_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) gif_loader_destroy, + (gboolean (*)(gpointer)) gif_loader_get_is_animation, + (void (*)(gpointer)) gif_loader_goto_first_frame, + (gboolean (*)(gpointer)) gif_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) gif_loader_get_frame_data, + (gint (*) (gpointer)) gif_loader_get_frame_delay + }, + [LOADER_TYPE_PNG] = + { + "PNG", + (gpointer (*)(gpointer)) png_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) png_loader_destroy, + (gboolean (*)(gpointer)) png_loader_get_is_animation, + (void (*)(gpointer)) png_loader_goto_first_frame, + (gboolean (*)(gpointer)) png_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) png_loader_get_frame_data, + (gint (*) (gpointer)) png_loader_get_frame_delay + }, + [LOADER_TYPE_XWD] = + { + "XWD", + (gpointer (*)(gpointer)) xwd_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) xwd_loader_destroy, + (gboolean (*)(gpointer)) xwd_loader_get_is_animation, + (void (*)(gpointer)) xwd_loader_goto_first_frame, + (gboolean (*)(gpointer)) xwd_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) xwd_loader_get_frame_data, + (gint (*) (gpointer)) xwd_loader_get_frame_delay + }, +#ifdef HAVE_JPEG + [LOADER_TYPE_JPEG] = + { + "JPEG", + (gpointer (*)(gpointer)) jpeg_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) jpeg_loader_destroy, + (gboolean (*)(gpointer)) jpeg_loader_get_is_animation, + (void (*)(gpointer)) jpeg_loader_goto_first_frame, + (gboolean (*)(gpointer)) jpeg_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) jpeg_loader_get_frame_data, + (gint (*) (gpointer)) jpeg_loader_get_frame_delay + }, +#endif +#ifdef HAVE_SVG + [LOADER_TYPE_SVG] = + { + "SVG", + (gpointer (*)(gpointer)) svg_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) svg_loader_destroy, + (gboolean (*)(gpointer)) svg_loader_get_is_animation, + (void (*)(gpointer)) svg_loader_goto_first_frame, + (gboolean (*)(gpointer)) svg_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) svg_loader_get_frame_data, + (gint (*) (gpointer)) svg_loader_get_frame_delay + }, +#endif +#ifdef HAVE_TIFF + [LOADER_TYPE_TIFF] = + { + "TIFF", + (gpointer (*)(gpointer)) tiff_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) tiff_loader_destroy, + (gboolean (*)(gpointer)) tiff_loader_get_is_animation, + (void (*)(gpointer)) tiff_loader_goto_first_frame, + (gboolean (*)(gpointer)) tiff_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) tiff_loader_get_frame_data, + (gint (*) (gpointer)) tiff_loader_get_frame_delay + }, +#endif +#ifdef HAVE_WEBP + [LOADER_TYPE_WEBP] = + { + "WebP", + (gpointer (*)(gpointer)) webp_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) webp_loader_destroy, + (gboolean (*)(gpointer)) webp_loader_get_is_animation, + (void (*)(gpointer)) webp_loader_goto_first_frame, + (gboolean (*)(gpointer)) webp_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) webp_loader_get_frame_data, + (gint (*) (gpointer)) webp_loader_get_frame_delay + }, +#endif +#ifdef HAVE_MAGICKWAND + [LOADER_TYPE_IMAGEMAGICK] = + { + "ImageMagick", + (gpointer (*)(gpointer)) NULL, + (gpointer (*)(gconstpointer)) im_loader_new, + (void (*)(gpointer)) im_loader_destroy, + (gboolean (*)(gpointer)) im_loader_get_is_animation, + (void (*)(gpointer)) im_loader_goto_first_frame, + (gboolean (*)(gpointer)) im_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) im_loader_get_frame_data, + (gint (*) (gpointer)) im_loader_get_frame_delay + }, +#endif +}; + +struct MediaLoader +{ + LoaderType loader_type; + gpointer loader; +}; + +static int +ascii_strcasecmp_ptrs (const void *a, const void *b) +{ + gchar * const *sa = a, * const *sb = b; + + return g_ascii_strcasecmp (*sa, *sb); +} + +MediaLoader * +media_loader_new (const gchar *path, GError **error) +{ + MediaLoader *loader; + FileMapping *mapping = NULL; + gboolean success = FALSE; + gint i; + + g_return_val_if_fail (path != NULL, NULL); + + loader = g_new0 (MediaLoader, 1); + mapping = file_mapping_new (path); + + if (!file_mapping_open_now (mapping, error)) + goto out; + + for (i = 0; i < LOADER_TYPE_LAST && !loader->loader; i++) + { + loader->loader_type = i; + + if (mapping && loader_vtable [i].new_from_mapping) + { + loader->loader = loader_vtable [i].new_from_mapping (mapping); + } + else if (loader_vtable [i].new_from_path) + { + loader->loader = loader_vtable [i].new_from_path (path); + if (loader->loader) + file_mapping_destroy (mapping); + } + + if (loader->loader) + { + /* Mapping was either transferred to the loader or destroyed by us above */ + mapping = NULL; + break; + } + } + + if (!loader->loader) + goto out; + + success = TRUE; + +out: + if (!success) + { + if (mapping) + file_mapping_destroy (mapping); + + media_loader_destroy (loader); + loader = NULL; + + if (error && !*error) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Unknown file format"); + } + } + + return loader; +} + +void +media_loader_destroy (MediaLoader *loader) +{ + if (loader->loader) + { + loader_vtable [loader->loader_type].destroy (loader->loader); + loader->loader = NULL; + } + + g_free (loader); +} + +gboolean +media_loader_get_is_animation (MediaLoader *loader) +{ + return loader_vtable [loader->loader_type].get_is_animation (loader->loader); +} + +void +media_loader_goto_first_frame (MediaLoader *loader) +{ + loader_vtable [loader->loader_type].goto_first_frame (loader->loader); +} + +gboolean +media_loader_goto_next_frame (MediaLoader *loader) +{ + return loader_vtable [loader->loader_type].goto_next_frame (loader->loader); +} + +gconstpointer +media_loader_get_frame_data (MediaLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + return loader_vtable [loader->loader_type].get_frame_data (loader->loader, pixel_type_out, + width_out, height_out, rowstride_out); +} + +gint +media_loader_get_frame_delay (MediaLoader *loader) +{ + return loader_vtable [loader->loader_type].get_frame_delay (loader->loader); +} + +gchar ** +get_loader_names (void) +{ + gchar **strv; + gint i, j; + + strv = g_new0 (gchar *, LOADER_TYPE_LAST + 1); + + for (i = 0, j = 0; i < LOADER_TYPE_LAST; i++) + { + if (loader_vtable [i].name == NULL) + continue; + + strv [j++] = g_strdup (loader_vtable [i].name); + } + + qsort (strv, j, sizeof (gchar *), ascii_strcasecmp_ptrs); + + return strv; +} diff -Nru chafa-1.2.1/tools/chafa/media-loader.h chafa-1.12.4/tools/chafa/media-loader.h --- chafa-1.2.1/tools/chafa/media-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/media-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __MEDIA_LOADER_H__ +#define __MEDIA_LOADER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct MediaLoader MediaLoader; + +MediaLoader *media_loader_new (const gchar *path, GError **error); +void media_loader_destroy (MediaLoader *loader); + +gboolean media_loader_get_is_animation (MediaLoader *loader); + +void media_loader_goto_first_frame (MediaLoader *loader); +gboolean media_loader_goto_next_frame (MediaLoader *loader); + +gconstpointer media_loader_get_frame_data (MediaLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint media_loader_get_frame_delay (MediaLoader *loader); + +gchar **get_loader_names (void); + +G_END_DECLS + +#endif /* __MEDIA_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/ms-utf8.xml chafa-1.12.4/tools/chafa/ms-utf8.xml --- chafa-1.2.1/tools/chafa/ms-utf8.xml 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/ms-utf8.xml 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,9 @@ + + + + + + UTF-8 + + + diff -Nru chafa-1.2.1/tools/chafa/named-colors.c chafa-1.12.4/tools/chafa/named-colors.c --- chafa-1.2.1/tools/chafa/named-colors.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/named-colors.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * diff -Nru chafa-1.2.1/tools/chafa/named-colors.h chafa-1.12.4/tools/chafa/named-colors.h --- chafa-1.2.1/tools/chafa/named-colors.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/named-colors.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * diff -Nru chafa-1.2.1/tools/chafa/png-loader.c chafa-1.12.4/tools/chafa/png-loader.c --- chafa-1.2.1/tools/chafa/png-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/png-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,172 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "png-loader.h" + +#define BYTES_PER_PIXEL 4 +#define IMAGE_BUFFER_SIZE_MAX 0xffffffffU + +struct PngLoader +{ + FileMapping *mapping; + const guint8 *file_data; + size_t file_data_len; + gpointer frame_data; + gint width, height; +}; + +static PngLoader * +png_loader_new (void) +{ + return g_new0 (PngLoader, 1); +} + +PngLoader * +png_loader_new_from_mapping (FileMapping *mapping) +{ + PngLoader *loader = NULL; + gboolean success = FALSE; + guint width, height; + unsigned char *frame_data = NULL; + LodePNGState lode_state; + gint lode_error; + + g_return_val_if_fail (mapping != NULL, NULL); + + lodepng_state_init (&lode_state); + + if (!file_mapping_has_magic (mapping, 0, "\x89PNG", 4)) + goto out; + + loader = png_loader_new (); + loader->mapping = mapping; + + loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); + if (!loader->file_data) + goto out; + + lode_state.info_raw.colortype = LCT_RGBA; + lode_state.info_raw.bitdepth = 8; + lode_state.decoder.zlibsettings.max_output_size = IMAGE_BUFFER_SIZE_MAX; + + /* Decodes to RGBA8 */ + if ((lode_error = lodepng_decode (&frame_data, &width, &height, + &lode_state, + loader->file_data, loader->file_data_len)) != 0) + goto out; + + if (width < 1 || width >= (1 << 28) + || height < 1 || height >= (1 << 28)) + goto out; + + loader->frame_data = frame_data; + loader->width = (gint) width; + loader->height = (gint) height; + + success = TRUE; + +out: + if (!success) + { + if (loader) + { + g_free (loader); + loader = NULL; + } + + if (frame_data) + free (frame_data); + } + + lodepng_state_cleanup (&lode_state); + return loader; +} + +void +png_loader_destroy (PngLoader *loader) +{ + if (loader->mapping) + file_mapping_destroy (loader->mapping); + + if (loader->frame_data) + free (loader->frame_data); + + g_free (loader); +} + +gboolean +png_loader_get_is_animation (PngLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return FALSE; +} + +gconstpointer +png_loader_get_frame_data (PngLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + g_return_val_if_fail (loader != NULL, NULL); + + if (pixel_type_out) + *pixel_type_out = CHAFA_PIXEL_RGBA8_UNASSOCIATED; + if (width_out) + *width_out = loader->width; + if (height_out) + *height_out = loader->height; + if (rowstride_out) + *rowstride_out = loader->width * BYTES_PER_PIXEL; + + return loader->frame_data; +} + +gint +png_loader_get_frame_delay (PngLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return 0; +} + +void +png_loader_goto_first_frame (PngLoader *loader) +{ + g_return_if_fail (loader != NULL); +} + +gboolean +png_loader_goto_next_frame (PngLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + return FALSE; +} diff -Nru chafa-1.2.1/tools/chafa/png-loader.h chafa-1.12.4/tools/chafa/png-loader.h --- chafa-1.2.1/tools/chafa/png-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/png-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __PNG_LOADER_H__ +#define __PNG_LOADER_H__ + +#include +#include "file-mapping.h" + +G_BEGIN_DECLS + +typedef struct PngLoader PngLoader; + +PngLoader *png_loader_new_from_mapping (FileMapping *mapping); +void png_loader_destroy (PngLoader *loader); + +gboolean png_loader_get_is_animation (PngLoader *loader); + +gconstpointer png_loader_get_frame_data (PngLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint png_loader_get_frame_delay (PngLoader *loader); + +void png_loader_goto_first_frame (PngLoader *loader); +gboolean png_loader_goto_next_frame (PngLoader *loader); + +G_END_DECLS + +#endif /* __PNG_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/svg-loader.c chafa-1.12.4/tools/chafa/svg-loader.c --- chafa-1.2.1/tools/chafa/svg-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/svg-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,253 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "svg-loader.h" + +#define DIMENSION_MAX 4096 +#define MAGIC_BUF_SIZE 4096 + +/* Cairo uses native byte order */ +#if G_BYTE_ORDER == G_BIG_ENDIAN +# define PIXEL_TYPE CHAFA_PIXEL_ARGB8_PREMULTIPLIED; +#else +# define PIXEL_TYPE CHAFA_PIXEL_BGRA8_PREMULTIPLIED; +#endif + +struct SvgLoader +{ + FileMapping *mapping; + const guint8 *file_data; + size_t file_data_len; + cairo_surface_t *surface; +}; + +static SvgLoader * +svg_loader_new (void) +{ + return g_new0 (SvgLoader, 1); +} + +static void +calc_dimensions (RsvgHandle *rsvg, guint *width_out, guint *height_out) +{ +#if !LIBRSVG_CHECK_VERSION(2, 52, 0) + RsvgDimensionData dim = { 0 }; +#endif + gdouble width, height; + + rsvg_handle_set_dpi (rsvg, 150.0); + +#if LIBRSVG_CHECK_VERSION(2, 52, 0) + if (!rsvg_handle_get_intrinsic_size_in_pixels (rsvg, &width, &height)) + { + width = height = (gdouble) DIMENSION_MAX; + } +#else + rsvg_handle_get_dimensions (rsvg, &dim); + width = dim.width; + height = dim.height; +#endif + + /* FIXME: It would've been nice to know the size of the final viewport; + * that is, the terminal's dimensions in pixels. We could pass this in + * if we change the internal API. */ + + if (width > DIMENSION_MAX || height > DIMENSION_MAX) + { + if (width > height) + { + height *= (gdouble) DIMENSION_MAX / width; + width = (gdouble) DIMENSION_MAX; + } + else + { + width *= (gdouble) DIMENSION_MAX / height; + height = (gdouble) DIMENSION_MAX; + } + } + + *width_out = lrint (width); + *height_out = lrint (height); +} + +SvgLoader * +svg_loader_new_from_mapping (FileMapping *mapping) +{ + SvgLoader *loader = NULL; + gboolean success = FALSE; + RsvgHandle *rsvg = NULL; + cairo_t *cr = NULL; +#if LIBRSVG_CHECK_VERSION(2, 46, 0) + RsvgRectangle viewport = { 0 }; +#endif + guint width, height; + + g_return_val_if_fail (mapping != NULL, NULL); + + if (file_mapping_has_magic (mapping, 0, "mapping = mapping; + + loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); + if (!loader->file_data) + goto out; + + /* Malformed SVGs will typically fail here */ + rsvg = rsvg_handle_new_from_data (loader->file_data, loader->file_data_len, NULL); + if (!rsvg) + goto out; + + calc_dimensions (rsvg, &width, &height); + if (width < 1 || width >= (1 << 28) + || height < 1 || height >= (1 << 28) + || (width * (guint64) height >= (1 << 29))) + goto out; + + loader->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (!loader->surface) + goto out; + + cr = cairo_create (loader->surface); + +#if LIBRSVG_CHECK_VERSION(2, 46, 0) + viewport.width = width; + viewport.height = height; + if (!rsvg_handle_render_document (rsvg, cr, &viewport, NULL)) + goto out; +#else + if (!rsvg_handle_render_cairo (rsvg, cr)) + goto out; +#endif + + success = TRUE; + +out: + if (cr) + cairo_destroy (cr); + + if (!success) + { + if (loader) + { + if (loader->surface) + cairo_surface_destroy (loader->surface); + g_free (loader); + loader = NULL; + } + } + + if (rsvg) + g_object_unref (rsvg); + + return loader; +} + +void +svg_loader_destroy (SvgLoader *loader) +{ + if (loader->mapping) + file_mapping_destroy (loader->mapping); + + if (loader->surface) + cairo_surface_destroy (loader->surface); + + g_free (loader); +} + +gboolean +svg_loader_get_is_animation (SvgLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return FALSE; +} + +gconstpointer +svg_loader_get_frame_data (SvgLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + g_return_val_if_fail (loader != NULL, NULL); + + if (pixel_type_out) + *pixel_type_out = PIXEL_TYPE; + if (width_out) + *width_out = cairo_image_surface_get_width (loader->surface); + if (height_out) + *height_out = cairo_image_surface_get_height (loader->surface); + if (rowstride_out) + *rowstride_out = cairo_image_surface_get_stride (loader->surface); + + return cairo_image_surface_get_data (loader->surface); +} + +gint +svg_loader_get_frame_delay (SvgLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return 0; +} + +void +svg_loader_goto_first_frame (SvgLoader *loader) +{ + g_return_if_fail (loader != NULL); +} + +gboolean +svg_loader_goto_next_frame (SvgLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + return FALSE; +} diff -Nru chafa-1.2.1/tools/chafa/svg-loader.h chafa-1.12.4/tools/chafa/svg-loader.h --- chafa-1.2.1/tools/chafa/svg-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/svg-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __SVG_LOADER_H__ +#define __SVG_LOADER_H__ + +#include +#include "file-mapping.h" + +G_BEGIN_DECLS + +typedef struct SvgLoader SvgLoader; + +SvgLoader *svg_loader_new_from_mapping (FileMapping *mapping); +void svg_loader_destroy (SvgLoader *loader); + +gboolean svg_loader_get_is_animation (SvgLoader *loader); + +gconstpointer svg_loader_get_frame_data (SvgLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint svg_loader_get_frame_delay (SvgLoader *loader); + +void svg_loader_goto_first_frame (SvgLoader *loader); +gboolean svg_loader_goto_next_frame (SvgLoader *loader); + +G_END_DECLS + +#endif /* __SVG_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/tiff-loader.c chafa-1.12.4/tools/chafa/tiff-loader.c --- chafa-1.2.1/tools/chafa/tiff-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/tiff-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,321 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "tiff-loader.h" + +/* ----------------------- * + * Global macros and types * + * ----------------------- */ + +#define BYTES_PER_PIXEL 4 + +struct TiffLoader +{ + FileMapping *mapping; + const guint8 *file_data; + size_t file_data_len; + gpointer frame_data; + gint width, height; + ChafaPixelType pixel_type; + + toff_t file_pos; +}; + +/* ----------- * + * TIFF loader * + * ----------- */ + +/* --- Memory source --- */ + +static tsize_t +my_tiff_read (thandle_t obj, tdata_t buffer, tsize_t size) +{ + TiffLoader *loader = (TiffLoader *) obj; + + size = CLAMP (size, 0, (tsize_t) loader->file_data_len - (tsize_t) loader->file_pos); + memcpy (buffer, loader->file_data + loader->file_pos, size); + loader->file_pos += size; + + return size; +} + +static tsize_t +my_tiff_write (G_GNUC_UNUSED thandle_t obj, G_GNUC_UNUSED tdata_t buffer, G_GNUC_UNUSED tsize_t size) +{ + return 0; +} + +static int +my_tiff_close (G_GNUC_UNUSED thandle_t obj) +{ + return 0; +} + +static toff_t +my_tiff_seek (thandle_t obj, toff_t pos, int whence) +{ + TiffLoader *loader = (TiffLoader *) obj; + + if (whence == SEEK_SET) + { + loader->file_pos = pos; + } + else if (whence == SEEK_CUR) + { + /* Since toff_t is unsigned, we can't seek backwards */ + loader->file_pos += pos; + } + else /* whence == SEEK_END */ + { + /* Since toff_t is unsigned, this is all we can do */ + loader->file_pos = loader->file_data_len; + } + + loader->file_pos = MIN (loader->file_pos, loader->file_data_len); + return loader->file_pos; +} + +static toff_t +my_tiff_size (thandle_t obj) +{ + TiffLoader *loader = (TiffLoader *) obj; + + return loader->file_data_len; +} + +static int +my_tiff_map (thandle_t obj, void **base, toff_t *len) +{ + TiffLoader *loader = (TiffLoader *) obj; + + /* When the TIFFMap delegate is non-NULL, libtiff will use it preferentially. + * + * Our map is read-only, while base points to non-const. Fingers crossed + * libtiff doesn't actually try to write to it during read operations. */ + + *base = (void *) loader->file_data; + *len = loader->file_data_len; + return 0; +} + +static void +my_tiff_unmap (G_GNUC_UNUSED thandle_t obj, G_GNUC_UNUSED void *base, G_GNUC_UNUSED toff_t len) +{ +} + +/* --- Error handlers --- */ + +static void +my_tiff_error_handler (G_GNUC_UNUSED const char *module, G_GNUC_UNUSED const char *format, G_GNUC_UNUSED va_list ap) +{ +} + +static void +my_tiff_warning_handler (G_GNUC_UNUSED const char *module, G_GNUC_UNUSED const char *format, G_GNUC_UNUSED va_list ap) +{ +} + +/* --- Loader --- */ + +static TiffLoader * +tiff_loader_new (void) +{ + return g_new0 (TiffLoader, 1); +} + +TiffLoader * +tiff_loader_new_from_mapping (FileMapping *mapping) +{ + TiffLoader *loader = NULL; + gboolean success = FALSE; + uint8_t *frame_data = NULL; + TIFF *tiff = NULL; + gint samples_per_pixel = 4; + uint32_t width, height; + + g_return_val_if_fail (mapping != NULL, NULL); + + if (!((file_mapping_has_magic (mapping, 0, "II", 2) + && file_mapping_has_magic (mapping, 2, "\x2a\x00", 2)) + || (file_mapping_has_magic (mapping, 0, "MM", 2) + && file_mapping_has_magic (mapping, 2, "\x00\x2a", 2)))) + goto out; + + loader = tiff_loader_new (); + loader->mapping = mapping; + + /* Get file data */ + + loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); + if (!loader->file_data) + goto out; + + /* Prepare to decode */ + + TIFFSetErrorHandler (my_tiff_error_handler); + TIFFSetWarningHandler (my_tiff_warning_handler); + + tiff = TIFFClientOpen ("Memory", "r", (thandle_t) loader, + my_tiff_read, my_tiff_write, my_tiff_seek, my_tiff_close, + my_tiff_size, my_tiff_map, my_tiff_unmap); + if (!tiff) + goto out; + + if (!TIFFGetField (tiff, TIFFTAG_IMAGEWIDTH, &width)) + goto out; + if (!TIFFGetField (tiff, TIFFTAG_IMAGELENGTH, &height)) + goto out; + if (!TIFFGetField (tiff, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel)) + goto out; + + if (width < 1 || width > (1 << 28) + || height < 1 || height > (1 << 28) + || (width * (guint64) height >= (1 << 29))) + goto out; + + /* An opaque image with unassociated alpha set to 0xff is equivalent to + * premultiplied alpha. This will speed up resampling later on. + * + * For an opaque image, samples_per_pixel will typically be 1 or 3. Other + * values may indicate there's an alpha channel -- in those cases, we look + * for an EXTRASAMPLES field, and if it doesn't explicitly specify + * premultiplied alpha, we fail safe to unassociated alpha. */ + + loader->pixel_type = CHAFA_PIXEL_RGBA8_PREMULTIPLIED; + + if (samples_per_pixel == 2 || samples_per_pixel >= 4) + { + const uint16_t *extra_samples = NULL; + uint16_t n_extra_samples = 0; + + if (TIFFGetField (tiff, TIFFTAG_EXTRASAMPLES, &n_extra_samples, &extra_samples) + && n_extra_samples >= 1 && extra_samples && extra_samples [0] != EXTRASAMPLE_ASSOCALPHA) + loader->pixel_type = CHAFA_PIXEL_RGBA8_UNASSOCIATED; + } + + frame_data = _TIFFmalloc (width * height * (guint64) BYTES_PER_PIXEL); + if (!frame_data) + goto out; + + /* Decode and rotate the image */ + + if (!TIFFReadRGBAImageOriented (tiff, width, height, (uint32_t *) frame_data, ORIENTATION_TOPLEFT, 0)) + goto out; + + /* Finish up */ + + loader->width = width; + loader->height = height; + loader->frame_data = frame_data; + + success = TRUE; + +out: + if (tiff) + TIFFClose (tiff); + + if (!success) + { + if (frame_data) + _TIFFfree (frame_data); + + if (loader) + { + g_free (loader); + loader = NULL; + } + } + + return loader; +} + +void +tiff_loader_destroy (TiffLoader *loader) +{ + if (loader->mapping) + file_mapping_destroy (loader->mapping); + + if (loader->frame_data) + _TIFFfree (loader->frame_data); + + g_free (loader); +} + +gboolean +tiff_loader_get_is_animation (TiffLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return FALSE; +} + +gconstpointer +tiff_loader_get_frame_data (TiffLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + g_return_val_if_fail (loader != NULL, NULL); + + if (pixel_type_out) + *pixel_type_out = loader->pixel_type; + if (width_out) + *width_out = loader->width; + if (height_out) + *height_out = loader->height; + if (rowstride_out) + *rowstride_out = loader->width * BYTES_PER_PIXEL; + + return loader->frame_data; +} + +gint +tiff_loader_get_frame_delay (TiffLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return 0; +} + +void +tiff_loader_goto_first_frame (TiffLoader *loader) +{ + g_return_if_fail (loader != NULL); +} + +gboolean +tiff_loader_goto_next_frame (TiffLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + return FALSE; +} diff -Nru chafa-1.2.1/tools/chafa/tiff-loader.h chafa-1.12.4/tools/chafa/tiff-loader.h --- chafa-1.2.1/tools/chafa/tiff-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/tiff-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __TIFF_LOADER_H__ +#define __TIFF_LOADER_H__ + +#include +#include "file-mapping.h" + +G_BEGIN_DECLS + +typedef struct TiffLoader TiffLoader; + +TiffLoader *tiff_loader_new_from_mapping (FileMapping *mapping); +void tiff_loader_destroy (TiffLoader *loader); + +gboolean tiff_loader_get_is_animation (TiffLoader *loader); + +gconstpointer tiff_loader_get_frame_data (TiffLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint tiff_loader_get_frame_delay (TiffLoader *loader); + +void tiff_loader_goto_first_frame (TiffLoader *loader); +gboolean tiff_loader_goto_next_frame (TiffLoader *loader); + +G_END_DECLS + +#endif /* __TIFF_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/webp-loader.c chafa-1.12.4/tools/chafa/webp-loader.c --- chafa-1.2.1/tools/chafa/webp-loader.c 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/webp-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,263 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "webp-loader.h" + +#define DEFAULT_FRAME_DURATION_MS 50 +#define BYTES_PER_PIXEL 4 + +struct WebpLoader +{ + FileMapping *mapping; + const guint8 *file_data; + size_t file_data_len; + gint width, height; + ChafaPixelType pixel_type; + WebPAnimDecoder *anim_dec; + gpointer this_frame_data, next_frame_data; + gint this_timestamp, next_timestamp; + guint is_animation : 1; +}; + +static WebpLoader * +webp_loader_new (void) +{ + return g_new0 (WebpLoader, 1); +} + +WebpLoader * +webp_loader_new_from_mapping (FileMapping *mapping) +{ + WebpLoader *loader = NULL; + gboolean success = FALSE; + WebPBitstreamFeatures features; + WebPAnimDecoderOptions anim_dec_options; + WebPData webp_data; + WebPAnimInfo anim_info; + + g_return_val_if_fail (mapping != NULL, NULL); + + /* Basic validation and info extraction */ + + if (!file_mapping_has_magic (mapping, 0, "RIFF", 4) + || !file_mapping_has_magic (mapping, 8, "WEBP", 4)) + goto out; + + loader = webp_loader_new (); + loader->mapping = mapping; + + loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); + if (!loader->file_data) + goto out; + + if (!WebPGetInfo (loader->file_data, loader->file_data_len, &features.width, &features.height)) + goto out; + + if (WebPGetFeatures (loader->file_data, loader->file_data_len, &features) != VP8_STATUS_OK) + goto out; + + /* Set up the animation decoder */ + + webp_data.bytes = loader->file_data; + webp_data.size = loader->file_data_len; + + WebPAnimDecoderOptionsInit (&anim_dec_options); + anim_dec_options.color_mode = MODE_RGBA; + anim_dec_options.use_threads = TRUE; + + loader->anim_dec = WebPAnimDecoderNew (&webp_data, &anim_dec_options); + if (!loader->anim_dec) + goto out; + + /* Get animation info and validate */ + + if (!WebPAnimDecoderGetInfo (loader->anim_dec, &anim_info)) + goto out; + + if (anim_info.canvas_width < 1 || anim_info.canvas_width >= (1 << 28) + || anim_info.canvas_height < 1 || anim_info.canvas_height >= (1 << 28) + || (anim_info.canvas_width * (guint64) anim_info.canvas_height >= (1 << 29))) + goto out; + + if (anim_info.frame_count < 1) + goto out; + + /* Store parameters */ + + if (anim_info.frame_count > 1) + loader->is_animation = TRUE; + + loader->width = anim_info.canvas_width; + loader->height = anim_info.canvas_height; + + /* An opaque image with unassociated alpha set to 0xff is equivalent to + * premultiplied alpha. This will speed up resampling later on. */ + loader->pixel_type = features.has_alpha ? CHAFA_PIXEL_RGBA8_UNASSOCIATED : CHAFA_PIXEL_RGBA8_PREMULTIPLIED; + + success = TRUE; + +out: + if (!success) + { + if (loader) + { + g_free (loader); + loader = NULL; + } + } + + return loader; +} + +void +webp_loader_destroy (WebpLoader *loader) +{ + if (loader->anim_dec) + WebPAnimDecoderDelete (loader->anim_dec); + + if (loader->mapping) + file_mapping_destroy (loader->mapping); + + g_free (loader->this_frame_data); + loader->this_frame_data = NULL; + g_free (loader->next_frame_data); + loader->next_frame_data = NULL; + + g_free (loader); +} + +gboolean +webp_loader_get_is_animation (WebpLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + + return loader->is_animation; +} + +static gboolean +decode_next_frame (WebpLoader *loader, uint8_t **buf, int *timestamp) +{ + return WebPAnimDecoderGetNext (loader->anim_dec, buf, timestamp); +} + +static gboolean +maybe_decode_frame (WebpLoader *loader) +{ + uint8_t *buf; + + if (loader->this_frame_data) + return TRUE; + + if (decode_next_frame (loader, &buf, &loader->this_timestamp)) + { + loader->this_frame_data = g_memdup (buf, loader->width * BYTES_PER_PIXEL * loader->height); + } + + return loader->this_frame_data ? TRUE : FALSE; +} + +gconstpointer +webp_loader_get_frame_data (WebpLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out) +{ + g_return_val_if_fail (loader != NULL, NULL); + + if (!maybe_decode_frame (loader)) + return NULL; + + if (pixel_type_out) + *pixel_type_out = loader->pixel_type; + if (width_out) + *width_out = loader->width; + if (height_out) + *height_out = loader->height; + if (rowstride_out) + *rowstride_out = loader->width * BYTES_PER_PIXEL; + + return loader->this_frame_data; +} + +gint +webp_loader_get_frame_delay (WebpLoader *loader) +{ + uint8_t *buf; + + g_return_val_if_fail (loader != NULL, 0); + + /* The libwebp API complicates this a little. We need to load the next frame + * in advance to know how long to hold this frame. */ + + maybe_decode_frame (loader); + + if (!loader->next_frame_data) + { + if (decode_next_frame (loader, &buf, &loader->next_timestamp)) + { + loader->next_frame_data = g_memdup (buf, loader->width * BYTES_PER_PIXEL * loader->height); + } + } + + return loader->next_frame_data ? + (loader->next_timestamp - loader->this_timestamp) + : DEFAULT_FRAME_DURATION_MS; +} + +void +webp_loader_goto_first_frame (WebpLoader *loader) +{ + g_return_if_fail (loader != NULL); + + WebPAnimDecoderReset (loader->anim_dec); + g_free (loader->this_frame_data); + loader->this_frame_data = NULL; + g_free (loader->next_frame_data); + loader->next_frame_data = NULL; +} + +gboolean +webp_loader_goto_next_frame (WebpLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + + g_free (loader->this_frame_data); + loader->this_frame_data = loader->next_frame_data; + loader->next_frame_data = NULL; + + if (loader->this_frame_data) + { + loader->this_timestamp = loader->next_timestamp; + return TRUE; + } + + return WebPAnimDecoderHasMoreFrames (loader->anim_dec) ? TRUE : FALSE; +} diff -Nru chafa-1.2.1/tools/chafa/webp-loader.h chafa-1.12.4/tools/chafa/webp-loader.h --- chafa-1.2.1/tools/chafa/webp-loader.h 1970-01-01 00:00:00.000000000 +0000 +++ chafa-1.12.4/tools/chafa/webp-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2022 Hans Petter Jansson + * + * This file is part of Chafa, a program that turns images into character art. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __WEBP_LOADER_H__ +#define __WEBP_LOADER_H__ + +#include +#include "file-mapping.h" + +G_BEGIN_DECLS + +typedef struct WebpLoader WebpLoader; + +WebpLoader *webp_loader_new_from_mapping (FileMapping *mapping); +void webp_loader_destroy (WebpLoader *loader); + +gboolean webp_loader_get_is_animation (WebpLoader *loader); + +gconstpointer webp_loader_get_frame_data (WebpLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint webp_loader_get_frame_delay (WebpLoader *loader); + +void webp_loader_goto_first_frame (WebpLoader *loader); +gboolean webp_loader_goto_next_frame (WebpLoader *loader); + +G_END_DECLS + +#endif /* __WEBP_LOADER_H__ */ diff -Nru chafa-1.2.1/tools/chafa/xwd-loader.c chafa-1.12.4/tools/chafa/xwd-loader.c --- chafa-1.2.1/tools/chafa/xwd-loader.c 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/xwd-loader.c 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -165,51 +165,83 @@ } #define ASSERT_HEADER(x) if (!(x)) return FALSE +#define UNPACK_FIELD_U32(dest, src, field) ((dest)->field = GUINT32_FROM_BE ((src)->field)) +#define UNPACK_FIELD_S32(dest, src, field) ((dest)->field = GINT32_FROM_BE ((src)->field)) static gboolean -load_header (XwdLoader *loader) // gconstpointer in, gsize in_max_len, XwdHeader *header_out) +load_header (XwdLoader *loader) { XwdHeader *h = &loader->header; XwdHeader in; - const guint32 *p = (const guint32 *) ∈ + const XwdHeader *inp; if (!file_mapping_taste (loader->mapping, &in, 0, sizeof (in))) return FALSE; - h->header_size = g_ntohl (*(p++)); - h->file_version = g_ntohl (*(p++)); - h->pixmap_format = g_ntohl (*(p++)); - h->pixmap_depth = g_ntohl (*(p++)); - h->pixmap_width = g_ntohl (*(p++)); - h->pixmap_height = g_ntohl (*(p++)); - h->x_offset = g_ntohl (*(p++)); - h->byte_order = g_ntohl (*(p++)); - h->bitmap_unit = g_ntohl (*(p++)); - h->bitmap_bit_order = g_ntohl (*(p++)); - h->bitmap_pad = g_ntohl (*(p++)); - h->bits_per_pixel = g_ntohl (*(p++)); - h->bytes_per_line = g_ntohl (*(p++)); - h->visual_class = g_ntohl (*(p++)); - h->red_mask = g_ntohl (*(p++)); - h->green_mask = g_ntohl (*(p++)); - h->blue_mask = g_ntohl (*(p++)); - h->bits_per_rgb = g_ntohl (*(p++)); - h->color_map_entries = g_ntohl (*(p++)); - h->n_colors = g_ntohl (*(p++)); - h->window_width = g_ntohl (*(p++)); - h->window_height = g_ntohl (*(p++)); - h->window_x = g_ntohl (*(p++)); - h->window_y = g_ntohl (*(p++)); - h->window_border_width = g_ntohl (*(p++)); + inp = ∈ + + UNPACK_FIELD_U32 (h, inp, header_size); + UNPACK_FIELD_U32 (h, inp, file_version); + UNPACK_FIELD_U32 (h, inp, pixmap_format); + UNPACK_FIELD_U32 (h, inp, pixmap_depth); + UNPACK_FIELD_U32 (h, inp, pixmap_width); + UNPACK_FIELD_U32 (h, inp, pixmap_height); + UNPACK_FIELD_U32 (h, inp, x_offset); + UNPACK_FIELD_U32 (h, inp, byte_order); + UNPACK_FIELD_U32 (h, inp, bitmap_unit); + UNPACK_FIELD_U32 (h, inp, bitmap_bit_order); + UNPACK_FIELD_U32 (h, inp, bitmap_pad); + UNPACK_FIELD_U32 (h, inp, bits_per_pixel); + UNPACK_FIELD_U32 (h, inp, bytes_per_line); + UNPACK_FIELD_U32 (h, inp, visual_class); + UNPACK_FIELD_U32 (h, inp, red_mask); + UNPACK_FIELD_U32 (h, inp, green_mask); + UNPACK_FIELD_U32 (h, inp, blue_mask); + UNPACK_FIELD_U32 (h, inp, bits_per_rgb); + UNPACK_FIELD_U32 (h, inp, color_map_entries); + UNPACK_FIELD_U32 (h, inp, n_colors); + UNPACK_FIELD_U32 (h, inp, window_width); + UNPACK_FIELD_U32 (h, inp, window_height); + UNPACK_FIELD_S32 (h, inp, window_x); + UNPACK_FIELD_S32 (h, inp, window_y); + UNPACK_FIELD_U32 (h, inp, window_border_width); /* Only support the most common/useful subset of XWD files out there; - * namely, that corresponding to screen dumps from modern X.Org servers. */ + * namely, that corresponding to screen dumps from modern X.Org servers. + * We could check visual_class == 5 too, but the other fields convey all + * the info we need. */ ASSERT_HEADER (h->header_size >= sizeof (XwdHeader)); + ASSERT_HEADER (h->header_size <= 65535); + ASSERT_HEADER (h->file_version == 7); ASSERT_HEADER (h->pixmap_depth == 24); - ASSERT_HEADER (h->bits_per_rgb == 8); + + /* Should be zero for truecolor/directcolor. Cap it to prevent overflows. */ + ASSERT_HEADER (h->color_map_entries <= 256); + + /* Xvfb sets bits_per_rgb to 8, but 'convert' uses 24 for the same image data. One + * of them is likely misunderstanding. Let's be lenient and accept either. */ + ASSERT_HEADER (h->bits_per_rgb == 8 || h->bits_per_rgb == 24); + + /* These are the pixel formats we allow. */ + ASSERT_HEADER (h->bits_per_pixel == 24 || h->bits_per_pixel == 32); + + /* Enforce sane dimensions. */ + ASSERT_HEADER (h->pixmap_width >= 1 && h->pixmap_width <= 65535); + ASSERT_HEADER (h->pixmap_height >= 1 && h->pixmap_height <= 65535); + + /* Make sure rowstride can actually hold a row's worth of data but is not padded to + * something ridiculous. */ ASSERT_HEADER (h->bytes_per_line >= h->pixmap_width * (h->bits_per_pixel / 8)); + ASSERT_HEADER (h->bytes_per_line <= h->pixmap_width * (h->bits_per_pixel / 8) + 1024); + + /* If each pixel is four bytes, reject unaligned rowstrides */ + ASSERT_HEADER (h->bits_per_pixel != 32 || h->bytes_per_line % 4 == 0); + + /* Make sure the total allocation/map is not too big. */ + ASSERT_HEADER (h->bytes_per_line * h->pixmap_height < (1UL << 31) - 65536 - 256 * 32); + ASSERT_HEADER (compute_pixel_type (loader) < CHAFA_PIXEL_MAX); loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); @@ -217,11 +249,11 @@ return FALSE; ASSERT_HEADER (loader->file_data_len >= h->header_size - + h->n_colors * sizeof (XwdColor) - + h->pixmap_height * h->bytes_per_line); + + h->color_map_entries * sizeof (XwdColor) + + h->pixmap_height * (gsize) h->bytes_per_line); loader->image_data = (const guint8 *) loader->file_data - + h->header_size + h->n_colors * sizeof (XwdColor); + + h->header_size + h->color_map_entries * sizeof (XwdColor); return TRUE; } @@ -235,7 +267,8 @@ XwdLoader * xwd_loader_new_from_mapping (FileMapping *mapping) { - XwdLoader *loader; + XwdLoader *loader = NULL; + gboolean success = FALSE; g_return_val_if_fail (mapping != NULL, NULL); @@ -250,6 +283,20 @@ DEBUG (dump_header (&loader->header)); + if (loader->header.pixmap_width < 1 || loader->header.pixmap_width >= (1 << 28) + || loader->header.pixmap_height < 1 || loader->header.pixmap_height >= (1 << 28) + || (loader->header.pixmap_width * (guint64) loader->header.pixmap_height >= (1 << 29))) + goto out; + + success = TRUE; + +out: + if (!success) + { + g_free (loader); + loader = NULL; + } + return loader; } @@ -262,8 +309,14 @@ g_free (loader); } +gboolean +xwd_loader_get_is_animation (G_GNUC_UNUSED XwdLoader *loader) +{ + return FALSE; +} + gconstpointer -xwd_loader_get_image_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, +xwd_loader_get_frame_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); @@ -279,3 +332,21 @@ return loader->image_data; } + +gint +xwd_loader_get_frame_delay (G_GNUC_UNUSED XwdLoader *loader) +{ + return 0; +} + +void +xwd_loader_goto_first_frame (G_GNUC_UNUSED XwdLoader *loader) +{ +} + +gboolean +xwd_loader_goto_next_frame (G_GNUC_UNUSED XwdLoader *loader) +{ + return FALSE; +} + diff -Nru chafa-1.2.1/tools/chafa/xwd-loader.h chafa-1.12.4/tools/chafa/xwd-loader.h --- chafa-1.2.1/tools/chafa/xwd-loader.h 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/chafa/xwd-loader.h 2022-11-12 01:18:35.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018 Hans Petter Jansson +/* Copyright (C) 2018-2022 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * @@ -30,8 +30,14 @@ XwdLoader *xwd_loader_new_from_mapping (FileMapping *mapping); void xwd_loader_destroy (XwdLoader *loader); -gconstpointer xwd_loader_get_image_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, +gboolean xwd_loader_get_is_animation (XwdLoader *loader); + +gconstpointer xwd_loader_get_frame_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); +gint xwd_loader_get_frame_delay (XwdLoader *loader); + +void xwd_loader_goto_first_frame (XwdLoader *loader); +gboolean xwd_loader_goto_next_frame (XwdLoader *loader); G_END_DECLS diff -Nru chafa-1.2.1/tools/fontgen/README.md chafa-1.12.4/tools/fontgen/README.md --- chafa-1.2.1/tools/fontgen/README.md 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/tools/fontgen/README.md 2022-11-12 01:18:35.000000000 +0000 @@ -26,7 +26,7 @@ clustering algorithms in the machine learning literature. You can generate a font by using some image dataset e.g. -[MSCOCO](http://cocodataset.org) dataset (reserach-oriented). +[MSCOCO](http://cocodataset.org) dataset (research-oriented). ## License diff -Nru chafa-1.2.1/.travis.yml chafa-1.12.4/.travis.yml --- chafa-1.2.1/.travis.yml 2019-08-15 01:49:52.000000000 +0000 +++ chafa-1.12.4/.travis.yml 2022-11-12 01:18:35.000000000 +0000 @@ -1,13 +1,18 @@ language: c +dist: bionic + compiler: - clang - gcc before_install: - - sudo apt-get install -qq -y automake libtool libglib2.0-dev libmagickwand-dev gtk-doc-tools docbook-xml libxml2-utils + - sudo apt-get install -qq -y automake libtool libglib2.0-dev libmagickwand-dev libjpeg-dev librsvg2-dev libtiff-dev libwebp-dev gtk-doc-tools docbook-xml libxml2-utils # Needed for ImageMagick/clang runtime not finding libomp.so - export LD_LIBRARY_PATH=$(if [[ $CC == "clang" ]]; then echo -n '/usr/local/clang/lib'; fi) script: - mkdir build && cd build && ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man && make -j4 && sudo make install && chafa --version + - mkdir build && cd build && CFLAGS='-g -O2 -fsanitize=address,undefined -fsanitize-undefined-trap-on-error -Werror -Wno-error=unused -Wno-error=unused-function -Wno-error=unused-parameter -Wno-error=unused-variable -Wno-error=unused-value -Wno-error=comment -Wno-error=missing-braces' ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man --without-imagemagick && make -j4 && make check && rm -Rf ../build/* && ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man && make -j4 && sudo make install && chafa --version && cd ../tests && ./postinstall.sh + +after_failure: + - touch tests/test-suite.log && cat tests/test-suite.log