Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hb-view] Use Chafa for terminal graphics if available #2959

Merged
merged 2 commits into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- checkout
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config ragel freetype glib cairo python3 icu4c graphite2 gobject-introspection gtk-doc ninja
- run: pip3 install meson --upgrade
- run: PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:/usr/local/opt/libffi/lib/pkgconfig" meson build -Dcoretext=enabled -Dgraphite=enabled -Dauto_features=enabled
- run: PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:/usr/local/opt/libffi/lib/pkgconfig" meson build -Dcoretext=enabled -Dgraphite=enabled -Dauto_features=enabled -Dchafa=disabled
- run: meson compile -Cbuild
- run: meson test -Cbuild --print-errorlogs
- store_artifacts:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linux-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
run: sudo apt-get install pkg-config gcc ragel gcovr gtk-doc-tools libfreetype6-dev libglib2.0-dev libcairo2-dev libicu-dev libgraphite2-dev python3 python3-setuptools ninja-build gobject-introspection libgirepository1.0-dev
- run: sudo pip3 install fonttools meson==0.47.0
- name: run
run: meson build -Db_coverage=true --auto-features=enabled -Dgraphite=enabled -Doptimization=2
run: meson build -Db_coverage=true --auto-features=enabled -Dgraphite=enabled -Dchafa=disabled -Doptimization=2
- name: ci
run: meson test --print-errorlogs -Cbuild

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/msys2-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ jobs:
--auto-features=enabled \
-Ddirectwrite=enabled \
-Dgdi=enabled \
-Dgraphite=enabled
-Dgraphite=enabled \
-Dchafa=disabled
ninja -C build
- name: Test
run: |
Expand Down
19 changes: 19 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,24 @@ AM_CONDITIONAL(HAVE_CAIRO_FT, $have_cairo_ft)

dnl ==========================================================================

AC_ARG_WITH(chafa,
[AS_HELP_STRING([--with-chafa=@<:@yes/no/auto@:>@],
[Use chafa @<:@default=auto@:>@])],,
[with_chafa=auto])
have_chafa=false
if test "x$with_chafa" = "xyes" -o "x$with_chafa" = "xauto"; then
PKG_CHECK_MODULES(CHAFA, chafa >= 1.6.0, have_chafa=true, :)
fi
if test "x$with_chafa" = "xyes" -a "x$have_chafa" != "xtrue"; then
AC_MSG_ERROR([chafa support requested but not found])
fi
if $have_chafa; then
AC_DEFINE(HAVE_CHAFA, 1, [Have chafa terminal graphics library])
fi
AM_CONDITIONAL(HAVE_CHAFA, $have_chafa)

dnl ==========================================================================

AC_ARG_WITH(icu,
[AS_HELP_STRING([--with-icu=@<:@yes/no/builtin/auto@:>@],
[Use ICU @<:@default=auto@:>@])],,
Expand Down Expand Up @@ -448,6 +466,7 @@ Font callbacks (the more the merrier):

Tools used for command-line utilities:
Cairo: ${have_cairo}
Chafa: ${have_chafa}

Additional shapers:
Graphite2: ${have_graphite2}
Expand Down
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ if not get_option('cairo').disabled()
endif
endif

chafa_dep = dependency('chafa', version: '>= 1.6.0', required: get_option('chafa'))

conf = configuration_data()
incconfig = include_directories('.')

Expand Down Expand Up @@ -181,6 +183,10 @@ if cairo_ft_dep.found()
conf.set('HAVE_CAIRO_FT', 1)
endif

if chafa_dep.found()
conf.set('HAVE_CHAFA', 1)
endif

if graphite2_dep.found()
conf.set('HAVE_GRAPHITE2', 1)
endif
Expand Down Expand Up @@ -363,6 +369,7 @@ build_summary = {
},
'Dependencies used for command-line utilities':
{'Cairo': conf.get('HAVE_CAIRO', 0) == 1,
'Chafa': conf.get('HAVE_CHAFA', 0) == 1,
},
'Additional shapers':
{'Graphite2': conf.get('HAVE_GRAPHITE2', 0) == 1,
Expand Down
2 changes: 2 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ option('gobject', type: 'feature', value: 'auto',
description: 'Enable GObject bindings')
option('cairo', type: 'feature', value: 'auto',
description: 'Use Cairo graphics library')
option('chafa', type: 'feature', value: 'auto',
description: 'Use Chafa terminal graphics library')
option('icu', type: 'feature', value: 'auto',
description: 'Enable ICU library unicode functions')
option('graphite', type: 'feature', value: 'disabled',
Expand Down
2 changes: 2 additions & 0 deletions util/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ AM_CPPFLAGS = \
$(GLIB_CFLAGS) \
$(FREETYPE_CFLAGS) \
$(CAIRO_FT_CFLAGS) \
$(CHAFA_CFLAGS) \
$(NULL)
LDADD = \
$(top_builddir)/src/libharfbuzz.la \
Expand All @@ -42,6 +43,7 @@ hb_view_LDADD = \
$(LDADD) \
$(CAIRO_LIBS) \
$(CAIRO_FT_LIBS) \
$(CHAFA_LIBS) \
$(NULL)
bin_PROGRAMS += hb-view
endif # HAVE_CAIRO_FT
Expand Down
104 changes: 102 additions & 2 deletions util/helper-cairo-ansi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,102 @@

#include "helper-cairo-ansi.hh"
#include "options.hh"

#include "ansi-print.hh"

#ifdef HAVE_CHAFA
# include <chafa.h>

/* Similar to ansi-print.cc */
# define CELL_W 8
# define CELL_H (2 * CELL_W)

static void
chafa_print_image_rgb24 (const void *data, int width, int height, int stride)
{
ChafaTermInfo *term_info;
ChafaSymbolMap *symbol_map;
ChafaCanvasConfig *config;
ChafaCanvas *canvas;
GString *gs;
unsigned int cols = (width + CELL_W - 1) / CELL_W;
unsigned int rows = (height + CELL_H - 1) / CELL_H;
gchar **environ;
ChafaCanvasMode mode;
ChafaPixelMode pixel_mode;

/* Adapt to terminal; use sixels if available, and fall back to symbols
* with as many colors as are supported */

environ = g_get_environ ();
term_info = chafa_term_db_detect (chafa_term_db_get_default (),
environ);

pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS;

if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS))
{
pixel_mode = CHAFA_PIXEL_MODE_SIXELS;
mode = CHAFA_CANVAS_MODE_TRUECOLOR;
}
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT))
mode = CHAFA_CANVAS_MODE_TRUECOLOR;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256))
mode = CHAFA_CANVAS_MODE_INDEXED_240;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16))
mode = CHAFA_CANVAS_MODE_INDEXED_16;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS))
mode = CHAFA_CANVAS_MODE_FGBG_BGFG;
else
mode = CHAFA_CANVAS_MODE_FGBG;

