[Skia] Optimize DrawPath() when knockout is on Instead of calculating geometric union and subtraction of paths which may contain many points, composite the objects in pixel-space using blend modes. This updates the pdfium_tests hash in DEPS to https://pdfium-review.googlesource.com/c/pdfium_tests/+/138210 Initially I kept the same structure of doing a union or difference based on the stroke color and drawing the stroke on the separate layer from the fill. However when I read ISO 32000-1:2008 I realized I could simplify it and make the function more accurately follow the spec. Rendering performance for the pdf in the bug using pdfium_test: - Before: 68.72s user 0.37s system 99% cpu 1:09.14 total - After: 0.52s user 0.12s system 99% cpu 0.645 total Bug: 334774307, 460180742 Change-Id: I7cc2d264bb06182c69d74dc7fcd04fe285d47256 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/137630 Reviewed-by: Tom Sepez <tsepez@chromium.org> Commit-Queue: April Kallmeyer <ask@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/DEPS b/DEPS index e66cb17..a363f56 100644 --- a/DEPS +++ b/DEPS
@@ -190,7 +190,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling pdfium_tests # and whatever else without interference from each other. - 'pdfium_tests_revision': '79a975f5b013ab1b3b0d1b887d9bdddcd50725e3', + 'pdfium_tests_revision': '47d8f8f7f29b30a7abb9b116c93d6034f80e255d', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling result_adapter_revision # and whatever else without interference from each other.
diff --git a/core/fxge/skia/fx_skia_device.cpp b/core/fxge/skia/fx_skia_device.cpp index 7a5bae2..6529997 100644 --- a/core/fxge/skia/fx_skia_device.cpp +++ b/core/fxge/skia/fx_skia_device.cpp
@@ -1122,50 +1122,78 @@ if (fill_options.full_cover) { path_paint.setBlendMode(SkBlendMode::kPlus); } + + SkPaint stroke_paint = path_paint; int stroke_alpha = FXARGB_A(stroke_color); if (stroke_alpha) { const CFX_GraphStateData stroke_options_copy = stroke_options ? *stroke_options : CFX_GraphStateData(); - SetupStrokePaint(&path_paint, &stroke_options_copy, transform_matrix, + SetupStrokePaint(&stroke_paint, &stroke_options_copy, transform_matrix, fill_options); } SkAutoCanvasRestore scoped_save_restore(canvas_, /*doSave=*/true); canvas_->concat(transform_matrix); - bool do_stroke = true; - if (fill_options.fill_type != CFX_FillRenderOptions::FillType::kNoFill && + + // Draw the fill (and the stroke if it's a knockout group) + const bool is_point_path = IsPathAPoint(path); + if (!is_point_path && + fill_options.fill_type != CFX_FillRenderOptions::FillType::kNoFill && fill_color) { - SkPath path_outline; - const SkPath* fill_path = &path; - if (stroke_alpha) { - if (group_knockout_) { - skpathutils::FillPathWithPaint(path, path_paint, &path_outline); - if (stroke_color == fill_color && - Op(path, path_outline, SkPathOp::kUnion_SkPathOp, &path_outline)) { - fill_path = &path_outline; - do_stroke = false; - } else if (Op(path, path_outline, SkPathOp::kDifference_SkPathOp, - &path_outline)) { - fill_path = &path_outline; - } - } + // Knockout Group is a PDF feature that means the elements of the group + // should not affect each other with transparency. + // + // See section 11.4.6 of in ISO 32000-1:2008: + // "At any given point, only the topmost object enclosing the point shall + // contribute to the result colour and opacity of the group as a whole" + if (stroke_alpha && group_knockout_) { + // Draw the knockout group path on a separate layer so blend modes can be + // adjusted. When restore() is called path_paint is used to composite the + // layer. + canvas_->saveLayer(nullptr, &path_paint); + SkPaint layer_paint = stroke_paint; + + // kSrc means the top-most object fully overwrites any pixels it draws. + // See https://skia.org/docs/user/api/skblendmode_overview/ + layer_paint.setBlendMode(SkBlendMode::kSrc); + layer_paint.setStyle(SkPaint::kFill_Style); + + layer_paint.setColor(fill_color); + DrawPathImpl(path, layer_paint); + + // Compute the stroke outline and draw with fill style so that if the + // path self-intersects the anti-aliasing pixels won't stack their + // transparency. + // + // Drawing it as a stroke normally already operates in the knockout way + // but not for the AA pixels in some cases. + SkPath stroke_outline; + skpathutils::FillPathWithPaint(path, stroke_paint, &stroke_outline); + layer_paint.setColor(stroke_color); + DrawPathImpl(stroke_outline, layer_paint); + + return true; // `scoped_save_restore` restores two layers here } - path_paint.setStyle(SkPaint::kFill_Style); - path_paint.setColor(fill_color); - DrawPathImpl(*fill_path, path_paint); + + // Draw the fill normally if it isn't a knockout group + stroke_paint.setStyle(SkPaint::kFill_Style); + stroke_paint.setColor(fill_color); + DrawPathImpl(path, stroke_paint); } - if (stroke_alpha && do_stroke) { - path_paint.setStyle(SkPaint::kStroke_Style); - path_paint.setColor(stroke_color); - if (!path.isLastContourClosed() && IsPathAPoint(path)) { - DCHECK_GE(path.countPoints(), 1); - canvas_->drawPoint(path.getPoint(0), path_paint); - } else if (IsPathAPoint(path) && - path_paint.getStrokeCap() != SkPaint::kRound_Cap) { + + // Draw the stroke + if (stroke_alpha) { + stroke_paint.setStyle(SkPaint::kStroke_Style); + stroke_paint.setColor(stroke_color); + if (!path.isLastContourClosed() && is_point_path) { + CHECK_GE(path.countPoints(), 1); + canvas_->drawPoint(path.getPoint(0), stroke_paint); + } else if (is_point_path && + stroke_paint.getStrokeCap() != SkPaint::kRound_Cap) { // Do nothing. A closed 0-length closed path can be rendered only if // its line cap type is round. } else { - DrawPathImpl(path, path_paint); + DrawPathImpl(path, stroke_paint); } } return true;
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index 57e9d78..b78d8bd 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -2697,7 +2697,11 @@ ScopedFPDFBitmap page_bitmap = RenderPage(page); const char* checksum_3 = []() { if (CFX_DefaultRenderDevice::UseSkiaRenderer()) { - return "a5de6ddefcbae60924bebc99347e460b"; +#if BUILDFLAG(IS_APPLE) && defined(ARCH_CPU_ARM64) + return "a4b4b739462a471cad72656e7e8c2af8"; +#else + return "037f5b38d8b612abf0833eb9ac8adf69"; +#endif } return "ff3e6a22326754944cc6e56609acd73b"; }(); @@ -3943,7 +3947,11 @@ TEST_F(FPDFEditEmbedderTest, SaveAndRender) { const char* checksum = []() { if (CFX_DefaultRenderDevice::UseSkiaRenderer()) { - return "edd4aed776c0eaf8c79dd24d9654af95"; +#if BUILDFLAG(IS_APPLE) && defined(ARCH_CPU_ARM64) + return "0b8b84f8da13bf8a5bbaff3087685bed"; +#else + return "a6e8827e9fda09151765130e2f5531eb"; +#endif } return "3c20472b0552c0c22b88ab1ed8c6202b"; }();
diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS index 2abdb60..8c8b44b 100644 --- a/testing/SUPPRESSIONS +++ b/testing/SUPPRESSIONS
@@ -348,6 +348,7 @@ example_042.pdf mac_x86 * * skia font_1_embedded_font_en_feature.pdf mac_x86 * * skia font_2_embedded_font_en_size14.pdf mac_x86 * * skia +transparent1.pdf mac_x86 * * skia xfermodes.pdf mac_x86 * * skia xfermodes2.pdf mac_x86 * * skia xfermodes3.pdf mac_x86 * * skia @@ -817,4 +818,5 @@ # rendering that fuzzy matching is not enough for it to match. The difference # looks like it may be due to several rounding errors combining for a bigger # than normal pixel value difference. +bug_1430333.in mac_x86 * * skia bug_1746.in mac_x86 * * skia
diff --git a/testing/SUPPRESSIONS_EXACT_MATCHING b/testing/SUPPRESSIONS_EXACT_MATCHING index db0a0fc..023eb63 100644 --- a/testing/SUPPRESSIONS_EXACT_MATCHING +++ b/testing/SUPPRESSIONS_EXACT_MATCHING
@@ -167,6 +167,7 @@ new_textmarkup2.pdf mac * * skia new_textmarkup8.pdf mac * * skia path_5_pattern.pdf mac * * skia +path_9.pdf mac_x86 * * skia quick_start_guide.pdf mac * * skia shading_2_15.pdf mac * * skia shading_2_17.pdf mac * * skia
diff --git a/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png index e6a1827..509eff5 100644 --- a/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png +++ b/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png Binary files differ
diff --git a/testing/resources/pixel/bug_1430333_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1430333_expected_skia_mac.pdf.0.png new file mode 100644 index 0000000..2e892d6 --- /dev/null +++ b/testing/resources/pixel/bug_1430333_expected_skia_mac.pdf.0.png Binary files differ
diff --git a/testing/resources/pixel/same_color_knockout_fill_expected_skia.pdf.0.png b/testing/resources/pixel/same_color_knockout_fill_expected_skia.pdf.0.png deleted file mode 100644 index c0ecfbd..0000000 --- a/testing/resources/pixel/same_color_knockout_fill_expected_skia.pdf.0.png +++ /dev/null Binary files differ
diff --git a/testing/resources/pixel/single_point_paths_expected_skia.pdf.0.png b/testing/resources/pixel/single_point_paths_expected_skia.pdf.0.png index 9034434..12b05e6 100644 --- a/testing/resources/pixel/single_point_paths_expected_skia.pdf.0.png +++ b/testing/resources/pixel/single_point_paths_expected_skia.pdf.0.png Binary files differ