Skip to content

Instantly share code, notes, and snippets.

@hjanuschka
Last active October 15, 2025 07:57
Show Gist options
  • Save hjanuschka/6e7ab70c5a1743227a9934623c838023 to your computer and use it in GitHub Desktop.
Save hjanuschka/6e7ab70c5a1743227a9934623c838023 to your computer and use it in GitHub Desktop.
Chromium-compatible Skia text rendering fix - matching LCD subpixel rendering
#include "skia.h"
#include <stdexcept>
#include <wrl/client.h>
#include <dwrite.h>
Skia::Skia() {
mgr = SkFontMgr_New_DirectWrite(); // Platform-specific for Windows
}
void Skia::set_font(sk_sp<SkData> data) {
typeface = mgr->makeFromData(std::move(data));
if (!typeface)
throw std::runtime_error("Couldn't load skia font from data");
}
// Helper function to get Windows DirectWrite rendering parameters
// This matches Chromium's approach in ui/gfx/font_util_win.cc
static void GetDirectWriteRenderingParams(float& out_contrast, float& out_gamma) {
// Default values matching Chromium on Windows
out_contrast = 1.0f; // SK_GAMMA_CONTRAST on Windows
out_gamma = 0.0f; // SK_GAMMA_EXPONENT (sRGB)
Microsoft::WRL::ComPtr<IDWriteFactory> factory;
if (SUCCEEDED(DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(factory.GetAddressOf())))) {
Microsoft::WRL::ComPtr<IDWriteRenderingParams> rendering_params;
if (SUCCEEDED(factory->CreateRenderingParams(&rendering_params))) {
// Get system-specific values from DirectWrite
out_contrast = rendering_params->GetEnhancedContrast();
out_gamma = rendering_params->GetGamma();
// Clamp values to Skia's acceptable ranges
// See: third_party/skia/include/core/SkSurfaceProps.h
out_contrast = std::clamp(out_contrast, 0.0f, 1.0f);
out_gamma = std::clamp(out_gamma, 0.0f, 4.0f - std::numeric_limits<float>::epsilon());
}
}
}
ImageData Skia::render_text(const Shaper& shaper, const unsigned font_size) const {
const auto size = static_cast<float>(font_size);
SkFont font(typeface, size);
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
font.setSubpixel(true);
font.setHinting(SkFontHinting::kSlight);
font.setLinearMetrics(false);
font.setBaselineSnap(false);
font.setForceAutoHinting(true);
SkTextBlobBuilder builder;
const auto& run_buffer = builder.allocRunPos(font, static_cast<int>(shaper.get_glyph_count()));
float x = 0;
for (const auto& [cluster_id, glyphs] : shaper.get_clusters()) {
for (const unsigned glyph_id : glyphs) {
run_buffer.glyphs[glyph_id] = shaper.get_glyph_info()[glyph_id].codepoint;
const auto& pos = shaper.get_glyph_pos()[glyph_id];
reinterpret_cast<SkPoint*>(run_buffer.pos)[glyph_id] =
SkPoint::Make(
x + static_cast<SkScalar>(pos.x_offset) / 64.0f,
static_cast<SkScalar>(-pos.y_offset) / 64.0f
);
x += static_cast<SkScalar>(pos.x_advance) / 64.0f;
}
}
const sk_sp<SkTextBlob> blob = builder.make();
const SkRect& bounds = blob->bounds();
const int left = SkScalarFloorToInt(bounds.left());
const int top = SkScalarFloorToInt(bounds.top());
const int right = SkScalarCeilToInt(bounds.right());
const int bottom = SkScalarCeilToInt(bounds.bottom());
const int width = right - left;
const int height = bottom - top;
// Get DirectWrite rendering parameters for Chromium-matching text rendering
// References:
// - ui/gfx/font_util_win.cc:43-46
// - skia/ext/legacy_display_globals.cc:73
float text_contrast, text_gamma;
GetDirectWriteRenderingParams(text_contrast, text_gamma);
// Create SkSurfaceProps with LCD pixel geometry and DirectWrite parameters
// This is CRITICAL for matching Chromium's text rendering on Windows
// Reference: third_party/blink/renderer/platform/fonts/win/font_platform_data_win.cc:54
SkSurfaceProps surface_props(
0, // flags
kRGB_H_SkPixelGeometry, // Horizontal RGB subpixel layout for LCD rendering
text_contrast, // Enhanced contrast from DirectWrite
text_gamma // Gamma from DirectWrite
);
const sk_sp<SkSurface> surface = SkSurfaces::Raster(
SkImageInfo::Make(
width,
height,
kN32_SkColorType,
kPremul_SkAlphaType,
SkColorSpace::MakeSRGBLinear()
),
&surface_props // Pass the surface properties for proper LCD text rendering
);
SkCanvas* canvas = surface->getCanvas();
canvas->clear(SK_ColorWHITE);
SkPaint p;
p.setColor(SK_ColorBLACK);
p.setBlendMode(SkBlendMode::kSrcOver);
canvas->drawTextBlob(blob, -static_cast<SkScalar>(left), -static_cast<SkScalar>(top), p);
// const sk_sp<SkSurface> surface_final = SkSurfaces::Raster(SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB()));
// surface_final->getCanvas()->drawImage(surface->makeImageSnapshot(), 0, 0);
SkPixmap pixmap;
if (!surface->peekPixels(&pixmap))
throw std::runtime_error("Couldn't peek pixels from Skia surface");
const auto c = static_cast<Eigen::Index>(1 + shaper.get_clusters().size());
ImageData img(c, height, width);
img.setZero();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
SkColor pixel = pixmap.getColor(x, y);
uint8_t r = SkColorGetR(pixel);
img(0, y, x) = r;
}
}
return img;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment