Repository: kyamagu/skia-python (language: python)

Issue:
Missing `getBlendMode` on `skia.Paint` objects
There is a `setBlendMode` method on `Paint` objects, but no corresponding `getBlendMode`. Also, the `BlendMode` parameter to `skia.Paint` is not documented

**To Reproduce**
```
>>> skia.__version__
'119.0b4'
>>> skia.Paint().getBlendMode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'skia.Paint' object has no attribute 'getBlendMode'. Did you mean: 'setBlendMode'?
```
To see the documentation issues, search "blendmode" on this page: https://kyamagu.github.io/skia-python/reference/skia.Paint.html
Note that the `BlendMode` argument to the `skia.Paint` constructor works, but is not documented.

**Expected behavior**
Like in previous releases of `skia-python`, the `getBlendMode` function should exist and be documented.

**Desktop:**
 - OS: macOS 14.3.1
 - Python: 3.12
 - skia-python version: 119.0b4



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fe6122f5..d472d8bb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,10 +13,10 @@ jobs:
     name: Build skia bundled 3rd-party on linux/aarch64
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions/cache@v3
+      - uses: actions/cache@v4
         id: cache-skia
         with:
           path: |
@@ -24,9 +24,9 @@ jobs:
             skia
           key: linux-aarch64-skia-${{ github.sha }}-3rd-party
       - name: Pre-fetch skia deps
-        run: git config --global core.compression 0 && cd skia && patch -p1 -i ../patch/skia-m121-minimize-download.patch && python tools/git-sync-deps && patch -p1 -R -i ../patch/skia-m121-minimize-download.patch
+        run: git config --global core.compression 0 && cd skia && patch -p1 -i ../patch/skia-m124-minimize-download.patch && python tools/git-sync-deps && patch -p1 -R -i ../patch/skia-m124-minimize-download.patch
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
+        uses: docker/setup-qemu-action@v3
       - name: Build skia 3rd-Party
         # Taken from https://github.com/pypa/cibuildwheel/blob/v2.16.2/cibuildwheel/resources/pinned_docker_images.cfg
         uses: docker://quay.io/pypa/manylinux2014_aarch64:2023-10-03-72cdc42
@@ -41,24 +41,24 @@ jobs:
     needs: prebuild_linux_aarch64_3rd_party
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions/cache@v3
+      - uses: actions/cache@v4
         id: cache-skia
         with:
           path: |
             gn
             skia
           key: linux-aarch64-skia-${{ github.sha }}
-      - uses: actions/cache/restore@v3
+      - uses: actions/cache/restore@v4
         with:
           path: |
             gn
             skia
           key: linux-aarch64-skia-${{ github.sha }}-3rd-party
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
+        uses: docker/setup-qemu-action@v3
       - name: Build Skia Proper
         uses: docker://quay.io/pypa/manylinux2014_aarch64:2023-10-03-72cdc42
         with:
@@ -100,11 +100,11 @@ jobs:
             cp: cp312
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           submodules: true
 
-      - uses: actions/cache/restore@v3
+      - uses: actions/cache/restore@v4
         if: runner.os == 'Linux' && matrix.arch == 'aarch64'
         with:
           path: |
@@ -114,12 +114,12 @@ jobs:
 
       - name: Set up QEMU
         if: runner.os == 'Linux' && matrix.arch == 'aarch64'
-        uses: docker/setup-qemu-action@v2
+        uses: docker/setup-qemu-action@v3
         with:
           platforms: ${{ matrix.arch }}
 
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.16.2
+        uses: pypa/cibuildwheel@v2.16.5
         env:
           CIBW_BUILD: "${{ matrix.cp }}-*"
           CIBW_SKIP: "*musllinux*"
@@ -144,7 +144,7 @@ jobs:
     needs: [build_wheels]
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - uses: actions/setup-python@v4
         name: Install Python
diff --git a/README.md b/README.md
index 8a0e6ae1..7f240309 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,8 @@ https://kyamagu.github.io/skia-python
 
 - For information about changes after `m116`, and tips on migration from `m87`: [README.m116](relnotes/README.m116.md),
   [README.m117](relnotes/README.m117.md), [README.m118](relnotes/README.m118.md), [README.m119](relnotes/README.m119.md),
-  [README.m120](relnotes/README.m120.md), [README.m121](relnotes/README.m121.md).
+  [README.m120](relnotes/README.m120.md), [README.m121](relnotes/README.m121.md), [README.m122](relnotes/README.m122.md),
+  [README.m123](relnotes/README.m123.md), [README.m124](relnotes/README.m124.md).
 
 ## Contributing
 
