| /* | |
| * Copyright 2025 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkBitmap.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/effects/SkBlurMaskFilter.h" | |
| #include "include/gpu/graphite/Context.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "tests/Test.h" | |
| #include "src/gpu/graphite/Surface_Graphite.h" | |
| using namespace skgpu::graphite; | |
| namespace { | |
| bool colors_are_similar(SkColor c1, SkColor c2, int tolerance) { | |
| if (std::abs((int)SkColorGetA(c1) - (int)SkColorGetA(c2)) > tolerance) return false; | |
| if (std::abs((int)SkColorGetR(c1) - (int)SkColorGetR(c2)) > tolerance) return false; | |
| if (std::abs((int)SkColorGetG(c1) - (int)SkColorGetG(c2)) > tolerance) return false; | |
| if (std::abs((int)SkColorGetB(c1) - (int)SkColorGetB(c2)) > tolerance) return false; | |
| return true; | |
| } | |
| bool layer_test(SkBitmap& bitmap, Context* context, SkBlendMode blendMode) { | |
| auto recorder = context->makeRecorder(); | |
| SkImageInfo info = SkImageInfo::Make(256, 256, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(recorder.get(), info); | |
| SkCanvas* canvas = surface->getCanvas(); | |
| SkPaint blendPaint; | |
| blendPaint.setBlendMode(blendMode); | |
| SkPaint bluePaint; | |
| bluePaint.setColor(0xFF0099FF); | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| canvas->drawRect(SkRect::MakeXYWH(5, 5, 45, 45), bluePaint); | |
| canvas->saveLayer(nullptr, &blendPaint); | |
| SkPaint greenPaint; | |
| greenPaint.setColor(0xCC33FF99); | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| canvas->drawRect(SkRect::MakeXYWH(30, 15, 45, 45), greenPaint); | |
| canvas->restore(); | |
| canvas->saveLayer(nullptr, &blendPaint); | |
| SkPaint redPaint; | |
| redPaint.setColor(0xFFFF3300); | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| canvas->drawRect(SkRect::MakeXYWH(20, 25, 45, 45), redPaint); | |
| canvas->restore(); | |
| bitmap.allocPixels(info); | |
| return canvas->readPixels(bitmap, 0, 0); | |
| } | |
| } // namespace | |
| /* | |
| This test creates a dependency chain between different surfaces, by forcing a | |
| Device::makeImageCopy through makeImageSnapshot(). | |
| Device::makeImageCopy first calls flushPendingWork(nullptr), then Image::Copy. However because | |
| flushPendingWork *only flushes tasks from B*, when Image::Copy adds the copy task directly to | |
| the recorder's root task list, A is not yet flushed. So when the recorder is snapped in | |
| readPixels, only B is copied into C. | |
| The incorrect ordering. | |
| =========== RECORDING =========== | |
| **** FLUSH TOKEN 1 Snap **** | |
| 0: Draw Task (target=0x120e84570) <-- B's drawTask | |
| └── RenderPass Task | |
| 1: Copy TtoT Task <-- the copy task from B to C, A has drawn yet! | |
| 2: Draw Task (target=0x120e834a0) <-- A's drawTask | |
| └── RenderPass Task | |
| 3: Draw Task (target=0x120e85690) <-- C's drawTask | |
| └── RenderPass Task | |
| -------------- END -------------- | |
| The correct ordering: | |
| =========== RECORDING =========== | |
| **** FLUSH TOKEN 1 Snap **** | |
| 0: Draw Task (target=0x120e834a0) <-- A's drawTask | |
| └── RenderPass Task | |
| 1: Draw Task (target=0x120e84570) <-- B's drawTask | |
| └── RenderPass Task | |
| 2: Copy TtoT Task <-- the copy task from B to C | |
| 3: Draw Task (target=0x120e85690) <-- C's drawContext | |
| └── RenderPass Task | |
| -------------- END -------------- | |
| */ | |
| DEF_GRAPHITE_TEST_FOR_CONTEXTS(NotifyInUseTestSnapshot, | |
| /*filter=*/nullptr, | |
| reporter, | |
| context, | |
| testContext, | |
| CtsEnforcement::kApiLevel_202604) { | |
| auto recorder = context->makeRecorder(); | |
| SkImageInfo info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| // "A" | |
| sk_sp<SkSurface> surfaceA = SkSurfaces::RenderTarget(recorder.get(), info); | |
| SkCanvas* canvasA = surfaceA->getCanvas(); | |
| SkPaint greenPaint; | |
| greenPaint.setColor(SK_ColorGREEN); | |
| canvasA->drawRect(SkRect::Make(info.bounds()), greenPaint); | |
| sk_sp<SkImage> imageA = SkSurfaces::AsImage(surfaceA); | |
| REPORTER_ASSERT(reporter, imageA); | |
| // "B" | |
| sk_sp<SkSurface> surfaceB = SkSurfaces::RenderTarget(recorder.get(), info); | |
| SkCanvas* canvasB = surfaceB->getCanvas(); | |
| canvasB->clear(SK_ColorBLACK); | |
| canvasB->drawImage(imageA, 0, 0); | |
| // "C" | |
| sk_sp<SkSurface> surfaceC = SkSurfaces::RenderTarget(recorder.get(), info); | |
| SkCanvas* canvasC = surfaceC->getCanvas(); | |
| canvasC->clear(SK_ColorBLACK); | |
| canvasC->drawImage(surfaceB->makeImageSnapshot(), 0, 0); | |
| // Recorder snap inside readPixels | |
| SkBitmap bitmap; | |
| bitmap.allocPixels(info); | |
| REPORTER_ASSERT(reporter, surfaceC->readPixels(bitmap, 0, 0)); | |
| SkColor topLeft = bitmap.getColor(0, 0); | |
| REPORTER_ASSERT(reporter, topLeft == SK_ColorGREEN, | |
| "Expected green pixel from surface C, got 0x%08X", topLeft); | |
| } | |
| /* | |
| This test confirms that mixing reads from an image view of a surface (SkSurfaces::AsImage) with | |
| writes to that surface's canvas order tasks correctly. At a high level, the flow of draws and | |
| flushes should be: | |
| DrawBlue to A | |
| DrawA to left of B (flushes A's tasks) | |
| DrawRed to A | |
| DrawA to right of B (flushes B's tasks, then A's tasks) | |
| This should produce the following task graph: | |
| =========== RECORDING 1 =========== | |
| **** FLUSH TOKEN 1 Recorder::Snap **** | |
| 0: Draw Task=0x600000433e80 (Target=0x600002e04690) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (A1) | |
| 1: Draw Task=0x600000433f00 (Target=0x600002e045a0) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (B1) | |
| 2: Draw Task=0x600000433fc0 (Target=0x600002e04690) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (A2) | |
| 3: Draw Task=0x60000041dd00 (Target=0x600002e045a0) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (B2) | |
| --------------- END --------------- | |
| Without tracking the fact that B depends on A's prior contents, when notifyInUse() incorrectly | |
| was only flushing A's tasks, the task graph would be the following. This results in the final | |
| contents of A being used for both of its draws into B: | |
| =========== RECORDING 1 =========== | |
| **** FLUSH TOKEN 1 Recorder::Snap **** | |
| 0: Draw Task=0x600001a27bc0 (Target=0x6000030084b0) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (A1) | |
| 1: Draw Task=0x600001a27d00 (Target=0x6000030084b0) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (A2) | |
| 2: Draw Task=0x600001a27c40 (Target=0x6000030083c0) (Label=SkSurfaceRenderTarget) | |
| └── RenderPass Task (B1+B2, only samples A2's state) | |
| --------------- END --------------- | |
| */ | |
| DEF_GRAPHITE_TEST_FOR_CONTEXTS(NotifyInUseTestAsImage, | |
| /*filter=*/nullptr, | |
| reporter, | |
| context, | |
| testContext, | |
| CtsEnforcement::kApiLevel_202604) { | |
| auto recorder = context->makeRecorder(); | |
| SkImageInfo aInfo = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| SkImageInfo bInfo = SkImageInfo::Make(20, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| // "A1" | |
| sk_sp<SkSurface> surfaceA = SkSurfaces::RenderTarget(recorder.get(), aInfo); | |
| surfaceA->getCanvas()->clear(SK_ColorBLUE); | |
| sk_sp<SkImage> imageA = SkSurfaces::AsImage(surfaceA); | |
| REPORTER_ASSERT(reporter, imageA); | |
| // "B1" | |
| sk_sp<SkSurface> surfaceB = SkSurfaces::RenderTarget(recorder.get(), bInfo); | |
| SkCanvas* canvasB = surfaceB->getCanvas(); | |
| canvasB->clear(SK_ColorBLACK); | |
| canvasB->drawImage(imageA, 0, 0); // Should see A's blue clear in left half of B | |
| // "A2" | |
| surfaceA->getCanvas()->clear(SK_ColorRED); | |
| // "B2" | |
| canvasB->drawImage(imageA, 10, 0); // Should see A's now-red clear in right half of B | |
| // Recorder snaps inside readPixels | |
| SkBitmap bitmap; | |
| bitmap.allocPixels(bInfo); | |
| REPORTER_ASSERT(reporter, surfaceB->readPixels(bitmap, 0, 0)); | |
| SkColor leftB = bitmap.getColor(5, 5); | |
| REPORTER_ASSERT(reporter, leftB == SK_ColorBLUE, | |
| "Expected blue pixel from surface B's left half, got 0x%08X", leftB); | |
| SkColor rightB = bitmap.getColor(15, 5); | |
| REPORTER_ASSERT(reporter, rightB == SK_ColorRED, | |
| "Expected red pixel from surface B's right half, got 0x%08X", rightB); | |
| } | |
| /* | |
| These tests replicate the compositing behavior in blink's canvas2d. Due to the use of save | |
| layers, the final order of draw tasks on the root task list does not match the order of draw | |
| calls in the code. At a high level the flow of draws and flushes looks like this: | |
| DrawBlue | |
| DrawGeometry DrawContext0 | |
| SaveLayer SkBlendMode::kMultiply | |
| DrawGreen | |
| DrawGeometry DrawContext1 | |
| Restore0 | |
| SetImmutable FlushPendingWork | |
| DrawSpecial DrawGeometry DrawContext0 | |
| SaveLayer SkBlendMode::kMultiply | |
| DrawRed | |
| DrawGeometry DrawContext2 | |
| Restore1 | |
| SetImmutable FlushPendingWork | |
| DrawSpecial DrawGeometry DrawContext0 | |
| Snap | |
| FlushTrackedDevices | |
| ------------------------------------------------------------------------------------------------ | |
| Some blends, like multiply, depend on prior draws. However, on devices without frame buffer | |
| fetch, this creates a dependency hazard because the destination surface must be copied to be | |
| read in for the blending. This means flushing prior draws to the root task list *before* the | |
| current draw---flushBeforeDraw. Dependency on prior draws is communicated through | |
| Image_Base::notifyInUse, but correct dependency ordering is contingent on the ordering of | |
| flushBeforeDraw and notifyInUse relative to each other. | |
| Calling notifyInUse prior to flushBeforeDraw causes the dependency draw task to be added as a | |
| child task of the current draw instead of flushing to the root task list. In many cases, adding | |
| the draw as a child of the dependent draw task is ok because child tasks execute prior to their | |
| parent task. However, if a later draw *also* depends on the now child draw, an incorrect draw | |
| order may arise. | |
| Using the SkBlendMode::Multiply version of the test as an example: | |
| Incorrect ordering: | |
| 1) Green flushed to the root task list by restore0 setImmutable | |
| 2) Green gets added as child of Blue restore0 drawSpecial | |
| 3) Blue+Green gets flushed as flushBeforeDraw restore0 drawSpecial | |
| 4) Red flushed to the root task list by restore1 setImmutable | |
| 5) Red gets added as child of Blue+Green+Red+Blend restore1 drawSpecial | |
| 6) Blue+Green+Red+Blend gets flushed as flushBeforeDraw restore1 drawSpecial | |
| 7) Malformed gets flushed as flushTrackedDevices snap | |
| Correct ordering: | |
| 1) Green flushed to the root task list by restore0 setImmutable | |
| 2) Blue gets flushed as flushBeforeDraw restore0 drawSpecial | |
| 3) Green added as child of Blue+Green+Blend restore0 drawSpecial | |
| 4) Red flushed to the root task list by restore1 setImmutable | |
| 5) Blue+Green+Blend flushed as flushBeforeDraw restore1 drawSpecial | |
| 6) Red added as child of Blue+Green+Red+Blend restore1 drawSpecial | |
| 7) Blue+Green+Red+Blend flushed as flushTrackedDevices snap | |
| SkBlendMode::Multiply incorrect ordering: | |
| =========== RECORDING 1 =========== | |
| **** FLUSH TOKEN 154 Recorder::Snap **** | |
| 0: Draw Task (target=0x17252f6c0) (DC=0x17253b7d0) <-- Draw Green into layer | |
| └── RenderPass Task | |
| 1: Draw Task (target=0x172544bf0) (DC=0x1725290d0) <-- Draw blue into main layer | |
| │ Draw Task (target=0x17252f6c0) (DC=0x17253b7d0) <-- Green is added as child of blue | |
| │ └── RenderPass Task | |
| └── RenderPass Task | |
| 2: Draw Task (target=0x17254c3d0) (DC=0x172530cc0) <-- Draw red into layer | |
| └── RenderPass Task | |
| 3: Draw Task (target=0x172544bf0) (DC=0x1725290d0) <-- Blend blue with red! | |
| │ Draw Task (target=0x17254c3d0) (DC=0x172530cc0) <-- Red is added as child of blend | |
| │ └── RenderPass Task | |
| │ Copy TtoT Task: Src=0x172544bf0 Dst=0x172547880 <-- Texture Copy because no FbFetch | |
| └── RenderPass Task | |
| 4: Draw Task (target=0x172544bf0) (DC=0x1725290d0) <-- ATTEMPT to blend blue+red with green! | |
| │ Copy TtoT Task: Src=0x172544bf0 Dst=0x17254c130 <-- Copy but there's no lastDrawTask ! | |
| └── RenderPass Task | |
| --------------- END --------------- | |
| The correct ordering: | |
| =========== RECORDING 1 =========== | |
| **** FLUSH TOKEN 181 Recorder::Snap **** | |
| 0: Draw Task (target=0x349db7690) (DC=0x349d856b0) <-- Draw green into layer | |
| └── RenderPass Task | |
| 1: Draw Task (target=0x349db7010) (DC=0x349d98a80) <-- Draw blue into main layer | |
| └── RenderPass Task | |
| 2: Draw Task (target=0x349d99f90) (DC=0x349d981d0) <-- Draw red into layer | |
| └── RenderPass Task | |
| 3: Draw Task (target=0x349db7010) (DC=0x349d98a80) <-- Blend blue with green | |
| │ Draw Task (target=0x349db7690) (DC=0x349d856b0) <-- Green is correctly added as child | |
| │ └── RenderPass Task | |
| │ Copy TtoT Task: Src=0x349db7010 Dst=0x349d96b90 <-- Texture Copy because no FbFetch | |
| └── RenderPass Task | |
| 4: Draw Task (target=0x349db7010) (DC=0x349d98a80) <-- Blend blue+green with red | |
| │ Draw Task (target=0x349d99f90) (DC=0x349d981d0) <-- Red is correctly added as child | |
| │ └── RenderPass Task | |
| │ Copy TtoT Task: Src=0x349db7010 Dst=0x349d988a0 <-- Texture Copy because no FbFetch | |
| └── RenderPass Task | |
| --------------- END --------------- | |
| */ | |
| #define DEFINE_LAYER_BLEND_TEST(TestName, \ | |
| BlendMode, \ | |
| ExpectedBlueOnly, \ | |
| ExpectedGreenOnly, \ | |
| ExpectedBlueAndGreen, \ | |
| ExpectedRedOnly, \ | |
| ExpectedAllOverlap) \ | |
| DEF_GRAPHITE_TEST_FOR_CONTEXTS(NotifyInUseTestLayer##TestName, \ | |
| /*filter=*/nullptr, \ | |
| reporter, \ | |
| context, \ | |
| testContext, \ | |
| CtsEnforcement::kApiLevel_202604) { \ | |
| SkBitmap bitmap; \ | |
| REPORTER_ASSERT(reporter, \ | |
| layer_test(bitmap, context, BlendMode), \ | |
| "Failed to read pixels for BlendMode '%s'", \ | |
| SkBlendMode_Name(BlendMode)); \ | |
| \ | |
| constexpr int kTolerance = 2; \ | |
| SkColor blueOnly = bitmap.getColor(10, 10); \ | |
| REPORTER_ASSERT(reporter, \ | |
| colors_are_similar(blueOnly, ExpectedBlueOnly, kTolerance), \ | |
| "Pt(10,10) BlendMode '%s': Expected blue-only ~0x%08X, got 0x%08X", \ | |
| SkBlendMode_Name(BlendMode), \ | |
| (uint32_t)ExpectedBlueOnly, \ | |
| blueOnly); \ | |
| \ | |
| SkColor greenOnly = bitmap.getColor(70, 30); \ | |
| REPORTER_ASSERT(reporter, \ | |
| colors_are_similar(greenOnly, ExpectedGreenOnly, kTolerance), \ | |
| "Pt(70,30) BlendMode '%s': Expected green-only ~0x%08X, got 0x%08X", \ | |
| SkBlendMode_Name(BlendMode), \ | |
| (uint32_t)ExpectedGreenOnly, \ | |
| greenOnly); \ | |
| \ | |
| SkColor blueAndGreen = bitmap.getColor(40, 20); \ | |
| REPORTER_ASSERT(reporter, \ | |
| colors_are_similar(blueAndGreen, ExpectedBlueAndGreen, kTolerance), \ | |
| "Pt(40,20) BlendMode '%s': Expected blue/green ~0x%08X, got 0x%08X", \ | |
| SkBlendMode_Name(BlendMode), \ | |
| (uint32_t)ExpectedBlueAndGreen, \ | |
| blueAndGreen); \ | |
| \ | |
| SkColor redOnly = bitmap.getColor(25, 68); \ | |
| REPORTER_ASSERT(reporter, \ | |
| colors_are_similar(redOnly, ExpectedRedOnly, kTolerance), \ | |
| "Pt(25,68) BlendMode '%s': Expected red-only ~0x%08X, got 0x%08X", \ | |
| SkBlendMode_Name(BlendMode), \ | |
| (uint32_t)ExpectedRedOnly, \ | |
| redOnly); \ | |
| \ | |
| SkColor allOverlap = bitmap.getColor(40, 40); \ | |
| REPORTER_ASSERT(reporter, \ | |
| colors_are_similar(allOverlap, ExpectedAllOverlap, kTolerance), \ | |
| "Pt(40,40) BlendMode '%s': Expected all-overlap ~0x%08X, got 0x%08X", \ | |
| SkBlendMode_Name(BlendMode), \ | |
| (uint32_t)ExpectedAllOverlap, \ | |
| allOverlap); \ | |
| } | |
| // Basic Porter-Duff blend modes | |
| DEFINE_LAYER_BLEND_TEST(Clear, SkBlendMode::kClear, 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| 0x00000000) | |
| DEFINE_LAYER_BLEND_TEST(Src, SkBlendMode::kSrc, 0x00000000, 0x00000000, 0x00000000, 0xFFFF3300, | |
| 0xFFFF3300) | |
| DEFINE_LAYER_BLEND_TEST(Dst, SkBlendMode::kDst, 0xFF0099FF, 0x00000000, 0xFF0099FF, 0x00000000, | |
| 0xFF0099FF) | |
| DEFINE_LAYER_BLEND_TEST(SrcOver, SkBlendMode::kSrcOver, 0xFF0099FF, 0xCC33FF99, 0xFF29EBAD, | |
| 0xFFFF3300, 0xFFFF3300) | |
| DEFINE_LAYER_BLEND_TEST(DstOver, SkBlendMode::kDstOver, 0xFF0099FF, 0xCC33FF99, 0xFF0099FF, | |
| 0xFFFD3302, 0xFF0099FF) | |
| DEFINE_LAYER_BLEND_TEST(SrcIn, SkBlendMode::kSrcIn, 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| 0xCCFF3300) | |
| DEFINE_LAYER_BLEND_TEST(DstIn, SkBlendMode::kDstIn, 0x00000000, 0x00000000, 0x00000000, 0x00000000, | |
| 0xCC0099FF) | |
| DEFINE_LAYER_BLEND_TEST(SrcOut, SkBlendMode::kSrcOut, 0x00000000, 0x00000000, 0x00000000, | |
| 0xFDFF3300, 0xFFFF3300) | |
| DEFINE_LAYER_BLEND_TEST(DstOut, SkBlendMode::kDstOut, 0xFF0099FF, 0x00000000, 0x33009BFF, | |
| 0x00000000, 0x00000000) | |
| DEFINE_LAYER_BLEND_TEST(SrcATop, SkBlendMode::kSrcATop, 0xFF0099FF, 0x00000000, 0xFF29EBAD, | |
| 0x00000000, 0xFFFF3300) | |
| DEFINE_LAYER_BLEND_TEST(DstATop, SkBlendMode::kDstATop, 0x00000000, 0x00000000, 0x00000000, | |
| 0xFFFD3302, 0xFF3384CC) | |
| DEFINE_LAYER_BLEND_TEST(Xor, SkBlendMode::kXor, 0xFF0099FF, 0xCC33FF99, 0x33009BFF, 0xFDFF3300, | |
| 0xCCFF3300) | |
| DEFINE_LAYER_BLEND_TEST(Plus, SkBlendMode::kPlus, 0xFF0099FF, 0xCC33FF99, 0xFF29FFFF, 0xFFFF3302, | |
| 0xFFFFFFFF) | |
| DEFINE_LAYER_BLEND_TEST(Modulate, SkBlendMode::kModulate, 0x00000000, 0x00000000, 0x00000000, | |
| 0x00000000, 0xCC001E00) | |
| DEFINE_LAYER_BLEND_TEST(Screen, SkBlendMode::kScreen, 0xFF0099FF, 0xCC33FF99, 0xFF29EBFF, | |
| 0xFFFF3302, 0xFFFFEFFF) | |
| // Advanced, non-separable blend modes | |
| DEFINE_LAYER_BLEND_TEST(Overlay, SkBlendMode::kOverlay, 0xFF0099FF, 0xCC33FF99, 0xFF00EBFF, | |
| 0xFFFD3302, 0xFF00DFFF) | |
| DEFINE_LAYER_BLEND_TEST(Darken, SkBlendMode::kDarken, 0xFF0099FF, 0xCC33FF99, 0xFF0099AD, | |
| 0xFFFD3300, 0xFF003300) | |
| DEFINE_LAYER_BLEND_TEST(Lighten, SkBlendMode::kLighten, 0xFF0099FF, 0xCC33FF99, 0xFF29EBFF, | |
| 0xFFFF3302, 0xFFFFEBFF) | |
| DEFINE_LAYER_BLEND_TEST(ColorDodge, SkBlendMode::kColorDodge, 0xFF0099FF, 0xCC33FF99, 0xFF00EBFF, | |
| 0xFFFD3302, 0xFF00FFFF) | |
| DEFINE_LAYER_BLEND_TEST(ColorBurn, SkBlendMode::kColorBurn, 0xFF0099FF, 0xCC33FF99, 0xFF0099FF, | |
| 0xFFFD3302, 0xFF0000FF) | |
| DEFINE_LAYER_BLEND_TEST(HardLight, SkBlendMode::kHardLight, 0xFF0099FF, 0xCC33FF99, 0xFF00EBFF, | |
| 0xFFFF3300, 0xFFFF5E00) | |
| DEFINE_LAYER_BLEND_TEST(SoftLight, SkBlendMode::kSoftLight, 0xFF0099FF, 0xCC33FF99, 0xFF00BDFF, | |
| 0xFFFD3302, 0xFF00A0FF) | |
| DEFINE_LAYER_BLEND_TEST(Difference, SkBlendMode::kDifference, 0xFF0099FF, 0xCC33FF99, 0xFF297085, | |
| 0xFFFF3302, 0xFFD63D85) | |
| DEFINE_LAYER_BLEND_TEST(Exclusion, SkBlendMode::kExclusion, 0xFF0099FF, 0xCC33FF99, 0xFF297085, | |
| 0xFFFF3302, 0xFFD67685) | |
| DEFINE_LAYER_BLEND_TEST(Multiply, SkBlendMode::kMultiply, 0xFF0099FF, 0xCC33FF99, 0xFF0099AD, | |
| 0xFFFD3300, 0xFF001F00) | |
| // HSL component blend modes | |
| DEFINE_LAYER_BLEND_TEST(Hue, SkBlendMode::kHue, 0xFF0099FF, 0xCC33FF99, 0xFF00B17C, 0xFFFE3300, | |
| 0xFFDD4F2C) | |
| DEFINE_LAYER_BLEND_TEST(Saturation, SkBlendMode::kSaturation, 0xFF0099FF, 0xCC33FF99, 0xFF1393E9, | |
| 0xFFFD3302, 0xFF0099FF) | |
| DEFINE_LAYER_BLEND_TEST(Color, SkBlendMode::kColor, 0xFF0099FF, 0xCC33FF99, 0xFF00B17C, 0xFFFE3300, | |
| 0xFFFF4314) | |
| DEFINE_LAYER_BLEND_TEST(Luminosity, SkBlendMode::kLuminosity, 0xFF0099FF, 0xCC33FF99, 0xFF60BFFF, | |
| 0xFFFE3302, 0xFF2180C0) |