blob: a8f752cf2ef001b0842d7ed34b29414a1f65ce16 [file] [log] [blame]
/*
* Copyright © 2026 Behdad Esfahbod
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Author(s): Behdad Esfahbod
*/
#ifndef RASTER_OUTPUT_HH
#define RASTER_OUTPUT_HH
#include "options.hh"
#include "output-options.hh"
#include "view-options.hh"
#include "helper-image-output.hh"
#include "hb-raster.h"
#include "hb-ot.h"
#include <math.h>
#include <vector>
static const char *raster_supported_formats[] = {
#ifdef HAVE_PNG
"png",
#endif
"ppm",
"ansi",
nullptr
};
#ifdef HAVE_PNG
#define HB_RASTER_DEFAULT_OUTPUT_FORMAT "png"
#else
#define HB_RASTER_DEFAULT_OUTPUT_FORMAT "ppm"
#endif
struct raster_output_t : output_options_t<true>, view_options_t
{
static const bool repeat_shape = false;
~raster_output_t ()
{
hb_raster_draw_destroy (rdr);
hb_raster_paint_destroy (pnt);
hb_font_destroy (font);
}
void add_options (option_parser_t *parser)
{
parser->set_summary ("Rasterize text with given font.");
parser->set_description ("Renders shaped text as a raster image.");
output_options_t::add_options (parser, raster_supported_formats);
view_options_t::add_options (parser);
}
template <typename app_t>
void init (hb_buffer_t *buffer HB_UNUSED, const app_t *font_opts)
{
lines.clear ();
direction = HB_DIRECTION_INVALID;
font = hb_font_reference (font_opts->font);
subpixel_bits = font_opts->subpixel_bits;
hb_face_t *face = hb_font_get_face (font);
has_color = hb_ot_color_has_paint (face) ||
hb_ot_color_has_layers (face) ||
#ifndef HB_NO_SVG
hb_ot_color_has_svg (face) ||
#endif
hb_ot_color_has_png (face);
fg_color = HB_COLOR ((uint8_t) foreground_color.b,
(uint8_t) foreground_color.g,
(uint8_t) foreground_color.r,
(uint8_t) foreground_color.a);
bg_r = (uint8_t) background_color.r;
bg_g = (uint8_t) background_color.g;
bg_b = (uint8_t) background_color.b;
bg_a = (uint8_t) background_color.a;
if (output_format &&
0 != g_ascii_strcasecmp (output_format, "ansi") &&
0 != g_ascii_strcasecmp (output_format, "ppm")
#ifdef HAVE_PNG
&& 0 != g_ascii_strcasecmp (output_format, "png")
#endif
)
fail (false, "Unknown output format `%s'; supported formats are: %s",
output_format,
g_strjoinv ("/", const_cast<char **> (raster_supported_formats)));
rdr = hb_raster_draw_create_or_fail ();
if (has_color)
{
pnt = hb_raster_paint_create_or_fail ();
hb_raster_paint_set_foreground (pnt, fg_color);
hb_raster_paint_clear_custom_palette_colors (pnt);
if (custom_palette_entries)
{
for (unsigned i = 0; i < custom_palette_entries->len; i++)
{
auto &entry =
g_array_index (custom_palette_entries,
typename view_options_t::custom_palette_entry_t, i);
hb_raster_paint_set_custom_palette_color (pnt, entry.index,
HB_COLOR ((uint8_t) entry.color.b,
(uint8_t) entry.color.g,
(uint8_t) entry.color.r,
(uint8_t) entry.color.a));
}
}
}
}
void new_line () { lines.emplace_back (); }
void consume_text (hb_buffer_t * HB_UNUSED,
const char * HB_UNUSED,
unsigned int HB_UNUSED,
hb_bool_t HB_UNUSED) {}
void error (const char *message)
{ g_printerr ("%s: %s\n", g_get_prgname (), message); }
void consume_glyphs (hb_buffer_t *buffer,
const char * HB_UNUSED,
unsigned int HB_UNUSED,
hb_bool_t HB_UNUSED)
{
if (lines.empty ()) lines.emplace_back ();
line_t &line = lines.back ();
hb_direction_t line_dir = hb_buffer_get_direction (buffer);
if (line_dir == HB_DIRECTION_INVALID)
line_dir = HB_DIRECTION_LTR;
if (direction == HB_DIRECTION_INVALID)
direction = line_dir;
unsigned count;
const hb_glyph_info_t *infos = hb_buffer_get_glyph_infos (buffer, &count);
const hb_glyph_position_t *positions = hb_buffer_get_glyph_positions (buffer, &count);
hb_position_t pen_x = 0, pen_y = 0;
line.glyphs.reserve (line.glyphs.size () + count);
for (unsigned i = 0; i < count; i++)
{
line.glyphs.push_back ({
infos[i].codepoint,
(float) (pen_x + positions[i].x_offset),
(float) (pen_y + positions[i].y_offset),
});
pen_x += positions[i].x_advance;
pen_y += positions[i].y_advance;
}
line.advance_x = (float) pen_x;
line.advance_y = (float) pen_y;
}
template <typename app_t>
void finish (hb_buffer_t *buffer HB_UNUSED, const app_t *app)
{
unsigned int num_iterations = app->num_iterations ? app->num_iterations : 1;
/* pixels per font unit */
float sx = scalbnf (1.f, -(int) subpixel_bits);
float sy = scalbnf (1.f, -(int) subpixel_bits);
float scale_factor = scalbnf (1.f, (int) subpixel_bits);
hb_raster_draw_set_scale_factor (rdr, scale_factor, scale_factor);
if (pnt)
hb_raster_paint_set_scale_factor (pnt, scale_factor, scale_factor);
/* line step in font units */
hb_direction_t dir = direction;
if (dir == HB_DIRECTION_INVALID) dir = HB_DIRECTION_LTR;
bool vertical = HB_DIRECTION_IS_VERTICAL (dir);
float step = 0.f;
if (have_font_extents)
step = fabsf (scalbnf ((float) (font_extents.ascent + font_extents.descent + font_extents.line_gap),
(int) subpixel_bits));
else
{
hb_font_extents_t extents = {};
hb_font_get_extents_for_direction (font, dir, &extents);
step = fabsf ((float) (extents.ascender - extents.descender + extents.line_gap));
}
step += scalbnf ((float) line_space, (int) subpixel_bits);
if (step < 0.f)
step = 0.f;
if (!(step > 0.f))
{
int x_scale = 0, y_scale = 0;
hb_font_get_scale (font, &x_scale, &y_scale);
int axis_scale = vertical ? x_scale : y_scale;
step = axis_scale ? fabsf ((float) axis_scale) : 1.f;
}
if (has_color && pnt)
{
finish_paint (sx, sy, step, vertical, num_iterations);
}
else if (foreground_use_palette && foreground_palette && foreground_palette->len > 1)
{
finish_draw_palette (sx, sy, step, vertical, num_iterations);
}
else
{
hb_raster_extents_t ext;
if (!compute_extents (sx, sy, step, vertical, &ext))
{
cleanup ();
return;
}
for (unsigned int iter = 0; iter < num_iterations; iter++)
{
hb_raster_draw_set_extents (rdr, &ext);
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
for (const auto &g : lines[li].glyphs)
{
float pen_x = g.x + off_x;
float pen_y = g.y + off_y;
hb_raster_draw_set_transform (rdr, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f);
hb_raster_draw_glyph (rdr, font, g.gid, pen_x, pen_y);
}
}
hb_raster_image_t *img = hb_raster_draw_render (rdr);
if (img)
{
if (iter + 1 == num_iterations)
write_a8_image (img, sx, sy, step, vertical, ext);
hb_raster_draw_recycle_image (rdr, img);
}
}
}
cleanup ();
}
private:
struct glyph_instance_t { hb_codepoint_t gid; float x, y; };
struct rect_i_t { int x0, y0, x1, y1; };
struct line_t
{
float advance_x = 0.f, advance_y = 0.f;
std::vector<glyph_instance_t> glyphs;
};
void finish_paint (float sx, float sy, float step, bool vertical, unsigned int num_iterations)
{
hb_raster_extents_t ext;
if (!compute_extents (sx, sy, step, vertical, &ext))
return;
unsigned w = ext.width;
unsigned h = ext.height;
unsigned stride = w * 4;
ext.stride = stride;
hb_raster_image_t *out_img = hb_raster_image_create_or_fail ();
if (!out_img) return;
if (!hb_raster_image_configure (out_img, HB_RASTER_FORMAT_BGRA32, &ext))
{
hb_raster_image_destroy (out_img);
return;
}
uint8_t *out_buf = const_cast<uint8_t *> (hb_raster_image_get_buffer (out_img));
if (!out_buf)
{
hb_raster_image_destroy (out_img);
return;
}
for (unsigned int iter = 0; iter < num_iterations; iter++)
{
hb_raster_image_clear (out_img);
/* Second pass: paint each glyph */
unsigned glyph_index = 0;
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
for (const auto &g : lines[li].glyphs)
{
hb_color_t glyph_fg = foreground_for_glyph (glyph_index++);
float pen_x = g.x + off_x;
float pen_y = g.y + off_y;
hb_raster_paint_set_transform (pnt, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f);
hb_raster_paint_set_extents (pnt, &ext);
hb_raster_paint_glyph (pnt, font, g.gid, pen_x, pen_y, palette, glyph_fg);
hb_raster_image_t *img = hb_raster_paint_render (pnt);
if (img)
{
/* Composite onto output buffer (SRC_OVER) */
const uint8_t *src = hb_raster_image_get_buffer (img);
hb_raster_extents_t img_ext;
hb_raster_image_get_extents (img, &img_ext);
for (unsigned y = 0; y < h; y++)
for (unsigned x = 0; x < w; x++)
{
uint32_t s;
hb_memcpy (&s, src + y * img_ext.stride + x * 4, 4);
if (!s) continue;
uint8_t sa = (uint8_t) (s >> 24);
uint32_t d;
hb_memcpy (&d, out_buf + y * stride + x * 4, 4);
if (sa == 255) { d = s; }
else
{
unsigned inv_sa = 255 - sa;
uint8_t rb = (uint8_t) (((d & 0xFF) * inv_sa + 128 + (((d & 0xFF) * inv_sa + 128) >> 8)) >> 8) + (uint8_t) (s & 0xFF);
uint8_t rg = (uint8_t) ((((d >> 8) & 0xFF) * inv_sa + 128 + ((((d >> 8) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + (uint8_t) ((s >> 8) & 0xFF);
uint8_t rr = (uint8_t) ((((d >> 16) & 0xFF) * inv_sa + 128 + ((((d >> 16) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + (uint8_t) ((s >> 16) & 0xFF);
uint8_t ra = (uint8_t) ((((d >> 24) & 0xFF) * inv_sa + 128 + ((((d >> 24) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sa;
d = (uint32_t) rb | ((uint32_t) rg << 8) | ((uint32_t) rr << 16) | ((uint32_t) ra << 24);
}
hb_memcpy (out_buf + y * stride + x * 4, &d, 4);
}
hb_raster_paint_recycle_image (pnt, img);
}
}
}
if (iter + 1 == num_iterations)
{
if (show_extents)
{
std::vector<rect_i_t> rects;
collect_ink_extents_rects (sx, sy, step, vertical, ext, rects);
overlay_rects_bgra (out_buf, w, h, stride, rects);
}
write_bgra_image (out_img);
}
}
hb_raster_image_destroy (out_img);
}
void finish_draw_palette (float sx, float sy, float step, bool vertical, unsigned int num_iterations)
{
hb_raster_extents_t ext;
if (!compute_extents (sx, sy, step, vertical, &ext))
return;
unsigned w = ext.width;
unsigned h = ext.height;
unsigned stride = w * 4;
ext.stride = stride;
hb_raster_image_t *out_img = hb_raster_image_create_or_fail ();
if (!out_img) return;
if (!hb_raster_image_configure (out_img, HB_RASTER_FORMAT_BGRA32, &ext))
{
hb_raster_image_destroy (out_img);
return;
}
uint8_t *out_buf = const_cast<uint8_t *> (hb_raster_image_get_buffer (out_img));
if (!out_buf)
{
hb_raster_image_destroy (out_img);
return;
}
for (unsigned int iter = 0; iter < num_iterations; iter++)
{
hb_raster_image_clear (out_img);
unsigned glyph_index = 0;
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
for (const auto &g : lines[li].glyphs)
{
hb_raster_draw_reset (rdr);
hb_raster_draw_set_scale_factor (rdr, scalbnf (1.f, (int) subpixel_bits),
scalbnf (1.f, (int) subpixel_bits));
hb_raster_draw_set_extents (rdr, &ext);
hb_raster_draw_set_transform (rdr, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f);
hb_raster_draw_glyph (rdr, font, g.gid, g.x + off_x, g.y + off_y);
hb_raster_image_t *img = hb_raster_draw_render (rdr);
if (!img) { glyph_index++; continue; }
hb_color_t glyph_fg = foreground_for_glyph (glyph_index++);
uint8_t fr = hb_color_get_red (glyph_fg);
uint8_t fg = hb_color_get_green (glyph_fg);
uint8_t fb = hb_color_get_blue (glyph_fg);
uint8_t fa = hb_color_get_alpha (glyph_fg);
const uint8_t *src = hb_raster_image_get_buffer (img);
hb_raster_extents_t img_ext;
hb_raster_image_get_extents (img, &img_ext);
for (unsigned y = 0; y < h; y++)
for (unsigned x = 0; x < w; x++)
{
uint8_t cov = src[y * img_ext.stride + x];
if (!cov) continue;
uint8_t sa = (uint8_t) ((cov * fa + 127) / 255);
uint8_t sb = (uint8_t) ((fb * sa + 127) / 255);
uint8_t sg = (uint8_t) ((fg * sa + 127) / 255);
uint8_t sr = (uint8_t) ((fr * sa + 127) / 255);
uint32_t s = (uint32_t) sb |
((uint32_t) sg << 8) |
((uint32_t) sr << 16) |
((uint32_t) sa << 24);
uint32_t d;
hb_memcpy (&d, out_buf + y * stride + x * 4, 4);
if (sa == 255) d = s;
else
{
unsigned inv_sa = 255 - sa;
uint8_t rb = (uint8_t) (((d & 0xFF) * inv_sa + 128 + (((d & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sb;
uint8_t rg = (uint8_t) ((((d >> 8) & 0xFF) * inv_sa + 128 + ((((d >> 8) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sg;
uint8_t rr = (uint8_t) ((((d >> 16) & 0xFF) * inv_sa + 128 + ((((d >> 16) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sr;
uint8_t ra = (uint8_t) ((((d >> 24) & 0xFF) * inv_sa + 128 + ((((d >> 24) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sa;
d = (uint32_t) rb | ((uint32_t) rg << 8) | ((uint32_t) rr << 16) | ((uint32_t) ra << 24);
}
hb_memcpy (out_buf + y * stride + x * 4, &d, 4);
}
hb_raster_draw_recycle_image (rdr, img);
}
}
if (iter + 1 == num_iterations)
{
if (show_extents)
{
std::vector<rect_i_t> rects;
collect_ink_extents_rects (sx, sy, step, vertical, ext, rects);
overlay_rects_bgra (out_buf, w, h, stride, rects);
}
write_bgra_image (out_img);
}
}
hb_raster_draw_reset (rdr);
hb_raster_image_destroy (out_img);
}
void cleanup ()
{
hb_raster_draw_destroy (rdr); rdr = nullptr;
hb_raster_paint_destroy (pnt); pnt = nullptr;
hb_font_destroy (font); font = nullptr;
lines.clear ();
direction = HB_DIRECTION_INVALID;
}
bool compute_extents (float sx, float sy, float step, bool vertical, hb_raster_extents_t *ext)
{
/* Compute bounding box in pixel space from logical and/or ink extents. */
const bool include_logical = include_logical_extents ();
const bool include_ink = include_ink_extents ();
float pmin_x = 1e30f, pmin_y = 1e30f;
float pmax_x = -1e30f, pmax_y = -1e30f;
bool have_extents = false;
if (include_logical)
{
float asc = 0.f, desc = 0.f;
if (have_font_extents)
{
asc = scalbnf ((float) font_extents.ascent, (int) subpixel_bits);
desc = -scalbnf ((float) font_extents.descent, (int) subpixel_bits);
}
else
{
hb_font_extents_t fext = {};
hb_font_get_extents_for_direction (font, direction, &fext);
asc = (float) fext.ascender;
desc = (float) fext.descender;
}
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
float ax0 = off_x;
float ay0 = off_y;
float ax1 = off_x + lines[li].advance_x;
float ay1 = off_y + lines[li].advance_y;
float lx0, ly0, lx1, ly1;
if (vertical)
{
lx0 = off_x + desc;
lx1 = off_x + asc;
ly0 = hb_min (ay0, ay1);
ly1 = hb_max (ay0, ay1);
}
else
{
lx0 = hb_min (ax0, ax1);
lx1 = hb_max (ax0, ax1);
ly0 = off_y + desc;
ly1 = off_y + asc;
}
lx0 *= sx; lx1 *= sx;
ly0 *= sy; ly1 *= sy;
pmin_x = hb_min (pmin_x, hb_min (lx0, lx1));
pmin_y = hb_min (pmin_y, hb_min (ly0, ly1));
pmax_x = hb_max (pmax_x, hb_max (lx0, lx1));
pmax_y = hb_max (pmax_y, hb_max (ly0, ly1));
have_extents = true;
}
}
if (include_ink)
{
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
for (const auto &g : lines[li].glyphs)
{
hb_glyph_extents_t gext;
if (!hb_font_get_glyph_extents (font, g.gid, &gext))
continue;
float gx = (g.x + off_x) * sx;
float gy = (g.y + off_y) * sy;
float x0 = gx + gext.x_bearing * sx;
float y0 = gy + (gext.y_bearing + gext.height) * sy;
float x1 = gx + (gext.x_bearing + gext.width) * sx;
float y1 = gy + gext.y_bearing * sy;
pmin_x = hb_min (pmin_x, hb_min (x0, x1));
pmin_y = hb_min (pmin_y, hb_min (y0, y1));
pmax_x = hb_max (pmax_x, hb_max (x0, x1));
pmax_y = hb_max (pmax_y, hb_max (y0, y1));
have_extents = true;
}
}
}
if (!have_extents || pmin_x >= pmax_x || pmin_y >= pmax_y)
return false;
int ix0 = (int) floorf (pmin_x) - (int) floor (margin.l);
int iy0 = (int) floorf (pmin_y) - (int) floor (margin.b);
int ix1 = (int) ceilf (pmax_x) + (int) ceil (margin.r);
int iy1 = (int) ceilf (pmax_y) + (int) ceil (margin.t);
if (ix1 <= ix0 || iy1 <= iy0)
return false;
ext->x_origin = ix0;
ext->y_origin = iy0;
ext->width = (unsigned) (ix1 - ix0);
ext->height = (unsigned) (iy1 - iy0);
ext->stride = 0;
return true;
}
hb_color_t foreground_for_glyph (unsigned glyph_index) const
{
if (foreground_use_palette && foreground_palette && foreground_palette->len)
{
auto &c = g_array_index (foreground_palette, rgba_color_t,
glyph_index % foreground_palette->len);
return HB_COLOR ((uint8_t) c.b, (uint8_t) c.g, (uint8_t) c.r, (uint8_t) c.a);
}
return fg_color;
}
void collect_ink_extents_rects (float sx, float sy, float step, bool vertical,
const hb_raster_extents_t &ext,
std::vector<rect_i_t> &rects) const
{
rects.clear ();
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
for (const auto &g : lines[li].glyphs)
{
hb_glyph_extents_t gext;
if (!hb_font_get_glyph_extents (font, g.gid, &gext))
continue;
float gx = (g.x + off_x) * sx;
float gy = (g.y + off_y) * sy;
float x0 = gx + gext.x_bearing * sx;
float y0 = gy + (gext.y_bearing + gext.height) * sy;
float x1 = gx + (gext.x_bearing + gext.width) * sx;
float y1 = gy + gext.y_bearing * sy;
int ix0 = (int) floorf (hb_min (x0, x1)) - ext.x_origin;
int iy0 = (int) floorf (hb_min (y0, y1)) - ext.y_origin;
int ix1 = (int) ceilf (hb_max (x0, x1)) - ext.x_origin;
int iy1 = (int) ceilf (hb_max (y0, y1)) - ext.y_origin;
rects.push_back ({ix0, iy0, ix1, iy1});
}
}
}
static void overlay_rects_bgra (uint8_t *buf, unsigned w, unsigned h, unsigned stride,
const std::vector<rect_i_t> &rects)
{
for (const auto &rc : rects)
{
int x0 = hb_max (0, hb_min ((int) w, rc.x0));
int x1 = hb_max (0, hb_min ((int) w, rc.x1));
int y0 = hb_max (0, hb_min ((int) h, rc.y0));
int y1 = hb_max (0, hb_min ((int) h, rc.y1));
if (x1 - x0 < 1 || y1 - y0 < 1) continue;
auto blend_at = [&] (int x, int y)
{
uint32_t d;
hb_memcpy (&d, buf + y * stride + x * 4, 4);
uint8_t db = (uint8_t) (d & 0xFF);
uint8_t dg = (uint8_t) ((d >> 8) & 0xFF);
uint8_t dr = (uint8_t) ((d >> 16) & 0xFF);
uint8_t da = (uint8_t) (d >> 24);
uint8_t ob = (uint8_t) ((db + 255) / 2);
uint8_t og = (uint8_t) (dg / 2);
uint8_t orr = (uint8_t) ((dr + 255) / 2);
uint8_t oa = hb_max (da, (uint8_t) 128);
uint32_t o = (uint32_t) ob | ((uint32_t) og << 8) | ((uint32_t) orr << 16) | ((uint32_t) oa << 24);
hb_memcpy (buf + y * stride + x * 4, &o, 4);
};
for (int x = x0; x < x1; x++)
{
blend_at (x, y0);
blend_at (x, y1 - 1);
}
for (int y = y0; y < y1; y++)
{
blend_at (x0, y);
blend_at (x1 - 1, y);
}
}
}
static void overlay_rects_rgb (std::vector<uint8_t> &buf, unsigned w, unsigned h,
const std::vector<rect_i_t> &rects)
{
for (const auto &rc : rects)
{
int x0 = hb_max (0, hb_min ((int) w, rc.x0));
int x1 = hb_max (0, hb_min ((int) w, rc.x1));
int y0 = hb_max (0, hb_min ((int) h, rc.y0));
int y1 = hb_max (0, hb_min ((int) h, rc.y1));
if (x1 - x0 < 1 || y1 - y0 < 1) continue;
auto blend_at = [&] (int x, int y)
{
uint8_t *p = &buf[(y * w + x) * 3];
p[0] = (uint8_t) ((p[0] + 255) / 2);
p[1] = (uint8_t) (p[1] / 2);
p[2] = (uint8_t) ((p[2] + 255) / 2);
};
for (int x = x0; x < x1; x++)
{
blend_at (x, y0);
blend_at (x, y1 - 1);
}
for (int y = y0; y < y1; y++)
{
blend_at (x0, y);
blend_at (x1 - 1, y);
}
}
}
static uint8_t mul8 (uint8_t a, uint8_t b)
{
unsigned t = (unsigned) a * b + 128;
return (uint8_t) ((t + (t >> 8)) >> 8);
}
static uint32_t premul_pixel (uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
return (uint32_t) mul8 (b, a)
| ((uint32_t) mul8 (g, a) << 8)
| ((uint32_t) mul8 (r, a) << 16)
| ((uint32_t) a << 24);
}
static uint32_t src_over_premul (uint32_t src, uint32_t dst)
{
uint8_t sa = (uint8_t) (src >> 24);
if (sa == 255) return src;
if (!sa) return dst;
unsigned inv_sa = 255 - sa;
uint8_t b = (uint8_t) (src & 0xFF) +
mul8 ((uint8_t) (dst & 0xFF), inv_sa);
uint8_t g = (uint8_t) ((src >> 8) & 0xFF) +
mul8 ((uint8_t) ((dst >> 8) & 0xFF), inv_sa);
uint8_t r = (uint8_t) ((src >> 16) & 0xFF) +
mul8 ((uint8_t) ((dst >> 16) & 0xFF), inv_sa);
uint8_t a = sa + mul8 ((uint8_t) (dst >> 24), inv_sa);
return (uint32_t) b | ((uint32_t) g << 8) | ((uint32_t) r << 16) | ((uint32_t) a << 24);
}
uint32_t background_premul_pixel () const
{
return premul_pixel (bg_r, bg_g, bg_b, bg_a);
}
hb_raster_image_t *composite_background (hb_raster_image_t *img)
{
hb_raster_extents_t ext;
hb_raster_image_get_extents (img, &ext);
hb_raster_image_t *out = hb_raster_image_create_or_fail ();
if (!out) return nullptr;
if (!hb_raster_image_configure (out, HB_RASTER_FORMAT_BGRA32, &ext))
{
hb_raster_image_destroy (out);
return nullptr;
}
const uint8_t *src = hb_raster_image_get_buffer (img);
uint8_t *dst = const_cast<uint8_t *> (hb_raster_image_get_buffer (out));
uint32_t bg = background_premul_pixel ();
for (unsigned y = 0; y < ext.height; y++)
for (unsigned x = 0; x < ext.width; x++)
{
uint32_t s;
hb_memcpy (&s, src + y * ext.stride + x * 4, 4);
uint32_t d = src_over_premul (s, bg);
hb_memcpy (dst + y * ext.stride + x * 4, &d, 4);
}
return out;
}
const char *get_output_format (helper_image_protocol_t *protocol)
{
if (output_format)
{
*protocol = helper_image_protocol_t::NONE;
return output_format;
}
#ifdef HAVE_PNG
return helper_image_get_implicit_output_format (out_fp,
HB_RASTER_DEFAULT_OUTPUT_FORMAT,
protocol);
#else
return helper_image_get_implicit_output_format (out_fp,
HB_RASTER_DEFAULT_OUTPUT_FORMAT,
protocol);
#endif
}
void write_blob (hb_blob_t *blob)
{
if (!blob) return;
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
if (data && len)
helper_image_stdio_write_func (out_fp, (const unsigned char *) data, len);
hb_blob_destroy (blob);
}
void write_png (hb_raster_image_t *img HB_UNUSED, helper_image_protocol_t protocol HB_UNUSED)
{
#ifdef HAVE_PNG
hb_raster_image_t *png_img = img;
if (bg_a)
{
png_img = composite_background (img);
if (!png_img) return;
}
hb_blob_t *blob = hb_raster_image_serialize_to_png_or_fail (png_img);
if (!blob)
{
if (png_img != img)
hb_raster_image_destroy (png_img);
return;
}
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
if (data && len)
helper_image_write_png_data ((const unsigned char *) data, len,
helper_image_stdio_write_func, out_fp, protocol);
hb_blob_destroy (blob);
if (png_img != img)
hb_raster_image_destroy (png_img);
#endif
}
void write_ansi (hb_raster_image_t *img)
{
hb_raster_extents_t ext;
hb_raster_image_get_extents (img, &ext);
const uint8_t *src = hb_raster_image_get_buffer (img);
if (!src || !ext.width || !ext.height) return;
std::vector<uint32_t> rgb (ext.width * ext.height);
for (unsigned y = 0; y < ext.height; y++)
{
const uint8_t *row = src + (size_t) (ext.height - 1 - y) * ext.stride;
for (unsigned x = 0; x < ext.width; x++)
{
uint32_t px;
hb_memcpy (&px, row + x * 4, 4);
uint8_t b = (uint8_t) (px & 0xFF);
uint8_t g = (uint8_t) ((px >> 8) & 0xFF);
uint8_t r = (uint8_t) ((px >> 16) & 0xFF);
uint8_t a = (uint8_t) (px >> 24);
unsigned inv_a = 255 - a;
r = (uint8_t) (r + ((bg_r * inv_a + 128 + ((bg_r * inv_a + 128) >> 8)) >> 8));
g = (uint8_t) (g + ((bg_g * inv_a + 128 + ((bg_g * inv_a + 128) >> 8)) >> 8));
b = (uint8_t) (b + ((bg_b * inv_a + 128 + ((bg_b * inv_a + 128) >> 8)) >> 8));
rgb[y * ext.width + x] = (uint32_t) b | ((uint32_t) g << 8) | ((uint32_t) r << 16);
}
}
helper_image_write_to_ansi_stream_rgb24 (rgb.data (), ext.width, ext.height, ext.width * 4,
helper_image_stdio_write_func, out_fp);
}
void write_bgra_image (hb_raster_image_t *img)
{
hb_raster_extents_t ext;
hb_raster_image_get_extents (img, &ext);
const uint8_t *buf = hb_raster_image_get_buffer (img);
helper_image_protocol_t protocol;
const char *format = get_output_format (&protocol);
if (0 == g_ascii_strcasecmp (format, "ansi"))
write_ansi (img);
else if (0 == g_ascii_strcasecmp (format, "png"))
write_png (img, protocol);
else
write_ppm (buf, ext.width, ext.height, ext.stride);
}
void write_a8_image (hb_raster_image_t *img,
float sx, float sy, float step, bool vertical,
const hb_raster_extents_t &fallback_ext)
{
hb_raster_extents_t ext = fallback_ext;
hb_raster_image_get_extents (img, &ext);
if (!ext.width || !ext.height) return;
uint8_t fg_r = hb_color_get_red (fg_color);
uint8_t fg_g = hb_color_get_green (fg_color);
uint8_t fg_b = hb_color_get_blue (fg_color);
uint8_t fg_a = hb_color_get_alpha (fg_color);
uint32_t bg_px = background_premul_pixel ();
const uint8_t *src = hb_raster_image_get_buffer (img);
hb_raster_image_t *bgra = hb_raster_image_create_or_fail ();
if (!bgra) return;
hb_raster_extents_t bgra_ext = ext;
bgra_ext.stride = 0;
if (!hb_raster_image_configure (bgra, HB_RASTER_FORMAT_BGRA32, &bgra_ext))
{
hb_raster_image_destroy (bgra);
return;
}
hb_raster_extents_t bgra_actual_ext;
hb_raster_image_get_extents (bgra, &bgra_actual_ext);
uint8_t *dst = const_cast<uint8_t *> (hb_raster_image_get_buffer (bgra));
for (unsigned y = 0; y < ext.height; y++)
for (unsigned x = 0; x < ext.width; x++)
{
uint8_t a = src[y * ext.stride + x];
uint8_t sa = mul8 (a, fg_a);
uint32_t fg_px = premul_pixel (fg_r, fg_g, fg_b, sa);
uint32_t px = src_over_premul (fg_px, bg_px);
hb_memcpy (dst + y * bgra_actual_ext.stride + x * 4, &px, 4);
}
if (show_extents)
{
std::vector<rect_i_t> rects;
collect_ink_extents_rects (sx, sy, step, vertical, ext, rects);
overlay_rects_bgra (dst, ext.width, ext.height, bgra_actual_ext.stride, rects);
}
write_bgra_image (bgra);
hb_raster_image_destroy (bgra);
}
/* Write a PPM file from BGRA32 buffer; Y-flipped, composited over white. */
void write_ppm (const uint8_t *buf, unsigned w, unsigned h, unsigned stride)
{
if (!w || !h) return;
fprintf (out_fp, "P6\n%u %u\n255\n", w, h);
std::vector<uint8_t> row_buf (w * 3);
for (unsigned row = 0; row < h; row++)
{
const uint8_t *src = buf + (h - 1 - row) * stride;
for (unsigned x = 0; x < w; x++)
{
uint32_t px;
hb_memcpy (&px, src + x * 4, 4);
uint8_t b = (uint8_t) (px & 0xFF);
uint8_t g = (uint8_t) ((px >> 8) & 0xFF);
uint8_t r = (uint8_t) ((px >> 16) & 0xFF);
uint8_t a = (uint8_t) (px >> 24);
/* Composite over background color */
unsigned inv_a = 255 - a;
row_buf[x * 3 + 0] = (uint8_t) (r + ((bg_r * inv_a + 128 + ((bg_r * inv_a + 128) >> 8)) >> 8));
row_buf[x * 3 + 1] = (uint8_t) (g + ((bg_g * inv_a + 128 + ((bg_g * inv_a + 128) >> 8)) >> 8));
row_buf[x * 3 + 2] = (uint8_t) (b + ((bg_b * inv_a + 128 + ((bg_b * inv_a + 128) >> 8)) >> 8));
}
fwrite (row_buf.data (), 1, w * 3, out_fp);
}
}
hb_color_t fg_color = HB_COLOR (0, 0, 0, 255);
uint8_t bg_r = 255, bg_g = 255, bg_b = 255, bg_a = 255;
hb_raster_draw_t *rdr = nullptr;
hb_raster_paint_t *pnt = nullptr;
hb_font_t *font = nullptr;
unsigned subpixel_bits = 0;
bool has_color = false;
hb_direction_t direction = HB_DIRECTION_INVALID;
std::vector<line_t> lines;
};
#undef HB_RASTER_DEFAULT_OUTPUT_FORMAT
#endif