Last active
October 15, 2025 07:57
-
-
Save hjanuschka/6e7ab70c5a1743227a9934623c838023 to your computer and use it in GitHub Desktop.
Chromium-compatible Skia text rendering fix - matching LCD subpixel rendering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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