blob: 69e88a68cc6a2a2fae94bfd4fa7a7e9bd8d5311d [file] [edit]
/*
* 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
*/
#include "hb.hh"
#include "hb-raster.h"
#include "hb-raster-image.hh"
#include "hb-gpu-draw.hh"
#include <math.h>
#ifndef HB_NO_RASTER_GPU
struct hb_raster_gpu_texel_t
{
int16_t r, g, b, a;
};
struct hb_raster_gpu_blob_t
{
const char *data = nullptr;
unsigned texel_count = 0;
float min_x = 0.f;
float min_y = 0.f;
float max_x = 0.f;
float max_y = 0.f;
int num_hbands = 0;
int num_vbands = 0;
float scale_x = 0.f;
float scale_y = 0.f;
float band_scale_x = 0.f;
float band_scale_y = 0.f;
float band_offset_x = 0.f;
float band_offset_y = 0.f;
unsigned band_base = 2;
};
static constexpr float HB_RASTER_GPU_INV_UNITS = 1.f / HB_GPU_UNITS_PER_EM;
static constexpr float HB_RASTER_GPU_EPSILON = 1.f / 65536.f;
static HB_ALWAYS_INLINE unsigned
hb_raster_gpu_decode_offset (int16_t v)
{
return (unsigned) ((int) v + 32768);
}
static HB_ALWAYS_INLINE float
hb_raster_gpu_clamp01 (float v)
{
return hb_clamp (v, 0.f, 1.f);
}
static HB_ALWAYS_INLINE float
hb_raster_gpu_smoothstep (float edge0,
float edge1,
float x)
{
float t = hb_raster_gpu_clamp01 ((x - edge0) / (edge1 - edge0));
return t * t * (3.f - 2.f * t);
}
static HB_ALWAYS_INLINE uint32_t
hb_raster_gpu_float_bits_to_uint (float v)
{
uint32_t u = 0;
hb_memcpy (&u, &v, sizeof (u));
return u;
}
static HB_ALWAYS_INLINE uint32_t
hb_raster_gpu_calc_root_code (float y1,
float y2,
float y3)
{
uint32_t i1 = hb_raster_gpu_float_bits_to_uint (y1) >> 31U;
uint32_t i2 = hb_raster_gpu_float_bits_to_uint (y2) >> 30U;
uint32_t i3 = hb_raster_gpu_float_bits_to_uint (y3) >> 29U;
uint32_t shift = (i2 & 2U) | (i1 & ~2U);
shift = (i3 & 4U) | (shift & ~4U);
return (0x2E74U >> shift) & 0x0101U;
}
static HB_ALWAYS_INLINE bool
hb_raster_gpu_read_texel (const hb_raster_gpu_blob_t *blob,
unsigned offset,
hb_raster_gpu_texel_t *out)
{
if (unlikely (offset >= blob->texel_count))
return false;
hb_memcpy (out, blob->data + offset * sizeof (*out), sizeof (*out));
return true;
}
static HB_ALWAYS_INLINE void
hb_raster_gpu_solve_horiz_poly (float ax,
float ay,
float bx,
float by,
float p1x,
float p1y,
float *r0,
float *r1)
{
float ra = 1.f / ay;
float rb = 0.5f / by;
float d = sqrtf (hb_max (by * by - ay * p1y, 0.f));
float t1 = (by - d) * ra;
float t2 = (by + d) * ra;
if (ay == 0.f)
t1 = t2 = p1y * rb;
*r0 = (ax * t1 - bx * 2.f) * t1 + p1x;
*r1 = (ax * t2 - bx * 2.f) * t2 + p1x;
}
static HB_ALWAYS_INLINE void
hb_raster_gpu_solve_vert_poly (float ax,
float ay,
float bx,
float by,
float p1x,
float p1y,
float *r0,
float *r1)
{
float ra = 1.f / ax;
float rb = 0.5f / bx;
float d = sqrtf (hb_max (bx * bx - ax * p1x, 0.f));
float t1 = (bx - d) * ra;
float t2 = (bx + d) * ra;
if (ax == 0.f)
t1 = t2 = p1x * rb;
*r0 = (ay * t1 - by * 2.f) * t1 + p1y;
*r1 = (ay * t2 - by * 2.f) * t2 + p1y;
}
static HB_ALWAYS_INLINE float
hb_raster_gpu_calc_coverage (float xcov,
float ycov,
float xwgt,
float ywgt)
{
float coverage = hb_max (fabsf (xcov * xwgt + ycov * ywgt) /
hb_max (xwgt + ywgt, HB_RASTER_GPU_EPSILON),
hb_min (fabsf (xcov), fabsf (ycov)));
return hb_raster_gpu_clamp01 (coverage);
}
static bool
hb_raster_gpu_decode_blob (const char *data,
unsigned texel_count,
hb_raster_gpu_blob_t *blob)
{
if (unlikely (texel_count < 2))
return false;
blob->data = data;
blob->texel_count = texel_count;
hb_raster_gpu_texel_t header0;
hb_raster_gpu_texel_t header1;
if (unlikely (!hb_raster_gpu_read_texel (blob, 0, &header0) ||
!hb_raster_gpu_read_texel (blob, 1, &header1)))
return false;
blob->min_x = header0.r * HB_RASTER_GPU_INV_UNITS;
blob->min_y = header0.g * HB_RASTER_GPU_INV_UNITS;
blob->max_x = header0.b * HB_RASTER_GPU_INV_UNITS;
blob->max_y = header0.a * HB_RASTER_GPU_INV_UNITS;
blob->num_hbands = header1.r;
blob->num_vbands = header1.g;
blob->scale_x = (float) header1.b;
blob->scale_y = (float) header1.a;
if (unlikely (blob->num_hbands <= 0 || blob->num_vbands <= 0 ||
blob->max_x < blob->min_x ||
blob->max_y < blob->min_y))
return false;
unsigned band_headers = (unsigned) blob->num_hbands + (unsigned) blob->num_vbands;
if (unlikely (blob->band_base + band_headers > texel_count))
return false;
float ext_size_x = blob->max_x - blob->min_x;
float ext_size_y = blob->max_y - blob->min_y;
blob->band_scale_x = (float) blob->num_vbands /
hb_max (ext_size_x, HB_RASTER_GPU_EPSILON);
blob->band_scale_y = (float) blob->num_hbands /
hb_max (ext_size_y, HB_RASTER_GPU_EPSILON);
blob->band_offset_x = -blob->min_x * blob->band_scale_x;
blob->band_offset_y = -blob->min_y * blob->band_scale_y;
return true;
}
static bool
hb_raster_gpu_render_single (const hb_raster_gpu_blob_t *blob,
float render_x,
float render_y,
float pixels_per_em_x,
float pixels_per_em_y,
float *coverage)
{
int band_index_x = hb_clamp ((int) (render_x * blob->band_scale_x + blob->band_offset_x),
0, blob->num_vbands - 1);
int band_index_y = hb_clamp ((int) (render_y * blob->band_scale_y + blob->band_offset_y),
0, blob->num_hbands - 1);
float xcov = 0.f;
float xwgt = 0.f;
hb_raster_gpu_texel_t hband_data;
if (unlikely (!hb_raster_gpu_read_texel (blob, blob->band_base + (unsigned) band_index_y, &hband_data) ||
hband_data.r < 0))
return false;
int h_curve_count = hband_data.r;
float h_split = hband_data.a * HB_RASTER_GPU_INV_UNITS;
bool h_left_ray = render_x < h_split;
unsigned h_data_offset = hb_raster_gpu_decode_offset (h_left_ray ? hband_data.b : hband_data.g);
for (int ci = 0; ci < h_curve_count; ci++)
{
hb_raster_gpu_texel_t curve_index;
if (unlikely (!hb_raster_gpu_read_texel (blob, h_data_offset + (unsigned) ci, &curve_index)))
return false;
unsigned curve_offset = hb_raster_gpu_decode_offset (curve_index.r);
hb_raster_gpu_texel_t raw12;
hb_raster_gpu_texel_t raw3;
if (unlikely (!hb_raster_gpu_read_texel (blob, curve_offset, &raw12) ||
!hb_raster_gpu_read_texel (blob, curve_offset + 1u, &raw3)))
return false;
float q12x = raw12.r * HB_RASTER_GPU_INV_UNITS;
float q12y = raw12.g * HB_RASTER_GPU_INV_UNITS;
float q12z = raw12.b * HB_RASTER_GPU_INV_UNITS;
float q12w = raw12.a * HB_RASTER_GPU_INV_UNITS;
float q3x = raw3.r * HB_RASTER_GPU_INV_UNITS;
float q3y = raw3.g * HB_RASTER_GPU_INV_UNITS;
float p12x = q12x - render_x;
float p12y = q12y - render_y;
float p12z = q12z - render_x;
float p12w = q12w - render_y;
float p3x = q3x - render_x;
float p3y = q3y - render_y;
if (h_left_ray)
{
if (hb_min (hb_min (p12x, p12z), p3x) * pixels_per_em_x > 0.5f)
break;
}
else
{
if (hb_max (hb_max (p12x, p12z), p3x) * pixels_per_em_x < -0.5f)
break;
}
uint32_t code = hb_raster_gpu_calc_root_code (p12y, p12w, p3y);
if (!code)
continue;
float ax = q12x - q12z * 2.f + q3x;
float ay = q12y - q12w * 2.f + q3y;
float bx = q12x - q12z;
float by = q12y - q12w;
float r0, r1;
hb_raster_gpu_solve_horiz_poly (ax, ay, bx, by, p12x, p12y, &r0, &r1);
r0 *= pixels_per_em_x;
r1 *= pixels_per_em_x;
float cov0 = h_left_ray ? hb_raster_gpu_clamp01 (0.5f - r0)
: hb_raster_gpu_clamp01 (r0 + 0.5f);
float cov1 = h_left_ray ? hb_raster_gpu_clamp01 (0.5f - r1)
: hb_raster_gpu_clamp01 (r1 + 0.5f);
if (code & 1U)
{
xcov += cov0;
xwgt = hb_max (xwgt, hb_raster_gpu_clamp01 (1.f - fabsf (r0) * 2.f));
}
if (code > 1U)
{
xcov -= cov1;
xwgt = hb_max (xwgt, hb_raster_gpu_clamp01 (1.f - fabsf (r1) * 2.f));
}
}
float ycov = 0.f;
float ywgt = 0.f;
hb_raster_gpu_texel_t vband_data;
if (unlikely (!hb_raster_gpu_read_texel (blob,
blob->band_base + (unsigned) blob->num_hbands + (unsigned) band_index_x,
&vband_data) ||
vband_data.r < 0))
return false;
int v_curve_count = vband_data.r;
float v_split = vband_data.a * HB_RASTER_GPU_INV_UNITS;
bool v_left_ray = render_y < v_split;
unsigned v_data_offset = hb_raster_gpu_decode_offset (v_left_ray ? vband_data.b : vband_data.g);
for (int ci = 0; ci < v_curve_count; ci++)
{
hb_raster_gpu_texel_t curve_index;
if (unlikely (!hb_raster_gpu_read_texel (blob, v_data_offset + (unsigned) ci, &curve_index)))
return false;
unsigned curve_offset = hb_raster_gpu_decode_offset (curve_index.r);
hb_raster_gpu_texel_t raw12;
hb_raster_gpu_texel_t raw3;
if (unlikely (!hb_raster_gpu_read_texel (blob, curve_offset, &raw12) ||
!hb_raster_gpu_read_texel (blob, curve_offset + 1u, &raw3)))
return false;
float q12x = raw12.r * HB_RASTER_GPU_INV_UNITS;
float q12y = raw12.g * HB_RASTER_GPU_INV_UNITS;
float q12z = raw12.b * HB_RASTER_GPU_INV_UNITS;
float q12w = raw12.a * HB_RASTER_GPU_INV_UNITS;
float q3x = raw3.r * HB_RASTER_GPU_INV_UNITS;
float q3y = raw3.g * HB_RASTER_GPU_INV_UNITS;
float p12x = q12x - render_x;
float p12y = q12y - render_y;
float p12z = q12z - render_x;
float p12w = q12w - render_y;
float p3x = q3x - render_x;
float p3y = q3y - render_y;
if (v_left_ray)
{
if (hb_min (hb_min (p12y, p12w), p3y) * pixels_per_em_y > 0.5f)
break;
}
else
{
if (hb_max (hb_max (p12y, p12w), p3y) * pixels_per_em_y < -0.5f)
break;
}
uint32_t code = hb_raster_gpu_calc_root_code (p12x, p12z, p3x);
if (!code)
continue;
float ax = q12x - q12z * 2.f + q3x;
float ay = q12y - q12w * 2.f + q3y;
float bx = q12x - q12z;
float by = q12y - q12w;
float r0, r1;
hb_raster_gpu_solve_vert_poly (ax, ay, bx, by, p12x, p12y, &r0, &r1);
r0 *= pixels_per_em_y;
r1 *= pixels_per_em_y;
float cov0 = v_left_ray ? hb_raster_gpu_clamp01 (0.5f - r0)
: hb_raster_gpu_clamp01 (r0 + 0.5f);
float cov1 = v_left_ray ? hb_raster_gpu_clamp01 (0.5f - r1)
: hb_raster_gpu_clamp01 (r1 + 0.5f);
if (code & 1U)
{
ycov -= cov0;
ywgt = hb_max (ywgt, hb_raster_gpu_clamp01 (1.f - fabsf (r0) * 2.f));
}
if (code > 1U)
{
ycov += cov1;
ywgt = hb_max (ywgt, hb_raster_gpu_clamp01 (1.f - fabsf (r1) * 2.f));
}
}
*coverage = hb_raster_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);
return true;
}
static bool
hb_raster_gpu_render_coverage (const hb_raster_gpu_blob_t *blob,
float render_x,
float render_y,
float *coverage)
{
float c = 0.f;
if (unlikely (!hb_raster_gpu_render_single (blob, render_x, render_y,
1.f, 1.f, &c)))
return false;
#ifndef HB_GPU_NO_MSAA
float ppem = hb_min (blob->scale_x, blob->scale_y);
if (ppem < 16.f)
{
float c0, c1, c2, c3;
if (unlikely (!hb_raster_gpu_render_single (blob, render_x - 1.f / 3.f, render_y - 1.f / 3.f, 1.f, 1.f, &c0) ||
!hb_raster_gpu_render_single (blob, render_x + 1.f / 3.f, render_y - 1.f / 3.f, 1.f, 1.f, &c1) ||
!hb_raster_gpu_render_single (blob, render_x - 1.f / 3.f, render_y + 1.f / 3.f, 1.f, 1.f, &c2) ||
!hb_raster_gpu_render_single (blob, render_x + 1.f / 3.f, render_y + 1.f / 3.f, 1.f, 1.f, &c3)))
return false;
float msaa = 0.25f * (c0 + c1 + c2 + c3);
c = c + (msaa - c) * hb_raster_gpu_smoothstep (16.f, 8.f, ppem);
}
#endif
*coverage = c;
return true;
}
static hb_raster_image_t *
hb_raster_gpu_render_from_blob_data_or_fail (const char *data,
unsigned texel_count)
{
hb_raster_gpu_blob_t blob;
if (unlikely (!hb_raster_gpu_decode_blob (data, texel_count, &blob)))
return nullptr;
constexpr float pad = 0.5f;
hb_raster_extents_t ext;
ext.x_origin = (int) floorf (blob.min_x - pad);
ext.y_origin = (int) floorf (blob.min_y - pad);
ext.width = (unsigned) hb_max (0, (int) ceilf (blob.max_x + pad) - ext.x_origin);
ext.height = (unsigned) hb_max (0, (int) ceilf (blob.max_y + pad) - ext.y_origin);
ext.stride = (ext.width + 3u) & ~3u;
hb_raster_image_t *image = hb_raster_image_create_or_fail ();
if (unlikely (!image))
return nullptr;
if (unlikely (!image->configure (HB_RASTER_FORMAT_A8, ext)))
goto fail;
image->clear ();
for (unsigned y = 0; y < ext.height; y++)
for (unsigned x = 0; x < ext.width; x++)
{
float render_x = (float) ext.x_origin + (float) x + 0.5f;
float render_y = (float) ext.y_origin + (float) y + 0.5f;
float coverage;
if (unlikely (!hb_raster_gpu_render_coverage (&blob,
render_x,
render_y,
&coverage)))
goto fail;
image->buffer.arrayZ[y * ext.stride + x] =
(uint8_t) (hb_raster_gpu_clamp01 (coverage) * 255.f + 0.5f);
}
return image;
fail:
hb_raster_image_destroy (image);
return nullptr;
}
#endif /* !HB_NO_RASTER_GPU */
/**
* hb_raster_gpu_render_from_blob_or_fail:
* @blob: a blob produced by hb_gpu_draw_encode()
*
* Decodes a glyph blob produced by hb_gpu_draw_encode() and rasterizes
* it into a new @HB_RASTER_FORMAT_A8 image using the same coverage
* algorithm as hb_gpu_render().
*
* Return value: (transfer full):
* A rendered #hb_raster_image_t, or `NULL` on malformed input or
* allocation failure. An empty blob returns an empty image.
*
* XSince: REPLACEME
**/
hb_raster_image_t *
hb_raster_gpu_render_from_blob_or_fail (hb_blob_t *blob)
{
#ifdef HB_NO_RASTER_GPU
return nullptr;
#else
if (unlikely (!blob))
return nullptr;
unsigned length = hb_blob_get_length (blob);
if (!length)
return hb_raster_image_create_or_fail ();
if (unlikely (length % sizeof (hb_raster_gpu_texel_t)))
return nullptr;
const char *data = hb_blob_get_data (blob, nullptr);
if (unlikely (!data))
return nullptr;
return hb_raster_gpu_render_from_blob_data_or_fail (data,
length / sizeof (hb_raster_gpu_texel_t));
#endif
}