diff --git a/patch/skia-m122-colrv1-freetype.diff b/patch/skia-m122-colrv1-freetype.diff
new file mode 100644
index 00000000..a6232503
--- /dev/null
+++ b/patch/skia-m122-colrv1-freetype.diff
@@ -0,0 +1,119 @@
+diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
+index 7e096a6..46fe7a0 100644
+--- a/src/ports/SkFontHost_FreeType.cpp
++++ b/src/ports/SkFontHost_FreeType.cpp
+@@ -33,7 +33,6 @@
+ #include "src/core/SkMask.h"
+ #include "src/core/SkMaskGamma.h"
+ #include "src/core/SkScalerContext.h"
+-#include "src/ports/SkFontHost_FreeType_common.h"
+ #include "src/ports/SkTypeface_FreeType.h"
+ #include "src/sfnt/SkOTUtils.h"
+ #include "src/sfnt/SkSFNTHeader.h"
+@@ -52,6 +51,7 @@
+ #ifdef FT_COLOR_H  // 2.10.0
+ #   include <freetype/ftcolor.h>
+ #endif
++#include "src/ports/SkFontHost_FreeType_common.h"
+ #include <freetype/freetype.h>
+ #include <freetype/ftlcdfil.h>
+ #include <freetype/ftmodapi.h>
+diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
+index 14b2adb..f041583 100644
+--- a/src/ports/SkFontHost_FreeType_common.cpp
++++ b/src/ports/SkFontHost_FreeType_common.cpp
+@@ -6,7 +6,6 @@
+  * found in the LICENSE file.
+  */
+ 
+-#include "src/ports/SkFontHost_FreeType_common.h"
+ 
+ #include "include/core/SkBitmap.h"
+ #include "include/core/SkCanvas.h"
+@@ -32,6 +31,7 @@
+ #ifdef FT_COLOR_H
+ #   include <freetype/ftcolor.h>
+ #endif
++#include "src/ports/SkFontHost_FreeType_common.h"
+ #include <freetype/ftimage.h>
+ #include <freetype/ftoutln.h>
+ #include <freetype/ftsizes.h>
+@@ -1574,6 +1574,41 @@ bool SkScalerContext_FreeType_Base::drawCOLRv1Glyph(FT_Face face,
+     SkASSERTF(haveLayers, "Could not get COLRv1 layers from '%s'.", face->family_name);
+     return haveLayers;
+ }
++/*
++ * This content is mostly just
++ *       SkTypeface_FreeType::FaceRec::setupPalette()
++ +     + SkScalerContext_FreeType_Base::drawCOLRv1Glyph()
++ +*/
++bool SkScalerContext_FreeType_Base::skia_colrv1_start_glyph(SkCanvas* canvas,
++                                    FT_Face face,
++                                    uint16_t glyphId,
++                                    FT_UShort palette_index,
++                                    FT_Color_Root_Transform rootTransform
++                                    ) {
++    uint32_t fForegroundColor{SK_ColorBLACK};
++    FT_Palette_Data paletteData;
++    FT_Palette_Data_Get(face, &paletteData);
++
++    FT_Color* ftPalette = nullptr;
++    FT_Palette_Select(face, palette_index, &ftPalette);
++    std::unique_ptr<SkColor[]> ptr_palette(new SkColor[paletteData.num_palette_entries]);
++    for (int i = 0; i < paletteData.num_palette_entries; ++i) {
++      ptr_palette[i] = SkColorSetARGB(ftPalette[i].alpha,
++                                  ftPalette[i].red,
++                                  ftPalette[i].green,
++                                  ftPalette[i].blue);
++    }
++    SkSpan<SkColor> palette(ptr_palette.get(), paletteData.num_palette_entries);
++
++    VisitedSet activePaints;
++    bool haveLayers =  colrv1_start_glyph(canvas, palette,
++                                          fForegroundColor, // FT_Palette_Get_Foreground_Color?
++                                          face, glyphId,
++                                          FT_COLOR_INCLUDE_ROOT_TRANSFORM,
++                                          &activePaints);
++    SkASSERTF(haveLayers, "Could not get COLRv1 layers from '%s'.", face->family_name);
++    return haveLayers;
++}
+ #endif  // TT_SUPPORT_COLRV1
+ 
+ #ifdef FT_COLOR_H
+diff --git a/src/ports/SkFontHost_FreeType_common.h b/src/ports/SkFontHost_FreeType_common.h
+index d649784..1100b0e 100644
+--- a/src/ports/SkFontHost_FreeType_common.h
++++ b/src/ports/SkFontHost_FreeType_common.h
+@@ -19,6 +19,7 @@
+ typedef struct FT_FaceRec_* FT_Face;
+ typedef struct FT_StreamRec_* FT_Stream;
+ typedef signed long FT_Pos;
++typedef unsigned short  FT_UShort; /* freetype/fttypes.h */
+ 
+ 
+ #ifdef SK_DEBUG
+@@ -31,7 +32,15 @@ const char* SkTraceFtrGetError(int);
+ #endif
+ 
+ 
+-class SkScalerContext_FreeType_Base : public SkScalerContext {
++class SK_SPI SkScalerContext_FreeType_Base : public SkScalerContext {
++public:
++    static bool computeColrV1GlyphBoundingBox(FT_Face, SkGlyphID, SkRect* bounds);
++    static bool skia_colrv1_start_glyph(SkCanvas* canvas,
++                                        FT_Face face,
++                                        uint16_t glyphId,
++                                        FT_UShort palette_index,
++                                        FT_Color_Root_Transform rootTransform
++                                        );
+ protected:
+     // See http://freetype.sourceforge.net/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
+     // This value was chosen by eyeballing the result in Firefox and trying to match it.
+@@ -57,7 +66,6 @@ protected:
+      *  configure size, matrix and load glyphs as needed after using this function to restore the
+      *  state of FT_Face.
+      */
+-    static bool computeColrV1GlyphBoundingBox(FT_Face, SkGlyphID, SkRect* bounds);
+ 
+     struct ScalerContextBits {
+         static const constexpr uint32_t COLRv0 = 1;
diff --git a/patch/skia-m122-minimize-download.patch b/patch/skia-m122-minimize-download.patch
new file mode 100644
index 00000000..9b207a33
--- /dev/null
+++ b/patch/skia-m122-minimize-download.patch
@@ -0,0 +1,70 @@
+diff --git a/DEPS b/DEPS
+index bd7ed9d..8180821 100644
+--- a/DEPS
++++ b/DEPS
+@@ -23,53 +23,18 @@ vars = {
+ #     ./tools/git-sync-deps
+ deps = {
+   "buildtools"                                   : "https://chromium.googlesource.com/chromium/src/buildtools.git@b138e6ce86ae843c42a1a08f37903207bebcca75",
+-  "third_party/externals/angle2"                 : "https://chromium.googlesource.com/angle/angle.git@c6fbf93decbac74cd14c6ca3d600d4ed91d1179d",
+-  "third_party/externals/brotli"                 : "https://skia.googlesource.com/external/github.com/google/brotli.git@6d03dfbedda1615c4cba1211f8d81735575209c8",
+-  "third_party/externals/d3d12allocator"         : "https://skia.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator.git@169895d529dfce00390a20e69c2f516066fe7a3b",
+-  # Dawn requires jinja2 and markupsafe for the code generator, tint for SPIRV compilation, and abseil for string formatting.
+-  # When the Dawn revision is updated these should be updated from the Dawn DEPS as well.
+-  "third_party/externals/dawn"                   : "https://dawn.googlesource.com/dawn.git@5b45794c2c24c3fa40dc480af92c5284a95423ef",
+-  "third_party/externals/jinja2"                 : "https://chromium.googlesource.com/chromium/src/third_party/jinja2@e2d024354e11cc6b041b0cff032d73f0c7e43a07",
+-  "third_party/externals/markupsafe"             : "https://chromium.googlesource.com/chromium/src/third_party/markupsafe@0bad08bb207bbfc1d6f3bbc82b9242b0c50e5794",
+-  "third_party/externals/abseil-cpp"             : "https://skia.googlesource.com/external/github.com/abseil/abseil-cpp.git@334aca32051ef6ede2711487acf45d959e9bdffc",
+   "third_party/externals/dng_sdk"                : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
+-  "third_party/externals/egl-registry"           : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@b055c9b483e70ecd57b3cf7204db21f5a06f9ffe",
+-  "third_party/externals/emsdk"                  : "https://skia.googlesource.com/external/github.com/emscripten-core/emsdk.git@a896e3d066448b3530dbcaa48869fafefd738f57",
+   "third_party/externals/expat"                  : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@441f98d02deafd9b090aea568282b28f66a50e36",
+   "third_party/externals/freetype"               : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@45903920b984540bb629bc89f4c010159c23a89a",
+   "third_party/externals/harfbuzz"               : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@4cfc6d8e173e800df086d7be078da2e8c5cfca19",
+-  "third_party/externals/highway"                : "https://chromium.googlesource.com/external/github.com/google/highway.git@424360251cdcfc314cfc528f53c872ecd63af0f0",
+   "third_party/externals/icu"                    : "https://chromium.googlesource.com/chromium/deps/icu.git@a0718d4f121727e30b8d52c7a189ebf5ab52421f",
+-  "third_party/externals/icu4x"                  : "https://chromium.googlesource.com/external/github.com/unicode-org/icu4x.git@4f81635489681ecf7707623177123cb78d6a66a0",
+-  "third_party/externals/imgui"                  : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@55d35d8387c15bf0cfd71861df67af8cfbda7456",
+-  "third_party/externals/libavif"                : "https://skia.googlesource.com/external/github.com/AOMediaCodec/libavif.git@55aab4ac0607ab651055d354d64c4615cf3d8000",
+-  "third_party/externals/libgav1"                : "https://chromium.googlesource.com/codecs/libgav1.git@5cf722e659014ebaf2f573a6dd935116d36eadf1",
+-  "third_party/externals/libgrapheme"            : "https://skia.googlesource.com/external/github.com/FRIGN/libgrapheme/@c0cab63c5300fa12284194fbef57aa2ed62a94c0",
+   "third_party/externals/libjpeg-turbo"          : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@ed683925e4897a84b3bffc5c1414c85b97a129a3",
+-  "third_party/externals/libjxl"                 : "https://chromium.googlesource.com/external/gitlab.com/wg1/jpeg-xl.git@a205468bc5d3a353fb15dae2398a101dff52f2d3",
+   "third_party/externals/libpng"                 : "https://skia.googlesource.com/third_party/libpng.git@386707c6d19b974ca2e3db7f5c61873813c6fe44",
+   "third_party/externals/libwebp"                : "https://chromium.googlesource.com/webm/libwebp.git@2af26267cdfcb63a88e5c74a85927a12d6ca1d76",
+-  "third_party/externals/libyuv"                 : "https://chromium.googlesource.com/libyuv/libyuv.git@d248929c059ff7629a85333699717d7a677d8d96",
+-  "third_party/externals/microhttpd"             : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
+-  "third_party/externals/oboe"                   : "https://chromium.googlesource.com/external/github.com/google/oboe.git@b02a12d1dd821118763debec6b83d00a8a0ee419",
+-  "third_party/externals/opengl-registry"        : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
+-  "third_party/externals/perfetto"               : "https://android.googlesource.com/platform/external/perfetto@93885509be1c9240bc55fa515ceb34811e54a394",
+   "third_party/externals/piex"                   : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
+-  "third_party/externals/sfntly"                 : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b55ff303ea2f9e26702b514cf6a3196a2e3e2974",
+-  "third_party/externals/swiftshader"            : "https://swiftshader.googlesource.com/SwiftShader@328e794f0c8bddc81c834ccc89c9652902f643cb",
+   "third_party/externals/vulkanmemoryallocator"  : "https://chromium.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator@a6bfc237255a6bac1513f7c1ebde6d8aed6b5191",
+-  # vulkan-deps is a meta-repo containing several interdependent Khronos Vulkan repositories.
+-  # When the vulkan-deps revision is updated, those repos (spirv-*, vulkan-*) should be updated as well.
+   "third_party/externals/vulkan-deps"            : "https://chromium.googlesource.com/vulkan-deps@4307b0183c9e16bc920ef0bceaa9e9d630c352c3",
+-  "third_party/externals/spirv-cross"            : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@b82536766d1b81631b126d1ddbe49baf42929bd3",
+-  "third_party/externals/spirv-headers"          : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@7b0309708da5126b89e4ce6f19835f36dc912f2f",
+-  "third_party/externals/spirv-tools"            : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@3e6bdd0f99655b1bc6a54aa73e5bfaaa4252198b",
+-  "third_party/externals/vello"                  : "https://skia.googlesource.com/external/github.com/linebender/vello.git@ee3a076b291d206c361431cc841407adf265c692",
+   "third_party/externals/vulkan-headers"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Headers@217e93c664ec6704ec2d8c36fa116c1a4a1e2d40",
+-  "third_party/externals/vulkan-tools"           : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Tools@7c6d640a5ca3ab73c1f42d22312f672b54babfaf",
+-  "third_party/externals/vulkan-utility-libraries": "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@4cfc176e3242b4dbdfd3f6c5680c5d8f2cb7db45",
+-  "third_party/externals/unicodetools"           : "https://chromium.googlesource.com/external/github.com/unicode-org/unicodetools@66a3fa9dbdca3b67053a483d130564eabc5fe095",
+-  #"third_party/externals/v8"                     : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
+   "third_party/externals/wuffs"                  : "https://skia.googlesource.com/external/github.com/google/wuffs-mirror-release-c.git@e3f919ccfe3ef542cfc983a82146070258fb57f8",
+   "third_party/externals/zlib"                   : "https://chromium.googlesource.com/chromium/src/third_party/zlib@c876c8f87101c5a75f6014b0f832499afeb65b73",
+ 
+diff --git a/bin/activate-emsdk b/bin/activate-emsdk
+index 687ca9f..7167d8d 100755
+--- a/bin/activate-emsdk
++++ b/bin/activate-emsdk
+@@ -17,6 +17,7 @@ EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py')
+ EMSDK_VERSION = '3.1.44'
+ 
+ def main():
++    return
+     if sysconfig.get_platform() in ['linux-aarch64', 'linux-arm64']:
+         # This platform cannot install emsdk at the provided version. See
+         # https://github.com/emscripten-core/emsdk/blob/main/emscripten-releases-tags.json#L5
diff --git a/patch/skia-m123-colrv1-freetype.diff b/patch/skia-m123-colrv1-freetype.diff
new file mode 100644
index 00000000..6d051d3b
--- /dev/null
+++ b/patch/skia-m123-colrv1-freetype.diff
@@ -0,0 +1,109 @@
+diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
+index 0b81565..9163883 100644
+--- a/src/ports/SkFontHost_FreeType.cpp
++++ b/src/ports/SkFontHost_FreeType.cpp
+@@ -33,7 +33,6 @@
+ #include "src/core/SkMask.h"
+ #include "src/core/SkMaskGamma.h"
+ #include "src/core/SkScalerContext.h"
+-#include "src/ports/SkFontHost_FreeType_common.h"
+ #include "src/ports/SkTypeface_FreeType.h"
+ #include "src/sfnt/SkOTUtils.h"
+ #include "src/sfnt/SkSFNTHeader.h"
+@@ -52,6 +51,7 @@
+ #ifdef FT_COLOR_H  // 2.10.0
+ #   include <freetype/ftcolor.h>
+ #endif
++#include "src/ports/SkFontHost_FreeType_common.h"
+ #include <freetype/freetype.h>
+ #include <freetype/ftlcdfil.h>
+ #include <freetype/ftmodapi.h>
+diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
+index 050a127..8b98adc 100644
+--- a/src/ports/SkFontHost_FreeType_common.cpp
++++ b/src/ports/SkFontHost_FreeType_common.cpp
+@@ -6,7 +6,6 @@
+  * found in the LICENSE file.
+  */
+ 
+-#include "src/ports/SkFontHost_FreeType_common.h"
+ 
+ #include "include/core/SkBitmap.h"
+ #include "include/core/SkCanvas.h"
+@@ -32,6 +31,7 @@
+ #ifdef FT_COLOR_H
+ #   include <freetype/ftcolor.h>
+ #endif
++#include "src/ports/SkFontHost_FreeType_common.h"
+ #include <freetype/ftimage.h>
+ #include <freetype/ftoutln.h>
+ #include <freetype/ftsizes.h>
+@@ -1577,6 +1577,41 @@ bool SkScalerContextFTUtils::drawCOLRv1Glyph(FT_Face face, const SkGlyph& glyph,
+     SkASSERTF(haveLayers, "Could not get COLRv1 layers from '%s'.", face->family_name);
+     return haveLayers;
+ }
++/*
++ * This content is mostly just
++ *       SkTypeface_FreeType::FaceRec::setupPalette()
++ +     + SkScalerContext_FreeType_Base::drawCOLRv1Glyph()
++ +*/
++bool SkScalerContextFTUtils::skia_colrv1_start_glyph(SkCanvas* canvas,
++                                    FT_Face face,
++                                    uint16_t glyphId,
++                                    FT_UShort palette_index,
++                                    FT_Color_Root_Transform rootTransform
++                                    ) {
++    uint32_t fForegroundColor{SK_ColorBLACK};
++    FT_Palette_Data paletteData;
++    FT_Palette_Data_Get(face, &paletteData);
++
++    FT_Color* ftPalette = nullptr;
++    FT_Palette_Select(face, palette_index, &ftPalette);
++    std::unique_ptr<SkColor[]> ptr_palette(new SkColor[paletteData.num_palette_entries]);
++    for (int i = 0; i < paletteData.num_palette_entries; ++i) {
++      ptr_palette[i] = SkColorSetARGB(ftPalette[i].alpha,
++                                  ftPalette[i].red,
++                                  ftPalette[i].green,
++                                  ftPalette[i].blue);
++    }
++    SkSpan<SkColor> palette(ptr_palette.get(), paletteData.num_palette_entries);
++
++    VisitedSet activePaints;
++    bool haveLayers =  colrv1_start_glyph(canvas, palette,
++                                          fForegroundColor, // FT_Palette_Get_Foreground_Color?
++                                          face, glyphId,
++                                          FT_COLOR_INCLUDE_ROOT_TRANSFORM,
++                                          &activePaints);
++    SkASSERTF(haveLayers, "Could not get COLRv1 layers from '%s'.", face->family_name);
++    return haveLayers;
++}
+ #endif  // TT_SUPPORT_COLRV1
+ 
+ #ifdef FT_COLOR_H
+diff --git a/src/ports/SkFontHost_FreeType_common.h b/src/ports/SkFontHost_FreeType_common.h
+index 444811e..d88433c 100644
+--- a/src/ports/SkFontHost_FreeType_common.h
++++ b/src/ports/SkFontHost_FreeType_common.h
+@@ -20,6 +20,7 @@ class SkCanvas;
+ // These are forward declared to avoid pimpl but also hide the FreeType implementation.
+ typedef struct FT_FaceRec_* FT_Face;
+ typedef signed long FT_Pos;
++typedef unsigned short  FT_UShort; /* freetype/fttypes.h */
+ 
+ 
+ #ifdef SK_DEBUG
+@@ -31,7 +32,13 @@ const char* SkTraceFtrGetError(int);
+ #define SK_TRACEFTR(ERR, ...) do { sk_ignore_unused_variable(ERR); } while (false)
+ #endif
+ 
+-struct SkScalerContextFTUtils {
++struct SK_SPI SkScalerContextFTUtils {
++    static bool skia_colrv1_start_glyph(SkCanvas* canvas,
++                                        FT_Face face,
++                                        uint16_t glyphId,
++                                        FT_UShort palette_index,
++                                        FT_Color_Root_Transform rootTransform
++                                        );
+     SkColor                 fForegroundColor;
+     SkScalerContext::Flags  fFlags;
+ 
diff --git a/patch/skia-m123-minimize-download.patch b/patch/skia-m123-minimize-download.patch
new file mode 100644
index 00000000..dec3706b
--- /dev/null
+++ b/patch/skia-m123-minimize-download.patch
@@ -0,0 +1,70 @@
+diff --git a/DEPS b/DEPS
+index fa09db3..7277713 100644
+--- a/DEPS
++++ b/DEPS
+@@ -23,53 +23,18 @@ vars = {
+ #     ./tools/git-sync-deps
+ deps = {
+   "buildtools"                                   : "https://chromium.googlesource.com/chromium/src/buildtools.git@b138e6ce86ae843c42a1a08f37903207bebcca75",
+-  "third_party/externals/angle2"                 : "https://chromium.googlesource.com/angle/angle.git@e04b7c7392d9d05907db6d453c8b8e577d306a7e",
+-  "third_party/externals/brotli"                 : "https://skia.googlesource.com/external/github.com/google/brotli.git@6d03dfbedda1615c4cba1211f8d81735575209c8",
+-  "third_party/externals/d3d12allocator"         : "https://skia.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator.git@169895d529dfce00390a20e69c2f516066fe7a3b",
+-  # Dawn requires jinja2 and markupsafe for the code generator, tint for SPIRV compilation, and abseil for string formatting.
+-  # When the Dawn revision is updated these should be updated from the Dawn DEPS as well.
+-  "third_party/externals/dawn"                   : "https://dawn.googlesource.com/dawn.git@d3e0bd4770cc8115d1342a8dc051a36e50e8bd26",
+-  "third_party/externals/jinja2"                 : "https://chromium.googlesource.com/chromium/src/third_party/jinja2@e2d024354e11cc6b041b0cff032d73f0c7e43a07",
+-  "third_party/externals/markupsafe"             : "https://chromium.googlesource.com/chromium/src/third_party/markupsafe@0bad08bb207bbfc1d6f3bbc82b9242b0c50e5794",
+-  "third_party/externals/abseil-cpp"             : "https://skia.googlesource.com/external/github.com/abseil/abseil-cpp.git@334aca32051ef6ede2711487acf45d959e9bdffc",
+   "third_party/externals/dng_sdk"                : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
+-  "third_party/externals/egl-registry"           : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@b055c9b483e70ecd57b3cf7204db21f5a06f9ffe",
+-  "third_party/externals/emsdk"                  : "https://skia.googlesource.com/external/github.com/emscripten-core/emsdk.git@a896e3d066448b3530dbcaa48869fafefd738f57",
+   "third_party/externals/expat"                  : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@441f98d02deafd9b090aea568282b28f66a50e36",
+   "third_party/externals/freetype"               : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@47574f7ea445c8bb751da0fa716424c9c29a6807",
+   "third_party/externals/harfbuzz"               : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@4cfc6d8e173e800df086d7be078da2e8c5cfca19",
+-  "third_party/externals/highway"                : "https://chromium.googlesource.com/external/github.com/google/highway.git@424360251cdcfc314cfc528f53c872ecd63af0f0",
+   "third_party/externals/icu"                    : "https://chromium.googlesource.com/chromium/deps/icu.git@a0718d4f121727e30b8d52c7a189ebf5ab52421f",
+-  "third_party/externals/icu4x"                  : "https://chromium.googlesource.com/external/github.com/unicode-org/icu4x.git@4f81635489681ecf7707623177123cb78d6a66a0",
+-  "third_party/externals/imgui"                  : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@55d35d8387c15bf0cfd71861df67af8cfbda7456",
+-  "third_party/externals/libavif"                : "https://skia.googlesource.com/external/github.com/AOMediaCodec/libavif.git@55aab4ac0607ab651055d354d64c4615cf3d8000",
+-  "third_party/externals/libgav1"                : "https://chromium.googlesource.com/codecs/libgav1.git@5cf722e659014ebaf2f573a6dd935116d36eadf1",
+-  "third_party/externals/libgrapheme"            : "https://skia.googlesource.com/external/github.com/FRIGN/libgrapheme/@c0cab63c5300fa12284194fbef57aa2ed62a94c0",
+   "third_party/externals/libjpeg-turbo"          : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@ed683925e4897a84b3bffc5c1414c85b97a129a3",
+-  "third_party/externals/libjxl"                 : "https://chromium.googlesource.com/external/gitlab.com/wg1/jpeg-xl.git@a205468bc5d3a353fb15dae2398a101dff52f2d3",
+   "third_party/externals/libpng"                 : "https://skia.googlesource.com/third_party/libpng.git@144b348e072a78e8130ed0acc452c9f039a67bf2",
+   "third_party/externals/libwebp"                : "https://chromium.googlesource.com/webm/libwebp.git@2af26267cdfcb63a88e5c74a85927a12d6ca1d76",
+-  "third_party/externals/libyuv"                 : "https://chromium.googlesource.com/libyuv/libyuv.git@d248929c059ff7629a85333699717d7a677d8d96",
+-  "third_party/externals/microhttpd"             : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
+-  "third_party/externals/oboe"                   : "https://chromium.googlesource.com/external/github.com/google/oboe.git@b02a12d1dd821118763debec6b83d00a8a0ee419",
+-  "third_party/externals/opengl-registry"        : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
+-  "third_party/externals/perfetto"               : "https://android.googlesource.com/platform/external/perfetto@93885509be1c9240bc55fa515ceb34811e54a394",
+   "third_party/externals/piex"                   : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
+-  "third_party/externals/sfntly"                 : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b55ff303ea2f9e26702b514cf6a3196a2e3e2974",
+-  "third_party/externals/swiftshader"            : "https://swiftshader.googlesource.com/SwiftShader@eb75201a4e0354a36d315dd01077092ec9aa2356",
+   "third_party/externals/vulkanmemoryallocator"  : "https://chromium.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator@a6bfc237255a6bac1513f7c1ebde6d8aed6b5191",
+-  # vulkan-deps is a meta-repo containing several interdependent Khronos Vulkan repositories.
+-  # When the vulkan-deps revision is updated, those repos (spirv-*, vulkan-*) should be updated as well.
+   "third_party/externals/vulkan-deps"            : "https://chromium.googlesource.com/vulkan-deps@13783d616289ab4ff6cad96e570d04183b24f2e0",
+-  "third_party/externals/spirv-cross"            : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3",
+-  "third_party/externals/spirv-headers"          : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@05cc486580771e4fa7ddc89f5c9ee1e97382689a",
+-  "third_party/externals/spirv-tools"            : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@dc6676445be97ab19d8191fee019af62e2aaf774",
+-  "third_party/externals/vello"                  : "https://skia.googlesource.com/external/github.com/linebender/vello.git@e04b6028651dfd6b87067c0d27c1207c7f735a6d",
+   "third_party/externals/vulkan-headers"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Headers@31aa7f634b052d87ede4664053e85f3f4d1d50d3",
+-  "third_party/externals/vulkan-tools"           : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Tools@069bd14c0548556e130fc9e205adc918b7a01891",
+-  "third_party/externals/vulkan-utility-libraries": "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@1b8b60bf7f271a09eeda032d117d51a43ed506cd",
+-  "third_party/externals/unicodetools"           : "https://chromium.googlesource.com/external/github.com/unicode-org/unicodetools@66a3fa9dbdca3b67053a483d130564eabc5fe095",
+-  #"third_party/externals/v8"                     : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
+   "third_party/externals/wuffs"                  : "https://skia.googlesource.com/external/github.com/google/wuffs-mirror-release-c.git@e3f919ccfe3ef542cfc983a82146070258fb57f8",
+   "third_party/externals/zlib"                   : "https://chromium.googlesource.com/chromium/src/third_party/zlib@646b7f569718921d7d4b5b8e22572ff6c76f2596",
+ 
+diff --git a/bin/activate-emsdk b/bin/activate-emsdk
+index 687ca9f..7167d8d 100755
+--- a/bin/activate-emsdk
++++ b/bin/activate-emsdk
+@@ -17,6 +17,7 @@ EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py')
+ EMSDK_VERSION = '3.1.44'
+ 
+ def main():
++    return
+     if sysconfig.get_platform() in ['linux-aarch64', 'linux-arm64']:
+         # This platform cannot install emsdk at the provided version. See
+         # https://github.com/emscripten-core/emsdk/blob/main/emscripten-releases-tags.json#L5
diff --git a/patch/skia-m124-minimize-download.patch b/patch/skia-m124-minimize-download.patch
new file mode 100644
index 00000000..ee7c8550
--- /dev/null
+++ b/patch/skia-m124-minimize-download.patch
@@ -0,0 +1,69 @@
+diff --git a/DEPS b/DEPS
+index b61a799..21bcc41 100644
+--- a/DEPS
++++ b/DEPS
+@@ -23,52 +23,18 @@ vars = {
+ #     ./tools/git-sync-deps
+ deps = {
+   "buildtools"                                   : "https://chromium.googlesource.com/chromium/src/buildtools.git@b138e6ce86ae843c42a1a08f37903207bebcca75",
+-  "third_party/externals/angle2"                 : "https://chromium.googlesource.com/angle/angle.git@21d124c4bf321a18dae1dc94602aa262fc346a8b",
+-  "third_party/externals/brotli"                 : "https://skia.googlesource.com/external/github.com/google/brotli.git@6d03dfbedda1615c4cba1211f8d81735575209c8",
+-  "third_party/externals/d3d12allocator"         : "https://skia.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator.git@169895d529dfce00390a20e69c2f516066fe7a3b",
+-  # Dawn requires jinja2 and markupsafe for the code generator, tint for SPIRV compilation, and abseil for string formatting.
+-  # When the Dawn revision is updated these should be updated from the Dawn DEPS as well.
+-  "third_party/externals/dawn"                   : "https://dawn.googlesource.com/dawn.git@bac513d0ae286600ea0f75a75223a5b52a198b9b",
+-  "third_party/externals/jinja2"                 : "https://chromium.googlesource.com/chromium/src/third_party/jinja2@e2d024354e11cc6b041b0cff032d73f0c7e43a07",
+-  "third_party/externals/markupsafe"             : "https://chromium.googlesource.com/chromium/src/third_party/markupsafe@0bad08bb207bbfc1d6f3bbc82b9242b0c50e5794",
+-  "third_party/externals/abseil-cpp"             : "https://skia.googlesource.com/external/github.com/abseil/abseil-cpp.git@334aca32051ef6ede2711487acf45d959e9bdffc",
+   "third_party/externals/dng_sdk"                : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
+-  "third_party/externals/egl-registry"           : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@b055c9b483e70ecd57b3cf7204db21f5a06f9ffe",
+-  "third_party/externals/emsdk"                  : "https://skia.googlesource.com/external/github.com/emscripten-core/emsdk.git@a896e3d066448b3530dbcaa48869fafefd738f57",
+   "third_party/externals/expat"                  : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@441f98d02deafd9b090aea568282b28f66a50e36",
+   "third_party/externals/freetype"               : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@f42ce25563b73fed0123d18a2556b9ba01d2c76b",
+   "third_party/externals/harfbuzz"               : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@4cfc6d8e173e800df086d7be078da2e8c5cfca19",
+-  "third_party/externals/highway"                : "https://chromium.googlesource.com/external/github.com/google/highway.git@424360251cdcfc314cfc528f53c872ecd63af0f0",
+   "third_party/externals/icu"                    : "https://chromium.googlesource.com/chromium/deps/icu.git@a0718d4f121727e30b8d52c7a189ebf5ab52421f",
+-  "third_party/externals/icu4x"                  : "https://chromium.googlesource.com/external/github.com/unicode-org/icu4x.git@bcf4f7198d4dc5f3127e84a6ca657c88e7d07a13",
+-  "third_party/externals/imgui"                  : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@55d35d8387c15bf0cfd71861df67af8cfbda7456",
+-  "third_party/externals/libavif"                : "https://skia.googlesource.com/external/github.com/AOMediaCodec/libavif.git@55aab4ac0607ab651055d354d64c4615cf3d8000",
+-  "third_party/externals/libgav1"                : "https://chromium.googlesource.com/codecs/libgav1.git@5cf722e659014ebaf2f573a6dd935116d36eadf1",
+-  "third_party/externals/libgrapheme"            : "https://skia.googlesource.com/external/github.com/FRIGN/libgrapheme/@c0cab63c5300fa12284194fbef57aa2ed62a94c0",
+   "third_party/externals/libjpeg-turbo"          : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@ed683925e4897a84b3bffc5c1414c85b97a129a3",
+-  "third_party/externals/libjxl"                 : "https://chromium.googlesource.com/external/gitlab.com/wg1/jpeg-xl.git@a205468bc5d3a353fb15dae2398a101dff52f2d3",
+   "third_party/externals/libpng"                 : "https://skia.googlesource.com/third_party/libpng.git@144b348e072a78e8130ed0acc452c9f039a67bf2",
+   "third_party/externals/libwebp"                : "https://chromium.googlesource.com/webm/libwebp.git@2af26267cdfcb63a88e5c74a85927a12d6ca1d76",
+-  "third_party/externals/libyuv"                 : "https://chromium.googlesource.com/libyuv/libyuv.git@d248929c059ff7629a85333699717d7a677d8d96",
+-  "third_party/externals/microhttpd"             : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
+-  "third_party/externals/oboe"                   : "https://chromium.googlesource.com/external/github.com/google/oboe.git@b02a12d1dd821118763debec6b83d00a8a0ee419",
+-  "third_party/externals/opengl-registry"        : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
+-  "third_party/externals/perfetto"               : "https://android.googlesource.com/platform/external/perfetto@93885509be1c9240bc55fa515ceb34811e54a394",
+   "third_party/externals/piex"                   : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
+-  "third_party/externals/swiftshader"            : "https://swiftshader.googlesource.com/SwiftShader@bbe6452b420c5ddc4b0fd421b0a3ce271262f4ca",
+   "third_party/externals/vulkanmemoryallocator"  : "https://chromium.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator@a6bfc237255a6bac1513f7c1ebde6d8aed6b5191",
+-  # vulkan-deps is a meta-repo containing several interdependent Khronos Vulkan repositories.
+-  # When the vulkan-deps revision is updated, those repos (spirv-*, vulkan-*) should be updated as well.
+   "third_party/externals/vulkan-deps"            : "https://chromium.googlesource.com/vulkan-deps@61f5e95eb939f4fa620f6770907591fee356c409",
+-  "third_party/externals/spirv-cross"            : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3",
+   "third_party/externals/spirv-headers"          : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@8b246ff75c6615ba4532fe4fde20f1be090c3764",
+-  "third_party/externals/spirv-tools"            : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@f20663ca7fec48fdc88e4c4d7c5889f8b4cc5664",
+-  "third_party/externals/vello"                  : "https://skia.googlesource.com/external/github.com/linebender/vello.git@e04b6028651dfd6b87067c0d27c1207c7f735a6d",
+-  "third_party/externals/vulkan-headers"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Headers@577baa05033cf1d9236b3d078ca4b3269ed87a2b",
+-  "third_party/externals/vulkan-tools"           : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Tools@733fd2b522918f81f9c4669350dafd066f99c5d5",
+-  "third_party/externals/vulkan-utility-libraries": "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@a4140c5fd47dcf3a030726a60b293db61cfb54a3",
+-  "third_party/externals/unicodetools"           : "https://chromium.googlesource.com/external/github.com/unicode-org/unicodetools@66a3fa9dbdca3b67053a483d130564eabc5fe095",
+-  #"third_party/externals/v8"                     : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
+   "third_party/externals/wuffs"                  : "https://skia.googlesource.com/external/github.com/google/wuffs-mirror-release-c.git@e3f919ccfe3ef542cfc983a82146070258fb57f8",
+   "third_party/externals/zlib"                   : "https://chromium.googlesource.com/chromium/src/third_party/zlib@646b7f569718921d7d4b5b8e22572ff6c76f2596",
+ 
+diff --git a/bin/activate-emsdk b/bin/activate-emsdk
+index 687ca9f..7167d8d 100755
+--- a/bin/activate-emsdk
++++ b/bin/activate-emsdk
+@@ -17,6 +17,7 @@ EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py')
+ EMSDK_VERSION = '3.1.44'
+ 
+ def main():
++    return
+     if sysconfig.get_platform() in ['linux-aarch64', 'linux-arm64']:
+         # This platform cannot install emsdk at the provided version. See
+         # https://github.com/emscripten-core/emsdk/blob/main/emscripten-releases-tags.json#L5
diff --git a/relnotes/README.m116.md b/relnotes/README.m116.md
index 1b16c092..78b5a237 100644
--- a/relnotes/README.m116.md
+++ b/relnotes/README.m116.md
@@ -69,11 +69,18 @@ of this update had taken from.
   internally, use `GrContext` as it is shorter. In the long term,
   people should use `GrDirectContext`, to align with upstream documentation.
 
-* New `SamplingOptions` class; there is only a default constructor for now.
+* New `SamplingOptions` class; there is only a default constructor in m116 for now.
   Many APIs changes from `m87` to `m116` concern adding new options with it, or
-  changing from `FilterQuality` enum to `SamplingOptions`. If you use any
-  value other than the default `FilterQuality.kMedium_SkFilterQuality`
-  enum, you'll need to request addition to the `SamplingOptions` class.
+  changing from `FilterQuality` enum to `SamplingOptions`.
+
+  m124 Addendum: m124 completed `SamplingOptions` class. In m124 or later,
+  to migrate from m87 FilterQuality-based code:
+
+  FilterQuality.kHigh_FilterQuality   -> SamplingOptions(CubicResampler.Mitchell())
+  FilterQuality.kMedium_FilterQuality -> SamplingOptions(FilterMode.kLinear, MipmapMode.kNearest)    // cpu
+                                         or SamplingOptions(FilterMode.kLinear, MipmapMode.kLinear)  // gpu
+  FilterQuality.kLow_FilterQuality    -> SamplingOptions(FilterMode.kLinear, MipmapMode.kNone)
+  FilterQuality.kNone_FilterQuality   -> SamplingOptions(FilterMode.kNearest, MipmapMode.kNone)
 
 * `yuvainfo.PlanarConfig` enums were splitted into a combination of
   `yuvainfo.PlaneConfig` and `yuvainfo.Subsampling` enums. Where the former
diff --git a/relnotes/README.m121.md b/relnotes/README.m121.md
index 77b046f1..1907c2c9 100644
--- a/relnotes/README.m121.md
+++ b/relnotes/README.m121.md
@@ -16,4 +16,3 @@ Since m120:
   Typeface.MakeDefault(), the no-argument forms, are likely to be removed in the future.
 
   Also avoid skia.Font() and skia.Font(None, ...) ; e.g. skia.Font(skia.Typeface('')) should be used.
-
diff --git a/relnotes/README.m122.md b/relnotes/README.m122.md
new file mode 100644
index 00000000..f9de44b9
--- /dev/null
+++ b/relnotes/README.m122.md
@@ -0,0 +1,4 @@
+No user-visible addition since m121
+
+* Upstream deleted SkFontMgr::RefDefault(); copied into skia-python to allow python code
+  which depends on a default fontmgr (now emulated) to continue to work.
diff --git a/relnotes/README.m123.md b/relnotes/README.m123.md
new file mode 100644
index 00000000..df09c7fc
--- /dev/null
+++ b/relnotes/README.m123.md
@@ -0,0 +1,4 @@
+New to m123:
+
+* skia.Paint().getBlendMode() is now emulated.
+  (upstream removed SkPaint::getBlendMode in Jul, 2021 between m87 and m116)
diff --git a/relnotes/README.m124.md b/relnotes/README.m124.md
new file mode 100644
index 00000000..419b6502
--- /dev/null
+++ b/relnotes/README.m124.md
@@ -0,0 +1,20 @@
+New to m124:
+
+* The skia.SamplingOptions class has been fleshed out.
+
+  To migrate from m87 SkFilterQuality-based code:
+
+  FilterQuality.kHigh_FilterQuality   -> SamplingOptions(CubicResampler.Mitchell())
+  FilterQuality.kMedium_FilterQuality -> SamplingOptions(FilterMode.kLinear, MipmapMode.kNearest)    // cpu
+                                         or SamplingOptions(FilterMode.kLinear, MipmapMode.kLinear)  // gpu
+  FilterQuality.kLow_FilterQuality    -> SamplingOptions(FilterMode.kLinear, MipmapMode.kNone)
+  FilterQuality.kNone_FilterQuality   -> SamplingOptions(FilterMode.kNearest, MipmapMode.kNone)
+
+Since m123:
+
+* Vulkan initialization has changed. In m123, one does:
+
+      vkinstance = GrBackendSemaphore()
+      vkinstance.initVulkan(vksemaphore)
+
+  In m124, `vkinstance = GrBackendSemaphore.MakeVk(vksemaphore)` is used instead.
diff --git a/scripts/build_Linux.sh b/scripts/build_Linux.sh
index 207df70a..2d328c3b 100644
--- a/scripts/build_Linux.sh
+++ b/scripts/build_Linux.sh
@@ -60,8 +60,8 @@ git clone https://gn.googlesource.com/gn && \
 
 # Build skia
 cd skia && \
-    patch -p1 < ../patch/skia-m121-minimize-download.patch && \
-    patch -p1 < ../patch/skia-m121-colrv1-freetype.diff && \
+    patch -p1 < ../patch/skia-m124-minimize-download.patch && \
+    patch -p1 < ../patch/skia-m123-colrv1-freetype.diff && \
     python3 tools/git-sync-deps && \
     cp -f ../gn/out/gn bin/gn && \
     bin/gn gen out/Release --args="
diff --git a/scripts/build_Windows.sh b/scripts/build_Windows.sh
index b4808480..e4d06a0e 100644
--- a/scripts/build_Windows.sh
+++ b/scripts/build_Windows.sh
@@ -4,8 +4,8 @@ export PATH="${PWD}/depot_tools:$PATH"
 
 # Build skia
 cd skia && \
-    patch -p1 < ../patch/skia-m121-minimize-download.patch && \
-    patch -p1 < ../patch/skia-m121-colrv1-freetype.diff && \
+    patch -p1 < ../patch/skia-m124-minimize-download.patch && \
+    patch -p1 < ../patch/skia-m123-colrv1-freetype.diff && \
     python tools/git-sync-deps && \
     bin/gn gen out/Release --args='
 is_official_build=true
diff --git a/scripts/build_macOS.sh b/scripts/build_macOS.sh
index 404f7b2c..7071daa5 100644
--- a/scripts/build_macOS.sh
+++ b/scripts/build_macOS.sh
@@ -22,8 +22,8 @@ function apply_patch {
 }
 
 cd skia && \
-    patch -p1 < ../patch/skia-m121-minimize-download.patch && \
-    patch -p1 < ../patch/skia-m121-colrv1-freetype.diff && \
+    patch -p1 < ../patch/skia-m124-minimize-download.patch && \
+    patch -p1 < ../patch/skia-m123-colrv1-freetype.diff && \
     python3 tools/git-sync-deps && \
     bin/gn gen out/Release --args="
 is_official_build=true
diff --git a/setup.py b/setup.py
index 94796253..d8a37a27 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@
     pass
 
 NAME = 'skia-python'
-__version__ = '121.0b6'
+__version__ = '124.0b7'
 
 SKIA_PATH = os.getenv('SKIA_PATH', 'skia')
 SKIA_OUT_PATH = os.getenv(
@@ -147,6 +147,7 @@ def build_extensions(self):
         SKIA_PATH,
         os.path.join(SKIA_PATH, "third_party/externals/freetype/include"),
         os.path.join(SKIA_PATH, "third_party/externals/vulkan-headers/include"),
+        os.path.join(SKIA_PATH, "include/third_party/vulkan"),
         os.path.join(SKIA_OUT_PATH, 'gen'),
     ],
     define_macros=DEFINE_MACROS,
diff --git a/skia b/skia
index 589c4655..e2ea2eb3 160000
--- a/skia
+++ b/skia
@@ -1,1 +1,1 @@
-Subproject commit 589c46555055ec23bcc4634c209f7f543f6b424c
+Subproject commit e2ea2eb3e0d37fff47968dfaaa337debb3b75b1e
diff --git a/src/skia/Font.cpp b/src/skia/Font.cpp
index f281f210..dcbb9a50 100644
--- a/src/skia/Font.cpp
+++ b/src/skia/Font.cpp
@@ -10,6 +10,48 @@
 #include <src/ports/SkFontHost_FreeType_common.h>
 #endif
 
+#ifdef __APPLE__
+#include "include/ports/SkFontMgr_mac_ct.h"
+#endif
+
+#ifdef __linux__
+#include "include/ports/SkFontMgr_fontconfig.h"
+#endif
+
+#ifdef _WIN32
+#include "include/ports/SkTypeface_win.h"
+#endif
+
+#include <mutex>
+
+namespace {
+
+bool g_factory_called = false;
+
+}  // namespace
+
+static sk_sp<SkFontMgr> fontmgr_factory() {
+#if defined(__APPLE__)
+  return SkFontMgr_New_CoreText(nullptr);
+#elif defined(__linux__)
+  return SkFontMgr_New_FontConfig(nullptr);
+#elif defined(_WIN32)
+  return SkFontMgr_New_DirectWrite();
+#else
+  return SkFontMgr_New_Custom_Empty(); /* last resort: SkFontMgr::RefEmpty(); */
+#endif
+}
+
+sk_sp<SkFontMgr> SkFontMgr_RefDefault() {
+  static std::once_flag flag;
+  static sk_sp<SkFontMgr> mgr;
+  std::call_once(flag, [] {
+    mgr = fontmgr_factory();
+    g_factory_called = true;
+  });
+  return mgr;
+}
+
 using Axis = SkFontParameters::Variation::Axis;
 using Coordinate = SkFontArguments::VariationPosition::Coordinate;
 PYBIND11_MAKE_OPAQUE(std::vector<Coordinate>);
@@ -44,7 +86,7 @@ py::str SkFontMgr_getFamilyName(const SkFontMgr& fontmgr, int index) {
 sk_sp<SkTypeface> SkTypeface_MakeFromName(
     py::object familyName,
     const SkFontStyle* fontStyle) {
-    return SkTypeface::MakeFromName(
+    return SkFontMgr_RefDefault()->legacyMakeTypeface(
         (familyName.is_none()) ?
             nullptr : familyName.cast<std::string>().c_str(),
         (fontStyle) ? *fontStyle : SkFontStyle());
@@ -266,7 +308,7 @@ typeface
             warnings.attr("warn")(
                 "\"Default typeface\" is deprecated upstream. Please specify name/file/style choices.",
                 builtins.attr("DeprecationWarning"));
-            return SkFontMgr::RefDefault()->legacyMakeTypeface("", SkFontStyle());
+            return SkFontMgr_RefDefault()->legacyMakeTypeface("", SkFontStyle());
         }),
         R"docstring(
         Returns the default normal typeface.
@@ -571,7 +613,7 @@ typeface
             warnings.attr("warn")(
                 "\"Default typeface\" is deprecated upstream. Please specify name/file/style choices.",
                 builtins.attr("DeprecationWarning"));
-            return SkFontMgr::RefDefault()->legacyMakeTypeface("", SkFontStyle());
+            return SkFontMgr_RefDefault()->legacyMakeTypeface("", SkFontStyle());
         },
         R"docstring(
         Returns the default normal typeface, which is never nullptr.
@@ -595,7 +637,7 @@ typeface
         py::arg("familyName"), py::arg("fontStyle") = nullptr)
     .def_static("MakeFromFile",
         [] (const std::string& path, int index) {
-            return SkTypeface::MakeFromFile(&path[0], index);
+            return SkFontMgr_RefDefault()->makeFromFile(&path[0], index);
         },
         R"docstring(
         Return a new typeface given a file.
@@ -606,7 +648,10 @@ typeface
         py::arg("path"), py::arg("index") = 0)
     // .def_static("MakeFromStream", &SkTypeface::MakeFromStream,
     //     "Return a new typeface given a stream.")
-    .def_static("MakeFromData", &SkTypeface::MakeFromData,
+    .def_static("MakeFromData",
+        [] (sk_sp<SkData> data, int index) {
+            return SkFontMgr_RefDefault()->makeFromData(data, index);
+        },
         R"docstring(
         Return a new typeface given a :py:class:`Data`.
 
@@ -618,9 +663,9 @@ typeface
     // .def_static("MakeFromFontData", &SkTypeface::MakeFromFontData,
     //     "Return a new typeface given font data and configuration.")
     .def_static("MakeDeserialize",
-        [] (const sk_sp<SkData>& data) {
+        [] (const sk_sp<SkData>& data, sk_sp<SkFontMgr> lastResortMgr) {
             SkMemoryStream stream(data);
-            return SkTypeface::MakeDeserialize(&stream);
+            return SkTypeface::MakeDeserialize(&stream, lastResortMgr);
         },
         R"docstring(
         Given the data previously written by :py:meth:`serialize`, return a new
@@ -628,7 +673,7 @@ typeface
 
         If that font is not available, return nullptr.
         )docstring",
-        py::arg("dats"))
+        py::arg("dats"), py::arg("lastResortMgr"))
     ;
 
 // FontMgr
@@ -660,7 +705,7 @@ py::class_<SkFontMgr, sk_sp<SkFontMgr>, SkRefCnt>(m, "FontMgr",
         assert typeface is not None
 
     )docstring")
-    .def(py::init([] () { return SkFontMgr::RefDefault(); }))
+    .def(py::init([] () { return SkFontMgr_RefDefault(); }))
     .def_static("New_Custom_Empty", &SkFontMgr_New_Custom_Empty)
     .def("__getitem__", &SkFontMgr_getFamilyName, py::arg("index"))
     .def("__len__", &SkFontMgr::countFamilies)
@@ -765,7 +810,7 @@ py::class_<SkFontMgr, sk_sp<SkFontMgr>, SkRefCnt>(m, "FontMgr",
             return fontmgr.legacyMakeTypeface(familyName.c_str(), style);
         },
         py::arg("familyName"), py::arg("style"))
-    .def_static("RefDefault", &SkFontMgr::RefDefault,
+    .def_static("RefDefault", &SkFontMgr_RefDefault,
         R"docstring(
         Return the default fontmgr.
         )docstring")
@@ -814,7 +859,7 @@ font
             warnings.attr("warn")(
                 "\"Default font\" is deprecated upstream. Please specify name/file/style choices.",
                 builtins.attr("DeprecationWarning"));
-            return SkFont(SkFontMgr::RefDefault()->legacyMakeTypeface("", SkFontStyle()));
+            return SkFont(SkFontMgr_RefDefault()->legacyMakeTypeface("", SkFontStyle()));
         }),
         R"docstring(
         Constructs :py:class:`Font` with default values.
@@ -827,7 +872,7 @@ font
                 warnings.attr("warn")(
                     "\"Default font\" is deprecated upstream. Please specify name/file/style choices.",
                     builtins.attr("DeprecationWarning"));
-                return SkFont(SkFontMgr::RefDefault()->legacyMakeTypeface("", SkFontStyle()), size);
+                return SkFont(SkFontMgr_RefDefault()->legacyMakeTypeface("", SkFontStyle()), size);
             } else {
                 return SkFont(typeface.cast<sk_sp<SkTypeface>>(), size);
             }
@@ -849,7 +894,7 @@ font
                 warnings.attr("warn")(
                     "\"Default font\" is deprecated upstream. Please specify name/file/style choices.",
                     builtins.attr("DeprecationWarning"));
-                return SkFont(SkFontMgr::RefDefault()->legacyMakeTypeface("", SkFontStyle()));
+                return SkFont(SkFontMgr_RefDefault()->legacyMakeTypeface("", SkFontStyle()));
             } else {
                 return SkFont(typeface.cast<sk_sp<SkTypeface>>());
             }
@@ -870,7 +915,7 @@ font
                 warnings.attr("warn")(
                     "\"Default font\" is deprecated upstream. Please specify name/file/style choices.",
                     builtins.attr("DeprecationWarning"));
-                return SkFont(SkFontMgr::RefDefault()->legacyMakeTypeface("", SkFontStyle()),
+                return SkFont(SkFontMgr_RefDefault()->legacyMakeTypeface("", SkFontStyle()),
                                       size, scaleX, skewX);
             } else {
                 return SkFont(typeface.cast<sk_sp<SkTypeface>>(),
diff --git a/src/skia/GrContext.cpp b/src/skia/GrContext.cpp
index 8173a177..e56de43b 100644
--- a/src/skia/GrContext.cpp
+++ b/src/skia/GrContext.cpp
@@ -8,8 +8,10 @@
 #include <include/gpu/gl/GrGLInterface.h>
 #include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
 #include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/vk/VulkanTypes.h>
 #include <include/gpu/vk/GrVkBackendContext.h>
 #include <include/gpu/MutableTextureState.h>
 #include <pybind11/chrono.h>
@@ -242,11 +244,18 @@ py::class_<GrBackendSemaphore>(m, "GrBackendSemaphore")
         py::arg("glsync"))
 */
 #ifdef SK_VULKAN
+    .def_static("MakeVk",
+        [] (void* vksemaphore) {
+            return GrBackendSemaphores::MakeVk(reinterpret_cast<VkSemaphore>(vksemaphore));
+        },
+        py::arg("semaphore"))
+/*
     .def("initVulkan",
         [] (GrBackendSemaphore& semaphore, void* vksemaphore) {
             semaphore.initVulkan(reinterpret_cast<VkSemaphore>(vksemaphore));
         },
         py::arg("semaphore"))
+*/
 #endif
     // .def("initMetal", &GrBackendSemaphore::initMetal)
     .def("isInitialized", &GrBackendSemaphore::isInitialized)
@@ -259,7 +268,7 @@ py::class_<GrBackendSemaphore>(m, "GrBackendSemaphore")
 #ifdef SK_VULKAN
     .def("vkSemaphore",
         [] (GrBackendSemaphore& semaphore) {
-            return reinterpret_cast<void*>(semaphore.vkSemaphore());
+            return reinterpret_cast<void*>(GrBackendSemaphores::GetVkSemaphore(semaphore));
         })
 #endif
     // .def("mtlSemaphore", &GrBackendSemaphore::mtlSemaphore)
diff --git a/src/skia/Paint.cpp b/src/skia/Paint.cpp
index 88a68833..d40d1eb0 100644
--- a/src/skia/Paint.cpp
+++ b/src/skia/Paint.cpp
@@ -543,8 +543,10 @@ paint
             subsequent draw
         )docstring",
         py::arg("colorFilter"))
-/*
-    .def("getBlendMode", &SkPaint::getBlendMode,
+    .def("getBlendMode",
+        [] (SkPaint& paint) {
+            return paint.getBlendMode_or(SkBlendMode::kSrcOver);
+        },
         R"docstring(
         Returns :py:class:`BlendMode`.
 
@@ -552,7 +554,6 @@ paint
 
         :return: mode used to combine source color with destination color
         )docstring")
-*/
     .def("isSrcOver", &SkPaint::isSrcOver,
         R"docstring(
         Returns true if :py:class:`BlendMode` is :py:attr:`BlendMode.kSrcOver`,
diff --git a/src/skia/SamplingOptions.cpp b/src/skia/SamplingOptions.cpp
index 0ea49105..b3a721d8 100644
--- a/src/skia/SamplingOptions.cpp
+++ b/src/skia/SamplingOptions.cpp
@@ -1,16 +1,71 @@
 #include "common.h"
 #include <include/core/SkSamplingOptions.h>
 
+/*
+  Reference:
+
+  https://github.com/google/skia/commit/95cc53bafd5fcd94b55a22450457cfde1dd0fd37
+  "Hide filterquality enum entirely"
+
+  The last of SkFilterQuality emulation is:
+
+  FilterQuality.kHigh_FilterQuality   -> SamplingOptions(CubicResampler.Mitchell())
+  FilterQuality.kMedium_FilterQuality -> SamplingOptions(FilterMode.kLinear, MipmapMode.kNearest)    // cpu
+                                           or SamplingOptions(FilterMode.kLinear, MipmapMode.kLinear)  // gpu
+  FilterQuality.kLow_FilterQuality    -> SamplingOptions(FilterMode.kLinear, MipmapMode.kNone)
+  FilterQuality.kNone_FilterQuality   -> SamplingOptions(FilterMode.kNearest, MipmapMode.kNone)
+*/
+
 void initSamplingOptions(py::module& m) {
     py::class_<SkSamplingOptions> samplingoptions(m, "SamplingOptions");
 
-        py::enum_<SkFilterMode>(m, "FilterMode")
+    py::enum_<SkFilterMode>(m, "FilterMode")
         .value("kNearest", SkFilterMode::kNearest,
             "single sample point (nearest neighbor)")
         .value("kLinear", SkFilterMode::kLinear,
             "interporate between 2x2 sample points (bilinear interpolation)")
         .export_values();
 
+    py::enum_<SkMipmapMode>(m, "MipmapMode")
+        .value("kNone", SkMipmapMode::kNone,
+            "ignore mipmap levels, sample from the \"base\"")
+        .value("kNearest", SkMipmapMode::kNearest,
+            "sample from the nearest level")
+        .value("kLinear", SkMipmapMode::kLinear,
+            "interpolate between the two nearest levels")
+        .export_values();
+
+    py::class_<SkCubicResampler>(m, "CubicResampler")
+        .def_readwrite("B", &SkCubicResampler::B)
+        .def_readwrite("C", &SkCubicResampler::C)
+        .def_static("Mitchell", &SkCubicResampler::Mitchell)
+        .def_static("CatmullRom", &SkCubicResampler::CatmullRom);
+
     samplingoptions
-        .def(py::init<>());
+        .def(py::init<>())
+        .def(py::init<SkSamplingOptions>(),
+            py::arg("SamplingOptions"))
+        .def(py::init<SkCubicResampler>(),
+            py::arg("CubicResampler"))
+        .def(py::init<SkFilterMode>(),
+            py::arg("FilterMode"))
+        .def(py::init<SkFilterMode, SkMipmapMode>(),
+            py::arg("FilterMode"), py::arg("MipmapMode"))
+        .def("isAniso", &SkSamplingOptions::isAniso)
+        .def_readonly("maxAniso", &SkSamplingOptions::maxAniso)
+        .def_readonly("useCubic", &SkSamplingOptions::useCubic)
+        .def_readonly("cubic", &SkSamplingOptions::cubic)
+        .def_readonly("filter", &SkSamplingOptions::filter)
+        .def_readonly("mipmap", &SkSamplingOptions::mipmap)
+        .def_static("Aniso", &SkSamplingOptions::Aniso, py::arg("maxAniso"))
+        .def("__eq__",
+            [](const SkSamplingOptions& self, const SkSamplingOptions& other) {
+                return self == other;
+            },
+            py::is_operator())
+        .def("__ne__",
+            [](const SkSamplingOptions& self, const SkSamplingOptions& other) {
+                return self != other;
+            },
+            py::is_operator());
 }


Test patch:
diff --git a/tests/test_font.py b/tests/test_font.py
index 12d03501..368ac1bd 100644
--- a/tests/test_font.py
+++ b/tests/test_font.py
@@ -219,9 +219,9 @@ def test_Typeface_MakeFromData(typeface, args):
         skia.Typeface.MakeFromData(*args), (skia.Typeface, type(None)))
 
 
-def test_Typeface_MakeDeserialize(typeface):
+def test_Typeface_MakeDeserialize(typeface, fontmgr):
     assert isinstance(
-        skia.Typeface.MakeDeserialize(typeface.serialize()), skia.Typeface)
+        skia.Typeface.MakeDeserialize(typeface.serialize(), fontmgr), skia.Typeface)
 
 
 @pytest.fixture
diff --git a/tests/test_grcontext.py b/tests/test_grcontext.py
index e9f80b1f..107342c5 100644
--- a/tests/test_grcontext.py
+++ b/tests/test_grcontext.py
@@ -14,6 +14,13 @@ def test_GrBackendSemaphore_initGL(backend_semaphore):
     backend_semaphore.initGL(None)
 
 
+@pytest.fixture
+def backend_semaphore_vk():
+    if sys.platform.startswith("darwin"):
+        pytest.skip("Known not to work")
+    return skia.GrBackendSemaphore.MakeVk(None)
+
+@pytest.mark.skip(reason='m124:GrBackendSemaphore::initVulkan replaced by GrBackendSemaphores::MakeVk')
 def test_GrBackendSemaphore_initVulkan(backend_semaphore):
     if sys.platform.startswith("darwin"):
         pytest.skip("Known not to work")
@@ -35,6 +42,12 @@ def test_GrBackendSemaphore_vkSemaphore(backend_semaphore):
     backend_semaphore.vkSemaphore()
 
 
+def test_GrBackendSemaphore_vkSemaphore_vk(backend_semaphore_vk):
+    if sys.platform.startswith("darwin"):
+        pytest.skip("Known not to work")
+    backend_semaphore_vk.vkSemaphore()
+
+
 @pytest.fixture(scope='module')
 def backend_format():
     return skia.GrBackendFormat.MakeMock(
diff --git a/tests/test_paint.py b/tests/test_paint.py
index 677420ea..36a58139 100644
--- a/tests/test_paint.py
+++ b/tests/test_paint.py
@@ -181,7 +181,6 @@ def test_Paint_setColorFilter(paint):
     paint.setColorFilter(skia.LumaColorFilter.Make())
 
 
-@pytest.mark.skip(reason='m116:REVISIT')
 def test_Paint_getBlendMode(paint):
     assert isinstance(paint.getBlendMode(), skia.BlendMode)
 
diff --git a/tests/test_samplingoptions.py b/tests/test_samplingoptions.py
index 1dd46fbc..5a6d4ab6 100644
--- a/tests/test_samplingoptions.py
+++ b/tests/test_samplingoptions.py
@@ -1,5 +1,22 @@
 import skia
 import pytest
 
-def test_SamplingOptions_init():
-    assert isinstance(skia.SamplingOptions(), skia.SamplingOptions)
+@pytest.mark.parametrize('args', [
+    (),
+    # FilterQuality enum Equivalents
+    (skia.FilterMode.kLinear,  skia.MipmapMode.kNearest),  # FilterQuality.kMedium_SkFilterQuality CPU
+    (skia.FilterMode.kLinear,  skia.MipmapMode.kLinear),   # FilterQuality.kMedium_SkFilterQuality GPU
+    (skia.FilterMode.kLinear,  skia.MipmapMode.kNone),     # FilterQuality.kLow_SkFilterQuality
+    (skia.FilterMode.kNearest, skia.MipmapMode.kNone)      # FilterQuality.kNone_SkFilterQuality
+])
+def test_SamplingOptions_init(args):
+    assert isinstance(skia.SamplingOptions(*args), skia.SamplingOptions)
+
+def test_SamplingOptions_init_cubic1():
+    # FilterQuality.kHigh_SkFilterQuality
+    assert isinstance(skia.SamplingOptions(skia.CubicResampler.Mitchell()), skia.SamplingOptions)
+
+
+def test_SamplingOptions_init_cubic2():
+    # No FilterQuality Equivalent
+    assert isinstance(skia.SamplingOptions(skia.CubicResampler.CatmullRom()), skia.SamplingOptions)