/* Create the configuration */

symbol_map = chafa_symbol_map_new ();
chafa_symbol_map_add_by_tags (symbol_map,
(ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK
| CHAFA_SYMBOL_TAG_SPACE));

config = chafa_canvas_config_new ();
chafa_canvas_config_set_canvas_mode (config, mode);
chafa_canvas_config_set_pixel_mode (config, pixel_mode);
chafa_canvas_config_set_cell_geometry (config, 10, 20);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious. How are these numbers used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cell geometry is the pixel size of a single character cell, and the canvas geometry is the size of the canvas in character cells. The cell size is only used in sixel mode: Let's say you have a canvas that's 10 cells wide, and we supply that character cells are 10 pixels wide. Chafa will then produce a sixel image that's 100 pixels wide, scaling the input image if necessary.

I decided to use 10x20 because that's the fixed cell size of VT240 and VT340 terminals (and possibly Windows Terminal too when/if it gets sixel support).

This will lead to sixel output being a bit bigger than expected on modern Linux terminals (character cells tend to be bigger than 10x20, but the aspect is usually about the same). It would be possible to get the cell size with the TIOCGWINSZ ioctl (.ws_xpixel and .ws_ypixel fields divided by .ws_col and .ws_row). The chafa command-line tool does this, but I avoided it in this patch because it seemed minor and might make Harfbuzz less portable.

See https://github.com/hpjansson/chafa/blob/2af7edec1adfdf1825cfb0dba46e8aaed7504918/tools/chafa/chafa.c#L726-L753 for how I did it in chafa. I can add something similar to hb-view if you think it's worth it.

Also see here for a long and somewhat opinionated discussion of sixel/cell dimensions arcana :)

chafa_canvas_config_set_geometry (config, cols, rows);
chafa_canvas_config_set_symbol_map (config, symbol_map);
chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN);
chafa_canvas_config_set_work_factor (config, 1.0f);

/* Create canvas, draw to it and render output string */

canvas = chafa_canvas_new (config);
chafa_canvas_draw_all_pixels (canvas,
/* Cairo byte order is host native */
G_BYTE_ORDER == G_LITTLE_ENDIAN
? CHAFA_PIXEL_BGRA8_PREMULTIPLIED
: CHAFA_PIXEL_ARGB8_PREMULTIPLIED,
(const guint8 *) data,
width,
height,
stride);
gs = chafa_canvas_print (canvas, term_info);

/* Print the string */

fwrite (gs->str, sizeof (char), gs->len, stdout);

if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS)
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);
chafa_term_info_unref (term_info);
g_strfreev (environ);
}

#endif /* HAVE_CHAFA */

cairo_status_t
helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
Expand Down Expand Up @@ -95,7 +188,14 @@ helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
height++; /* Add one last blank row for padding. */

if (width && height)
ansi_print_image_rgb24 (data, width, height, stride / 4);
{
#ifdef HAVE_CHAFA
if (true)
chafa_print_image_rgb24 (data, width, height, stride);
else
#endif
ansi_print_image_rgb24 (data, width, height, stride / 4);
}

cairo_surface_destroy (surface);
return CAIRO_STATUS_SUCCESS;
Expand Down
2 changes: 1 addition & 1 deletion util/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ if conf.get('HAVE_GLIB', 0) == 1
hb_view = executable('hb-view', hb_view_sources,
cpp_args: cpp_args,
include_directories: [incconfig, incsrc],
dependencies: util_deps,
dependencies: [util_deps, chafa_dep],
link_with: [libharfbuzz],
install: true,
)
Expand Down