Write glyph quads into a caller-provided RGBA8 pixel buffer.
Applies coverage-multiplied alpha blend per glyph cell, compositing over whatever is already in dst rather than overwriting it - a glyph's bounding box includes its own zero-coverage interior (the hole in an 'o' or 'e'), so composited blending is what keeps a pre-filled background intact under that hole instead of a low-coverage pixel punching it transparent. The destination buffer must be row-major RGBA8 with stride == buf_w * 4 bytes. Quads that fall outside [0, buf_w) x [0, buf_h) are clipped per pixel.
auto layout =
lay_out(text, atlas, 0.F, 0.F, wrap_w);
for (
auto&
q : layout.quads) { ... }
const std::vector< float > * pixels
void rasterize_quads(std::span< const GlyphQuad > quads, GlyphAtlas &atlas, glm::vec4 color, uint8_t *dst, uint32_t buf_w, uint32_t buf_h)
Write glyph quads into a caller-provided RGBA8 pixel buffer.
LayoutResult lay_out(std::string_view text, GlyphAtlas &atlas, float pen_x, float pen_y, uint32_t wrap_w, uint32_t tab_width)
Lay out a UTF-8 string into a sequence of screen-space quads.
251{
252 const uint8_t cr =
static_cast<uint8_t
>(std::clamp(
color.r, 0.F, 1.F) * 255.F);
253 const uint8_t cg =
static_cast<uint8_t
>(std::clamp(
color.g, 0.F, 1.F) * 255.F);
254 const uint8_t cb =
static_cast<uint8_t
>(std::clamp(
color.b, 0.F, 1.F) * 255.F);
255 const uint8_t ca =
static_cast<uint8_t
>(std::clamp(
color.a, 0.F, 1.F) * 255.F);
256
258 const std::span<const uint8_t> atlas_pixels = atlas_tex.
pixel_bytes(0);
259 const uint32_t atlas_size = atlas.
atlas_size();
260
261 for (
const auto&
q : quads) {
262 const auto gx =
static_cast<int32_t
>(std::floor(
q.x0));
263 const auto gy =
static_cast<int32_t
>(std::floor(
q.y0));
264 const auto gw =
static_cast<uint32_t
>(std::ceil(
q.x1 -
q.x0));
265 const auto gh =
static_cast<uint32_t
>(std::ceil(
q.y1 -
q.y0));
266
267 const auto src_x =
static_cast<uint32_t
>(
q.uv_x0 *
static_cast<float>(atlas_size));
268 const auto src_y =
static_cast<uint32_t
>(
q.uv_y0 *
static_cast<float>(atlas_size));
269
270 for (uint32_t row = 0; row < gh; ++row) {
271 const int32_t dst_row = gy + static_cast<int32_t>(row);
272 if (dst_row < 0 || static_cast<uint32_t>(dst_row) >= buf_h) {
273 continue;
274 }
275
276 const uint8_t* src_row = atlas_pixels.data()
277 +
static_cast<size_t>(
src_y + row) * atlas_size +
src_x;
278 uint8_t* dst_row_ptr =
dst +
static_cast<size_t>(dst_row) * buf_w * 4;
279
280 for (uint32_t col = 0; col < gw; ++col) {
281 const int32_t dst_col = gx + static_cast<int32_t>(col);
282 if (dst_col < 0 || static_cast<uint32_t>(dst_col) >= buf_w) {
283 continue;
284 }
285
286 const uint8_t coverage = src_row[col];
287 const uint32_t src_a = (static_cast<uint32_t>(coverage) * static_cast<uint32_t>(ca)) / 255U;
288
289 uint8_t*
px = dst_row_ptr +
static_cast<size_t>(dst_col) * 4;
290
291 const uint32_t dst_a =
px[3];
292 const uint32_t blended_dst_a = (dst_a * (255U - src_a)) / 255U;
293 const uint32_t out_a = src_a + blended_dst_a;
294
295 if (out_a == 0U) {
300 } else {
301 px[0] =
static_cast<uint8_t
>((cr * src_a +
px[0] * blended_dst_a) / out_a);
302 px[1] =
static_cast<uint8_t
>((cg * src_a +
px[1] * blended_dst_a) / out_a);
303 px[2] =
static_cast<uint8_t
>((cb * src_a +
px[2] * blended_dst_a) / out_a);
304 px[3] =
static_cast<uint8_t
>(out_a);
305 }
306 }
307 }
308 }
309}
std::span< const uint8_t > pixel_bytes(uint32_t layer=0) const
Read-only byte-level view over the pixel buffer.
SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
uint32_t atlas_size() const
Atlas texture dimension (width == height == atlas_size).
const Kakshya::TextureContainer & texture() const
The atlas texture as a TextureContainer (R8, atlas_size x atlas_size).