| /* |
| * 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 |