Skip to content

Instantly share code, notes, and snippets.

@mattleibow
Created April 28, 2026 12:48
Show Gist options
  • Select an option

  • Save mattleibow/59182057c05cbbeae7629cdfe1352740 to your computer and use it in GitHub Desktop.

Select an option

Save mattleibow/59182057c05cbbeae7629cdfe1352740 to your computer and use it in GitHub Desktop.
SkiaSharp Release Notes: v3.119.4 → 4.147.0 main (30 findings with upstream Skia m134-m147 benefits)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Release Notes — v3.119.4-preview.1.1..origin/main (2026-04-28)</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root{--bg:#f6f8fa;--border:#d8dee4;--row-hover:#f0f4f8;--muted:#656d76;}
body{background:var(--bg);color:#1f2328;font-size:14px;}
.shell{max-width:1100px;}
.summary-bar{background:#fff;border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:12px;}
.summary-num{font-size:1.5rem;font-weight:700;line-height:1;}
.summary-label{font-size:.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;}
.pill{display:inline-block;font-size:.68rem;font-weight:600;padding:1px 7px;border-radius:10px;cursor:pointer;user-select:none;white-space:nowrap;vertical-align:middle;}
.pill:hover{opacity:.8;}.pill.dim{opacity:.3;}
.i-breaking{background:#fee2e2;color:#991b1b;}.i-major{background:#dbeafe;color:#1e40af;}
.i-minor{background:#fef3c7;color:#92400e;}.i-patch{background:#f3f4f6;color:#6b7280;}
.ct-added{background:#dcfce7;color:#166534;}.ct-changed{background:#fef3c7;color:#92400e;}
.ct-fixed{background:#dbeafe;color:#1e40af;}.ct-deprecated{background:#fed7aa;color:#9a3412;}
.ct-removed{background:#fee2e2;color:#991b1b;}.ct-dependency{background:#ede9fe;color:#5b21b6;}
.ct-platform{background:#ccfbf1;color:#115e59;}.ct-upstream{background:#fce7f3;color:#9d174d;}
.l-pill{background:#e0e7ff;color:#3730a3;}
.row-item{border-bottom:1px solid #eaeef2;padding:7px 12px;cursor:pointer;}
.row-item:hover{background:var(--row-hover);}
.row-item.expanded{background:#fafbfc;}
.row-name{font-weight:600;font-size:.88rem;}.row-chevron{color:#bbb;font-size:.7rem;margin-right:6px;transition:transform .15s;}
.row-item.expanded .row-chevron{transform:rotate(90deg);color:#666;}
.row-desc{color:var(--muted);font-size:.78rem;margin-top:2px;}
.row-pills{margin-top:3px;}
.row-detail{display:none;padding:8px 0 4px 0;font-size:.82rem;}
.row-item.expanded .row-detail{display:block;}
.detail-grid{display:grid;grid-template-columns:110px 1fr;gap:2px 12px;}
.detail-label{color:var(--muted);font-weight:600;font-size:.72rem;text-transform:uppercase;}
.detail-value{font-size:.82rem;}
.detail-value code{font-size:.78rem;background:#f0f4f8;padding:1px 4px;border-radius:3px;}
.filter-bar{display:flex;flex-direction:column;gap:4px;padding:10px 12px;background:#fff;border:1px solid var(--border);border-radius:8px;margin-bottom:8px;}
.filter-row{display:flex;gap:4px;align-items:center;flex-wrap:wrap;}
.filter-label{font-size:.7rem;color:var(--muted);font-weight:600;width:80px;flex-shrink:0;text-align:right;padding-right:6px;}
.search-box{font-size:.8rem;padding:3px 10px;border:1px solid var(--border);border-radius:6px;}
.ctrl-select{font-size:.78rem;padding:2px 8px;border:1px solid var(--border);border-radius:6px;background:#fff;color:#1f2328;}
.reset-btn{font-size:.7rem;color:#3730a3;cursor:pointer;border:none;background:none;padding:2px 6px;}
.reset-btn:hover{text-decoration:underline;}
.count-badge{font-size:.6rem;background:#e5e7eb;color:#374151;padding:0 5px;border-radius:8px;margin-left:2px;vertical-align:middle;}
.tab-btn{font-size:.85rem;font-weight:600;padding:6px 16px;border:none;background:none;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;}
.tab-btn.active{color:#1e40af;border-bottom-color:#1e40af;}.tab-btn:hover{color:#1e40af;}
.tab-pane{display:none;}.tab-pane.active{display:block;}
.group-header{font-weight:700;font-size:.82rem;padding:8px 12px 4px;color:#1e40af;border-bottom:2px solid #dbeafe;margin-top:12px;background:#f8faff;}
.meta-footer{font-size:.72rem;color:var(--muted);margin-top:16px;padding:8px 12px;}
.meta-footer a{color:#3730a3;}
.dir-btn{font-size:.8rem;width:26px;height:26px;border:1px solid var(--border);border-radius:4px;background:#fff;cursor:pointer;color:#1e40af;font-weight:700;padding:0;line-height:26px;text-align:center;}
.dir-btn:hover{background:#f0f4f8;}
.md-content{background:#fff;border:1px solid var(--border);border-radius:8px;padding:20px 24px;font-size:.9rem;line-height:1.6;}
.md-content h2{font-size:1.1rem;margin-top:20px;padding-bottom:4px;border-bottom:1px solid var(--border);}
.md-content h3{font-size:1rem;margin-top:16px;}
.md-content code{font-size:.85em;background:#f0f4f8;padding:1px 4px;border-radius:3px;}
.md-content pre{background:#f6f8fa;padding:12px;border-radius:6px;overflow-x:auto;font-size:.82rem;}
.md-content ul{padding-left:20px;}
.md-content li{margin-bottom:4px;}
.pr-link{font-size:.72rem;color:#3730a3;text-decoration:none;}
.pr-link:hover{text-decoration:underline;}
</style>
</head>
<body>
<script>const DATA = {"meta": {"date": "2026-04-28", "schemaVersion": "1.0", "repo": "mono/SkiaSharp", "refFrom": "v3.119.4-preview.1.1", "refTo": "origin/main", "shaFrom": "f15b01b4df4dce510d1db721c7a75a261211f703", "shaTo": "be2b63901242d8a17769b09975f39dd11c533a60", "dateFrom": "2026-04-22", "dateTo": "2026-04-28", "commitCount": 64, "prCount": 52, "skiaMilestoneBumped": true, "skiaMilestoneFrom": 132, "skiaMilestoneTo": 147}, "summary": {"totalFindings": 30, "byChangeType": {"added": 7, "changed": 1, "fixed": 4, "deprecated": 0, "removed": 0, "dependency": 3, "platform": 7, "upstream": 8}, "byImportance": {"breaking": 1, "major": 8, "minor": 15, "patch": 6}, "byLabel": {"breaking-change": 1, "skia-upstream": 9, "security": 1, "new-platform": 2, "performance": 1}}, "findings": [{"name": "Major version bump from 3.x to 4.x", "changeType": "changed", "importance": "breaking", "description": "SkiaSharp's major version jumps from 3.119.x to 4.147.x. All NuGet packages, assembly versions, and file versions are updated. Consumers must update their PackageReference versions. No public API signatures were removed — this is a version numbering change that signals the large Skia engine upgrade.", "labels": ["breaking-change"], "pr": 3640, "prUrl": "https://github.com/mono/SkiaSharp/pull/3640", "author": "mattleibow", "migrationGuide": "Update all SkiaSharp NuGet package references from `3.x` to `4.x`.\n\n### Before\n```xml\n<PackageReference Include=\"SkiaSharp\" Version=\"3.119.4\" />\n```\n\n### After\n```xml\n<PackageReference Include=\"SkiaSharp\" Version=\"4.147.0\" />\n```", "slideBullet": "🚀 **SkiaSharp 4.0** — Major version bump signaling the Skia m147 engine upgrade with new APIs and platform support"}, {"name": "Variable font support for SkiaSharp and HarfBuzzSharp", "changeType": "added", "importance": "major", "description": "Full OpenType variable font APIs added to both SkiaSharp and HarfBuzzSharp. Query variation design axes and positions, clone typefaces with custom design coordinates, and set font variations at runtime. Includes Span overloads for zero-allocation scenarios.", "labels": [], "pr": 3703, "prUrl": "https://github.com/mono/SkiaSharp/pull/3703", "author": "nicklef", "affectedTypes": ["SKTypeface", "SKFontVariationAxis", "SKFontVariationPositionCoordinate", "SKFontArguments", "SKFourByteTag", "HarfBuzzSharp.Face", "HarfBuzzSharp.Font", "OpenTypeVarAxisInfo", "Variation"], "affectedMethods": ["SKTypeface.VariationDesignParameters", "SKTypeface.VariationDesignPosition", "SKTypeface.Clone", "Face.VariationAxisInfos", "Face.TryFindVariationAxis", "Font.SetVariations", "Font.SetVariationCoordsDesign"], "slideBullet": "🎨 **Variable Font Support** — Query and adjust font weight, width, and custom axes at runtime with full OpenType variation control"}, {"name": "Color font palette support", "changeType": "added", "importance": "major", "description": "Color font palette APIs added to both SkiaSharp and HarfBuzzSharp. Query palette metadata, retrieve palette colors, clone typefaces with specific palettes or custom palette overrides. Enables full control over multi-color emoji and icon fonts.", "pr": 3742, "prUrl": "https://github.com/mono/SkiaSharp/pull/3742", "affectedTypes": ["SKTypeface", "SKFontArguments", "SKFontPaletteOverride", "HarfBuzzSharp.Face"], "affectedMethods": ["SKTypeface.Clone(int paletteIndex)", "SKTypeface.Clone(SKFontArguments)", "Face.HasPalettes", "Face.PaletteCount", "Face.GetPaletteColors", "Face.GetPaletteFlags"], "slideBullet": "🎨 **Color Font Palettes** — Switch between color palettes in emoji and icon fonts, with custom override support"}, {"name": "SKSamplingOptions support for surface drawing", "changeType": "added", "importance": "major", "description": "New overloads on SKSurface.Draw and SKCanvas.DrawSurface accept SKSamplingOptions, giving control over filtering when drawing one surface onto another. Previously, surface-to-surface drawing always used default sampling.", "pr": 3491, "prUrl": "https://github.com/mono/SkiaSharp/pull/3491", "issue": 3344, "issueUrl": "https://github.com/mono/SkiaSharp/issues/3344", "affectedTypes": ["SKSurface", "SKCanvas"], "affectedMethods": ["SKSurface.Draw(SKCanvas, float, float, SKSamplingOptions, SKPaint)", "SKCanvas.DrawSurface(SKSurface, float, float, SKSamplingOptions, SKPaint)"], "cApiFunction": "sk_surface_draw_with_sampling", "slideBullet": "🖼️ **Surface Sampling Control** — Specify filtering quality when drawing surfaces with SKSamplingOptions"}, {"name": "CICP color space creation", "changeType": "added", "importance": "major", "description": "New SKColorSpace.CreateCicp() factory creates color spaces from ITU-T H.273 CICP (Coding-Independent Code Points) parameters, commonly used in video (H.264/H.265/AV1) and HDR workflows. Simplifies correct color handling for video-sourced content.", "pr": 3660, "prUrl": "https://github.com/mono/SkiaSharp/pull/3660", "affectedTypes": ["SKColorSpace", "SKColorspacePrimariesCicp", "SKColorspaceTransferFnCicp"], "affectedMethods": ["SKColorSpace.CreateCicp"], "cApiFunction": "sk_colorspace_new_cicp", "slideBullet": "🎬 **CICP Color Spaces** — Create color spaces from video standards (H.264/H.265/AV1) with a single call"}, {"name": "New SKColorType values: Bgra10101010XR, RgbF16F16F16x, R16Unorm", "changeType": "added", "importance": "minor", "description": "Three new color types added to the SKColorType enum: Bgra10101010XR (extended range 10-bit), RgbF16F16F16x (opaque half-float RGB), and R16Unorm (16-bit unsigned normalized single-channel). These support wider color gamuts and HDR content.", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "affectedTypes": ["SKColorType"], "slideBullet": "🌈 **Extended Color Types** — New Bgra10101010XR, RgbF16F16F16x, and R16Unorm for HDR and wide-gamut content"}, {"name": "SKPaint.GetFillPath overloads with SKPathBuilder", "changeType": "added", "importance": "minor", "description": "New GetFillPath overloads on SKPaint accept SKPathBuilder as the destination, aligning with Skia's migration toward immutable paths. Six new overloads covering different combinations of resScale, matrix, and cullRect.", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "affectedTypes": ["SKPaint"], "affectedMethods": ["SKPaint.GetFillPath(SKPath, SKPathBuilder)", "SKPaint.GetFillPath(SKPath, SKPathBuilder, float)", "SKPaint.GetFillPath(SKPath, SKPathBuilder, SKMatrix)", "SKPaint.GetFillPath(SKPath, SKPathBuilder, SKRect)", "SKPaint.GetFillPath(SKPath, SKPathBuilder, SKRect, float)", "SKPaint.GetFillPath(SKPath, SKPathBuilder, SKRect, SKMatrix)"]}, {"name": "SKTypeface.Empty and IsEmpty properties", "changeType": "added", "importance": "minor", "description": "New SKTypeface.Empty static property and IsEmpty instance property allow checking for empty/invalid typefaces. Part of the Android crash fix that improved typeface resolution robustness.", "pr": 3730, "prUrl": "https://github.com/mono/SkiaSharp/pull/3730", "affectedTypes": ["SKTypeface"], "affectedMethods": ["SKTypeface.Empty", "SKTypeface.IsEmpty"]}, {"name": "Fix SIGSEGV crash on Android API 36 during SKTypeface initialization", "changeType": "fixed", "importance": "major", "description": "Fixed a fatal SIGSEGV at SkiaSharp static initialization on Android 16 (API 36). The crash occurred in sk_typeface_ref_default. The fix moves default typeface resolution to the managed layer, using SKFontManager.MatchFamily for robust platform-independent typeface lookup.", "pr": 3730, "prUrl": "https://github.com/mono/SkiaSharp/pull/3730", "issue": 3693, "issueUrl": "https://github.com/mono/SkiaSharp/issues/3693", "affectedTypes": ["SKTypeface", "SKFontManager", "SKFont"], "affectedMethods": ["SKTypeface.CreateDefault", "SKTypeface.FromFamilyName", "SKTypeface.FromFile", "SKTypeface.FromStream", "SKTypeface.FromData"], "slideBullet": "🐛 **Android 16 Crash Fix** — Resolved fatal SIGSEGV during initialization on Android API 36"}, {"name": "Fix SKGLView not rendering after tab switch on Android", "changeType": "fixed", "importance": "minor", "description": "Fixed SKGLView on Android not rendering when it appears a second time in a MAUI TabBar. The first display works correctly, but switching tabs and returning caused a blank view.", "pr": 3076, "prUrl": "https://github.com/mono/SkiaSharp/pull/3076", "affectedTypes": ["SKGLView"], "platforms": ["Android"]}, {"name": "Fix x86 .NET Framework threading test OOM failures", "changeType": "fixed", "importance": "patch", "description": "Fixed out-of-memory failures in threading tests when running on x86 .NET Framework.", "pr": 3674, "prUrl": "https://github.com/mono/SkiaSharp/pull/3674", "issue": 3608, "issueUrl": "https://github.com/mono/SkiaSharp/issues/3608", "platforms": ["Windows"]}, {"name": "Fix SKTypeface.ContainsGlyphs infinite recursion", "changeType": "fixed", "importance": "minor", "description": "Fixed a stack overflow bug in SKTypeface.ContainsGlyphs(ReadOnlySpan<byte>, SKTextEncoding) which was calling itself recursively instead of delegating to the font instance.", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "affectedTypes": ["SKTypeface"], "affectedMethods": ["SKTypeface.ContainsGlyphs"]}, {"name": "Bump Skia to milestone 147 (from m132)", "changeType": "dependency", "importance": "major", "description": "Updated the Skia graphics engine from Chrome milestone 132 to 147 — a 15-milestone jump. This brings rendering quality improvements, new codec features, color accuracy fixes, and performance optimizations. The bump was done in two stages: m132→m133 (PR #3660) and m133→m147 (PR #3702).", "labels": ["skia-upstream"], "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "dependencyName": "skia", "dependencyFrom": "chrome/m132", "dependencyTo": "chrome/m147", "slideBullet": "🚀 **Skia m147 Engine** — 15-milestone Skia upgrade brings rendering, codec, and color improvements across the board"}, {"name": "Update libpng to 1.6.58", "changeType": "dependency", "importance": "patch", "description": "Bumped libpng from 1.6.54 to 1.6.58, bringing bug fixes and security hardening.", "pr": 3718, "prUrl": "https://github.com/mono/SkiaSharp/pull/3718", "dependencyName": "libpng", "dependencyFrom": "1.6.54", "dependencyTo": "1.6.58"}, {"name": "Update libexpat to 2.7.5", "changeType": "dependency", "importance": "patch", "description": "Bumped libexpat from 2.7.3 to 2.7.5, bringing security fixes.", "labels": ["security"], "pr": 3717, "prUrl": "https://github.com/mono/SkiaSharp/pull/3717", "dependencyName": "libexpat", "dependencyFrom": "2.7.3", "dependencyTo": "2.7.5"}, {"name": "Linux Bionic (Android NDK) native assets support", "changeType": "platform", "importance": "major", "description": "Added Linux Bionic native build support for AArch64 and x64 architectures. Enables running SkiaSharp on Android's Bionic libc outside of the traditional Android runtime, supporting Android 10+.", "labels": ["new-platform"], "pr": 3217, "prUrl": "https://github.com/mono/SkiaSharp/pull/3217", "platforms": ["Linux-Bionic"], "slideBullet": "🌐 **Linux Bionic Support** — Run SkiaSharp natively on Android Bionic (AArch64/x64) for non-runtime Android scenarios"}, {"name": "Tizen 64-bit native build support (x64 and arm64)", "changeType": "platform", "importance": "major", "description": "Added 64-bit native builds for Tizen (x86_64 and aarch64) on Tizen 8.0 rootstraps. 32-bit Tizen targets (armel, i586) were removed as those platforms are past EOL.", "labels": ["new-platform"], "pr": 3620, "prUrl": "https://github.com/mono/SkiaSharp/pull/3620", "platforms": ["Tizen"], "slideBullet": "🌐 **Tizen 64-bit** — Native aarch64 and x86_64 builds for Tizen 8.0"}, {"name": "C++ standard upgraded to C++20", "changeType": "platform", "importance": "minor", "description": "All native builds upgraded from C++17 to C++20 to support newer Skia requirements. Apple platforms (iOS, macOS, tvOS) Xcode projects and Tizen project definitions updated accordingly.", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "platforms": ["iOS", "macOS", "tvOS", "Tizen"]}, {"name": "Removed sfntly build flag from all platforms", "changeType": "platform", "importance": "patch", "description": "The skia_use_sfntly=false GN flag was removed from all native build scripts. Sfntly support has been removed from upstream Skia.", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "platforms": ["Android", "iOS", "macOS", "tvOS", "Linux", "Windows", "Tizen", "WASM"]}, {"name": "PolySharp enables C# 13 on legacy target frameworks", "changeType": "platform", "importance": "minor", "description": "Added PolySharp 1.15.0 as a private build-time dependency, enabling use of modern C# 13 language features on older .NET target frameworks without carrying local attribute shims.", "pr": 3642, "prUrl": "https://github.com/mono/SkiaSharp/pull/3642"}, {"name": "Split Tizen native CI into per-architecture jobs", "changeType": "platform", "importance": "patch", "description": "Tizen CI now builds each architecture (x64, arm64) as a separate job for better parallelism and faster CI feedback.", "pr": 3672, "prUrl": "https://github.com/mono/SkiaSharp/pull/3672", "platforms": ["Tizen"]}, {"name": "Linked jsonreader module in Apple and Tizen native builds", "changeType": "platform", "importance": "patch", "description": "Native builds for Apple platforms (iOS, macOS, tvOS) and Tizen now link the jsonreader module required by Skia's Skottie animation support post-m133. Also removed the -all_load linker flag from Apple builds.", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "platforms": ["iOS", "macOS", "tvOS", "Tizen"]}, {"name": "Gradient interpolation in wide-gamut color spaces", "changeType": "upstream", "importance": "minor", "description": "Gradient shaders can now interpolate colors in A98 RGB, ProPhoto RGB, Display P3, and Rec2020 color spaces. Apps using gradients with wide-gamut surfaces will see more accurate color transitions without any code changes.", "labels": ["skia-upstream"], "skiaMilestone": 134, "skiaFeature": "Gradient interpolation in A98 RGB, ProPhoto, P3, Rec2020", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}, {"name": "Rec.709 transfer function corrected to BT.1886", "changeType": "upstream", "importance": "minor", "description": "The kRec709 transfer function now matches the pure gamma 2.4 definition from ITU-R BT.1886. Apps displaying video-sourced content may see subtle color improvements. This is a silent behavior change — no API change required.", "labels": ["skia-upstream"], "skiaMilestone": 142, "skiaFeature": "SkNamedTransferFn::kRec709 corrected to BT.1886", "notes": "Affects transfer characteristics values 1, 6, 11, 14, and 16. (Delivered via Skia submodule bump)", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702"}, {"name": "Rust PNG encoder/decoder promoted to official API", "changeType": "upstream", "importance": "minor", "description": "Skia's Rust-based PNG encoder and decoder are now part of the official public API surface. This brings improved security and performance for PNG operations.", "labels": ["skia-upstream"], "skiaMilestone": 142, "skiaFeature": "SkPngRustEncoder/Decoder promoted to public API", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}, {"name": "HDR metadata support in PNG", "changeType": "upstream", "importance": "minor", "description": "PNG decoding and encoding now support HDR metadata (content light level info and mastering display color volume). Apps working with HDR images in PNG format get proper metadata preservation automatically.", "labels": ["skia-upstream"], "skiaMilestone": 142, "skiaFeature": "HDR metadata in SkPngDecoder/SkPngEncoder", "slideBullet": "🖼️ **HDR PNG Metadata** — Automatic HDR metadata preservation when encoding and decoding PNG images", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}, {"name": "SkPath immutability migration with SkPathBuilder factories", "changeType": "upstream", "importance": "minor", "description": "Skia is migrating SkPath toward immutability, introducing new factory methods (Rect, Oval, Circle, RRect, Polygon, Line) and preferring SkPathBuilder for path construction. SkiaSharp benefits from internal path performance improvements.", "labels": ["skia-upstream"], "skiaMilestone": 143, "skiaFeature": "SK_HIDE_PATH_EDIT_METHODS and SkPath factories", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}, {"name": "Codec memory limit protection (MaxDecodeMemory)", "changeType": "upstream", "importance": "minor", "description": "SkCodec now supports a maximum decode memory limit. If decoding would exceed the limit, it returns an out-of-memory result instead of consuming unbounded memory. Protects against malicious or extremely large images.", "labels": ["skia-upstream"], "skiaMilestone": 145, "skiaFeature": "SkCodec::Options::fMaxDecodeMemory", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}, {"name": "AGTM (Adaptive Global Tone Mapping) interface", "changeType": "upstream", "importance": "minor", "description": "Skia added the skhdr::Agtm interface for SMPTE ST 2094-50 adaptive global tone mapping metadata. Includes parsing, serialization, and tone mapping via SkColorFilter. Improves HDR-to-SDR display quality.", "labels": ["skia-upstream"], "skiaMilestone": 145, "skiaFeature": "skhdr::Agtm interface for SMPTE ST 2094-50", "slideBullet": "⚡ **Adaptive Tone Mapping** — SMPTE ST 2094-50 AGTM support for superior HDR-to-SDR display quality", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}, {"name": "Path utilities take SkPathBuilder for zero-copy output", "changeType": "upstream", "importance": "minor", "description": "Public path utilities (SkContourMeasure::getSegment, SkPathEffect::filterPath, SkPathMeasure::getSegment, FillPathWithPaint) now accept SkPathBuilder instead of SkPath, avoiding extra copies of path data.", "labels": ["skia-upstream", "performance"], "skiaMilestone": 146, "skiaFeature": "Path utilities accept SkPathBuilder", "pr": 3702, "prUrl": "https://github.com/mono/SkiaSharp/pull/3702", "notes": " (Delivered via Skia submodule bump)"}], "slides": "## What's New in SkiaSharp 4.0\n\n🚀 **SkiaSharp 4.0** — Major version bump signaling the Skia m147 engine upgrade with new APIs and platform support\n\n🎨 **Variable Font Support** — Query and adjust font weight, width, and custom axes at runtime with full OpenType variation control\n\n🎨 **Color Font Palettes** — Switch between color palettes in emoji and icon fonts, with custom override support\n\n🖼️ **Surface Sampling Control** — Specify filtering quality when drawing surfaces with SKSamplingOptions\n\n🎬 **CICP Color Spaces** — Create color spaces from video standards (H.264/H.265/AV1) with a single call\n\n🌈 **Extended Color Types** — New Bgra10101010XR, RgbF16F16F16x, and R16Unorm for HDR and wide-gamut content\n\n🐛 **Android 16 Crash Fix** — Resolved fatal SIGSEGV during initialization on Android API 36\n\n🌐 **Linux Bionic Support** — Run SkiaSharp natively on Android Bionic (AArch64/x64) for non-runtime Android scenarios\n\n🌐 **Tizen 64-bit** — Native aarch64 and x86_64 builds for Tizen 8.0\n\n---\n\n### 🚀 Engine Improvements (Skia m132 → m147)\n\n🚀 **Skia m147 Engine** — 15-milestone Skia upgrade brings rendering, codec, and color improvements across the board\n\n🖼️ **HDR PNG Metadata** — Automatic HDR metadata preservation when encoding and decoding PNG images\n\n⚡ **Adaptive Tone Mapping** — SMPTE ST 2094-50 AGTM support for superior HDR-to-SDR display quality\n\n🎨 Wide-gamut gradient interpolation in A98 RGB, ProPhoto, P3, and Rec2020\n\n🎬 Corrected Rec.709/BT.1886 transfer function for video-sourced content\n\n🔒 Codec memory limit protection against malicious images\n\n⚡ SkPath immutability migration for better path performance", "changelog": "# Changelog: v3.119.4-preview.1.1 → origin/main\n\n## ⚠️ Breaking Changes\n\n- **Major version bump from 3.x to 4.x** ([#3640](https://github.com/mono/SkiaSharp/pull/3640))\n - All NuGet packages, assembly versions, and file versions updated\n - Update PackageReference from `3.x` to `4.x`\n - No public API signatures removed — version change signals the large Skia engine upgrade\n\n## ✨ New APIs\n\n- **Variable font support** ([#3703](https://github.com/mono/SkiaSharp/pull/3703))\n - `SKTypeface.VariationDesignParameters` / `VariationDesignPosition` — query font axes and positions\n - `SKTypeface.Clone(ReadOnlySpan<SKFontVariationPositionCoordinate>)` — clone with custom design coordinates\n - `SKFontVariationAxis`, `SKFontVariationPositionCoordinate`, `SKFontArguments`, `SKFourByteTag` types\n - `HarfBuzzSharp.Face.VariationAxisInfos`, `TryFindVariationAxis`, `NamedInstanceCount`\n - `HarfBuzzSharp.Font.SetVariations`, `SetVariationCoordsDesign`, `SetVariationCoordsNormalized`\n\n- **Color font palette support** ([#3742](https://github.com/mono/SkiaSharp/pull/3742))\n - `SKTypeface.Clone(int paletteIndex)` / `Clone(SKFontArguments)` — clone with palette selection\n - `SKFontPaletteOverride` type for custom color overrides\n - `HarfBuzzSharp.Face.HasPalettes`, `PaletteCount`, `GetPaletteColors`, `GetPaletteFlags`\n - `HarfBuzzSharp.Face.HasColorLayers`, `HasColorPng`, `HasColorSvg`\n\n- **SKSamplingOptions for surface drawing** ([#3491](https://github.com/mono/SkiaSharp/pull/3491), fixes [#3344](https://github.com/mono/SkiaSharp/issues/3344))\n - `SKSurface.Draw(SKCanvas, float, float, SKSamplingOptions, SKPaint)`\n - `SKCanvas.DrawSurface(SKSurface, float, float, SKSamplingOptions, SKPaint)`\n\n- **CICP color space creation** ([#3660](https://github.com/mono/SkiaSharp/pull/3660))\n - `SKColorSpace.CreateCicp(SKColorspacePrimariesCicp, SKColorspaceTransferFnCicp)`\n\n- **New SKColorType values** ([#3702](https://github.com/mono/SkiaSharp/pull/3702))\n - `SKColorType.Bgra10101010XR` — extended range 10-bit\n - `SKColorType.RgbF16F16F16x` — opaque half-float RGB\n - `SKColorType.R16Unorm` — 16-bit unsigned normalized single-channel\n\n- **SKPaint.GetFillPath with SKPathBuilder** ([#3702](https://github.com/mono/SkiaSharp/pull/3702))\n - Six new overloads accepting SKPathBuilder as destination\n\n- **SKTypeface.Empty and IsEmpty** ([#3730](https://github.com/mono/SkiaSharp/pull/3730))\n - Static `Empty` property and `IsEmpty` instance property\n\n## 🐛 Bug Fixes\n\n- **Fix SIGSEGV crash on Android API 36** ([#3730](https://github.com/mono/SkiaSharp/pull/3730), fixes [#3693](https://github.com/mono/SkiaSharp/issues/3693))\n - Fatal crash during SKTypeface static initialization on Android 16\n - Default typeface resolution moved to managed layer using SKFontManager\n\n- **Fix SKGLView not rendering after tab switch on Android** ([#3076](https://github.com/mono/SkiaSharp/pull/3076))\n - SKGLView in MAUI TabBar would not render when revisited\n\n- **Fix SKTypeface.ContainsGlyphs infinite recursion** ([#3702](https://github.com/mono/SkiaSharp/pull/3702))\n - The `ContainsGlyphs(ReadOnlySpan<byte>, SKTextEncoding)` overload was calling itself instead of delegating to the font\n\n- **Fix x86 .NET Framework threading test OOM failures** ([#3674](https://github.com/mono/SkiaSharp/pull/3674))\n\n## 🚀 Engine Improvements (Skia m132 → m147)\n\nThe Skia graphics engine was upgraded 15 milestones (m132 → m147). These improvements are automatic — no code changes needed:\n\n- **m134**: Gradient interpolation in A98 RGB, ProPhoto RGB, Display P3, and Rec2020 color spaces\n- **m142**: Rec.709 transfer function corrected to match BT.1886 (pure gamma 2.4)\n- **m142**: Rust PNG encoder/decoder promoted to official public API\n- **m142**: HDR metadata support in PNG (content light level, mastering display color volume)\n- **m142**: Removed fICCProfile/fICCProfileDescription from encoder options\n- **m143**: SkPath migrating to immutable with new SkPathBuilder factories\n- **m144**: kR16_unorm_SkColorType added\n- **m145**: skhdr::Agtm interface for SMPTE ST 2094-50 adaptive global tone mapping\n- **m145**: SkCodec MaxDecodeMemory protection against malicious images\n- **m146**: SkGradientShader.h → SkGradient.h header migration\n- **m146**: Path utilities (getSegment, filterPath, FillPathWithPaint) accept SkPathBuilder for zero-copy output\n\n## 📦 Dependencies\n\n- **Skia**: chrome/m132 → chrome/m147 ([#3660](https://github.com/mono/SkiaSharp/pull/3660), [#3702](https://github.com/mono/SkiaSharp/pull/3702))\n- **libpng**: 1.6.54 → 1.6.58 ([#3718](https://github.com/mono/SkiaSharp/pull/3718))\n- **libexpat**: 2.7.3 → 2.7.5 ([#3717](https://github.com/mono/SkiaSharp/pull/3717))\n\n## 🌐 Platform Changes\n\n- **Linux Bionic (Android NDK)** — New AArch64 and x64 native assets ([#3217](https://github.com/mono/SkiaSharp/pull/3217))\n- **Tizen 64-bit** — Native x86_64 and aarch64 builds for Tizen 8.0 ([#3620](https://github.com/mono/SkiaSharp/pull/3620))\n- **Tizen 32-bit removed** — armel and i586 targets dropped (EOL platforms)\n- **C++20** — All native builds upgraded from C++17 to C++20 ([#3702](https://github.com/mono/SkiaSharp/pull/3702))\n- **PolySharp** — C# 13 language features on legacy TFMs ([#3642](https://github.com/mono/SkiaSharp/pull/3642))\n- **sfntly removed** — Build flag dropped from all platforms (removed upstream)\n- **jsonreader linked** — Required for Skottie animation support post-m133"};</script>
<div class="shell mx-auto px-3 py-3">
<div class="d-flex align-items-center justify-content-between mb-2">
<div><strong style="font-size:1.15rem">SkiaSharp Release Notes</strong>
<span class="text-muted" style="font-size:.8rem;margin-left:8px" id="subtitle"></span></div>
</div>
<div class="summary-bar d-flex gap-4 align-items-center flex-wrap" id="summary-bar"></div>
<div style="border-bottom:1px solid var(--border);margin-bottom:8px">
<button class="tab-btn active" onclick="switchTab('findings')">Findings <span id="findings-count" class="count-badge"></span></button>
<button class="tab-btn" onclick="switchTab('slides')">Slides</button>
<button class="tab-btn" onclick="switchTab('changelog')">Changelog</button>
</div>
<!-- Findings Tab -->
<div class="tab-pane active" id="tab-findings">
<div class="filter-bar" id="findings-filters">
<div class="filter-row">
<span class="filter-label">Search:</span>
<input type="text" class="search-box" style="width:300px" id="search" placeholder="Search findings...">
</div>
<div class="filter-row" id="fr-importance"></div>
<div class="filter-row" id="fr-changetype"></div>
<div class="filter-row" id="fr-label"></div>
<div class="filter-row">
<span class="filter-label">Group by:</span>
<select class="ctrl-select" id="group-by" onchange="rebuildList()">
<option value="none">None</option>
<option value="changeType" selected>Change Type</option>
<option value="importance">Importance</option>
<option value="label">Label</option>
</select>
<button class="dir-btn" id="group-dir" onclick="toggleDir('group-dir');rebuildList()" title="Toggle group order">▼</button>
<span style="width:16px"></span>
<span class="filter-label" style="width:55px">Sort by:</span>
<select class="ctrl-select" id="sort-by" onchange="rebuildList()">
<option value="importance">Importance</option>
<option value="changeType">Change Type</option>
<option value="name">Name</option>
</select>
<button class="dir-btn" id="sort-dir" onclick="toggleDir('sort-dir');rebuildList()" title="Toggle sort order">▼</button>
</div>
</div>
<div class="text-muted d-flex justify-content-between" style="font-size:.72rem;padding:0 12px 4px">
<span id="filter-count"></span>
<button class="reset-btn" onclick="resetFilters()">Reset filters</button>
</div>
<div id="list"></div>
</div>
<!-- Slides Tab -->
<div class="tab-pane" id="tab-slides">
<div class="md-content" id="slides-content"></div>
</div>
<!-- Changelog Tab -->
<div class="tab-pane" id="tab-changelog">
<div class="md-content" id="changelog-content"></div>
</div>
<div class="meta-footer" id="meta-footer"></div>
</div>
<script>
const CTL={added:'✨ Added',changed:'🔄 Changed',fixed:'🐛 Fixed',deprecated:'⚠️ Deprecated',removed:'❌ Removed',dependency:'📦 Dependency',platform:'🌐 Platform',upstream:'🚀 Engine'};
const IL={breaking:'⚠️ Breaking',major:'Major',minor:'Minor',patch:'Patch'};
const IO={breaking:0,major:1,minor:2,patch:3};
const CTO={removed:0,added:1,changed:2,fixed:3,upstream:4,deprecated:5,dependency:6,platform:7};
let fF={importance:new Set(),changeType:new Set(),label:new Set()};
let sT='';
function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML;}
function toggleDir(id){const b=document.getElementById(id);b.textContent=b.textContent==='▼'?'▲':'▼';}
function getDir(id){return document.getElementById(id).textContent==='▲'?-1:1;}
function switchTab(t){document.querySelectorAll('.tab-pane').forEach(p=>p.classList.remove('active'));document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.getElementById('tab-'+t).classList.add('active');document.querySelector(`[onclick="switchTab('${t}')"]`).classList.add('active');}
function tF(type,val){fF[type].has(val)?fF[type].delete(val):fF[type].add(val);applyAndRebuild();}
function resetFilters(){Object.values(fF).forEach(s=>s.clear());sT='';document.getElementById('search').value='';applyAndRebuild();}
function passesFilter(f){if(fF.importance.size&&!fF.importance.has(f.importance))return false;if(fF.changeType.size&&!fF.changeType.has(f.changeType))return false;if(fF.label.size&&!(f.labels||[]).some(l=>fF.label.has(l)))return false;if(sT&&!JSON.stringify(f).toLowerCase().includes(sT))return false;return true;}
function sortFindings(arr){const s=document.getElementById('sort-by').value;const dir=getDir('sort-dir');return[...arr].sort((a,b)=>{let r=0;switch(s){case'importance':r=(IO[a.importance]??4)-(IO[b.importance]??4);break;case'changeType':r=(CTO[a.changeType]??9)-(CTO[b.changeType]??9);break;case'name':r=(a.name||'').localeCompare(b.name||'');break;}return r*dir;});}
function groupKey(f){const g=document.getElementById('group-by').value;switch(g){case'changeType':return f.changeType;case'importance':return f.importance;case'label':return(f.labels||[])[0]||'other';default:return null;}}
function groupLabel(key,by){switch(by){case'changeType':return CTL[key]||key;case'importance':return IL[key]||key;default:return key;}}
function groupOrder(key,by){switch(by){case'changeType':return CTO[key]??9;case'importance':return IO[key]??9;default:return 0;}}
function applyAndRebuild(){rebuildList();document.querySelectorAll('#tab-findings .pill[data-filter]').forEach(p=>{const[t,v]=p.dataset.filter.split(':');p.classList.toggle('dim',fF[t]?.size>0&&!fF[t].has(v));});}
function rebuildList(){const filtered=DATA.findings.filter(passesFilter);const sorted=sortFindings(filtered);document.getElementById('filter-count').textContent=`Showing ${filtered.length} of ${DATA.findings.length}`;const by=document.getElementById('group-by').value;const el=document.getElementById('list');if(by==='none'){el.innerHTML=sorted.map(renderRow).join('');return;}
const groups={};sorted.forEach(f=>{const k=groupKey(f);if(!groups[k])groups[k]=[];groups[k].push(f);});const gOrder=Object.keys(groups);const gDir=getDir('group-dir');gOrder.sort((a,b)=>(groupOrder(a,by)-groupOrder(b,by))*gDir||(a.localeCompare(b)*gDir));
el.innerHTML=gOrder.map(k=>`<div class="group-header">${groupLabel(k,by)} <span class="count-badge">${groups[k].length}</span></div>`+groups[k].map(renderRow).join('')).join('');}
function renderRow(f){const ct=`<span class="pill ct-${f.changeType}" data-filter="changeType:${f.changeType}" onclick="event.stopPropagation();tF('changeType','${f.changeType}')">${CTL[f.changeType]||f.changeType}</span>`;const imp=`<span class="pill i-${f.importance}" data-filter="importance:${f.importance}" onclick="event.stopPropagation();tF('importance','${f.importance}')">${f.importance}</span>`;const labels=(f.labels||[]).map(l=>`<span class="pill l-pill" data-filter="label:${l}" onclick="event.stopPropagation();tF('label','${l}')">${l}</span>`).join(' ');const prLink=f.pr?`<a class="pr-link" href="${f.prUrl||'https://github.com/mono/SkiaSharp/pull/'+f.pr}" target="_blank">#${f.pr}</a>`:'';const commitLink=f.commit&&!f.pr?`<a class="pr-link" href="${f.commitUrl||'https://github.com/mono/SkiaSharp/commit/'+f.commit}" target="_blank">${f.commit.slice(0,7)}</a>`:'';
let det='<div class="detail-grid">';if(f.affectedTypes?.length)det+=`<div class="detail-label">Types</div><div class="detail-value">${f.affectedTypes.map(t=>'<code>'+esc(t)+'</code>').join(', ')}</div>`;if(f.affectedMethods?.length)det+=`<div class="detail-label">Methods</div><div class="detail-value">${f.affectedMethods.map(m=>'<code>'+esc(m)+'</code>').join(', ')}</div>`;if(f.cApiFunction)det+=`<div class="detail-label">C API</div><div class="detail-value"><code>${esc(f.cApiFunction)}</code></div>`;if(f.csharpMethod)det+=`<div class="detail-label">C#</div><div class="detail-value"><code>${esc(f.csharpMethod)}</code>${f.csharpFile?' ('+esc(f.csharpFile)+')':''}</div>`;if(f.skiaFeature)det+=`<div class="detail-label">Skia</div><div class="detail-value">${esc(f.skiaFeature)}${f.skiaMilestone?' (m'+f.skiaMilestone+')':''}</div>`;if(f.dependencyName)det+=`<div class="detail-label">Dependency</div><div class="detail-value"><code>${esc(f.dependencyName)}</code>: ${esc(f.dependencyFrom||'?')} → ${esc(f.dependencyTo||'?')}</div>`;if(f.platforms?.length)det+=`<div class="detail-label">Platforms</div><div class="detail-value">${f.platforms.join(', ')}</div>`;if(f.author)det+=`<div class="detail-label">Author</div><div class="detail-value">${esc(f.author)}</div>`;if(f.migrationGuide)det+=`<div class="detail-label">Migration</div><div class="detail-value" style="white-space:pre-wrap">${esc(f.migrationGuide)}</div>`;if(f.notes)det+=`<div class="detail-label">Notes</div><div class="detail-value">${esc(f.notes)}</div>`;det+='</div>';
return`<div class="row-item" onclick="this.classList.toggle('expanded')"><div class="d-flex align-items-start"><span class="row-chevron">▶</span><div style="flex:1"><div class="row-name">${esc(f.name)} ${prLink}${commitLink}</div><div class="row-pills">${ct} ${imp} ${labels}</div><div class="row-desc">${esc(f.description)}</div><div class="row-detail">${det}</div></div></div></div>`;}
function renderSummary(s){const byCt=s.byChangeType||{};const byImp=s.byImportance||{};document.getElementById('summary-bar').innerHTML=[['Total',s.totalFindings||0,'#1f2328'],['Added',byCt.added||0,'#166534'],['Fixed',byCt.fixed||0,'#1e40af'],['Engine',byCt.upstream||0,'#9d174d'],['Deps',byCt.dependency||0,'#5b21b6'],['Platform',byCt.platform||0,'#115e59'],['Breaking',byImp.breaking||0,'#991b1b']].map(([l,n,c])=>`<div class="text-center"><div class="summary-num" style="color:${c}">${n}</div><div class="summary-label">${l}</div></div>`).join('');}
function addPillRow(rowId,label,type,vals,pre){const row=document.getElementById(rowId);row.innerHTML=`<span class="filter-label">${label}:</span>`+vals.map(v=>{const cls=pre?`${pre}${v}`:'l-pill';const lb=type==='changeType'?(CTL[v]||v):type==='importance'?(IL[v]||v):v;return`<span class="pill ${cls}" data-filter="${type}:${v}" onclick="tF('${type}','${v}')">${lb}</span>`;}).join(' ');}
function renderMarkdown(md){if(!md)return'<p style="color:var(--muted)">No content generated.</p>';return md.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/^### (.+)$/gm,'<h3>$1</h3>').replace(/^## (.+)$/gm,'<h2>$1</h2>').replace(/^# (.+)$/gm,'<h1>$1</h1>').replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/`([^`]+)`/g,'<code>$1</code>').replace(/^- (.+)$/gm,'<li>$1</li>').replace(/(<li>.*<\/li>\n?)+/g,'<ul>$&</ul>').replace(/\n\n/g,'<br><br>').replace(/\n(?!<)/g,'\n');}
document.addEventListener('DOMContentLoaded',()=>{if(typeof DATA==='undefined'){document.body.innerHTML='<p style="padding:2rem;color:red">No DATA found.</p>';return;}const m=DATA.meta;document.getElementById('subtitle').textContent=`${m.refFrom}..${m.refTo} · ${m.date} · ${m.commitCount} commits`;renderSummary(DATA.summary);
addPillRow('fr-importance','Importance','importance',['breaking','major','minor','patch'],'i-');
addPillRow('fr-changetype','Type','changeType',['added','changed','fixed','deprecated','removed','dependency','platform','upstream'],'ct-');
const lbls=[...new Set(DATA.findings.flatMap(f=>f.labels||[]))].sort();if(lbls.length)addPillRow('fr-label','Label','label',lbls,'');
document.getElementById('findings-count').textContent=DATA.findings.length;
document.getElementById('slides-content').innerHTML=renderMarkdown(DATA.slides);
document.getElementById('changelog-content').innerHTML=renderMarkdown(DATA.changelog);
document.getElementById('meta-footer').innerHTML=`${m.repo} · ${m.refFrom}..${m.refTo} · ${m.date} · ${m.commitCount} commits`+(m.prCount?` · ${m.prCount} PRs`:'')+(m.skiaMilestoneBumped?` · Skia m${m.skiaMilestoneFrom}→m${m.skiaMilestoneTo}`:'');
document.getElementById('search').addEventListener('input',e=>{sT=e.target.value.toLowerCase();applyAndRebuild();});rebuildList();});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment