Implement support for drawing bezier patches
Instead of only having support for coons patches, have support
for only bezier patches ("tensor-product patches" in PDF spec speak)
and implement coons patches as a special case of them.
"Type 7 Shadings (Tensor-Product Patch Meshes)" in the spec explains
how to do that.
This for the most part makes us paint tensor-product patches
the same way as Acrobat Reader and pdf.js.
Still draws some single-color bezier patches as coons
patches, but for most things this works. (The missing bits
won't affect baseline updates here and can be done in a small
follow-up -- the main work there is making test cases I think.)
corpus updates:
https://pdfium-review.googlesource.com/c/pdfium_tests/+/131892
Bug: 407070894
Change-Id: Ic947f5f6202a8e59199fc9009154457cd5b6a4fd
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/131890
Reviewed-by: Nico Weber <thakis@google.com>
Auto-Submit: Nico Weber <thakis@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/DEPS b/DEPS
index fb1afa2..b425e56 100644
--- a/DEPS
+++ b/DEPS
@@ -192,7 +192,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': '039b875bf67383c6da78426a9cb75732b38d5dbb',
+ 'pdfium_tests_revision': '4220c33c6603eea45d1d0b52c1eec7842dd3fb3a',
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling resultdb
# and whatever else without interference from each other.
diff --git a/core/fpdfapi/render/cpdf_rendershading.cpp b/core/fpdfapi/render/cpdf_rendershading.cpp
index c7f279c..501d900 100644
--- a/core/fpdfapi/render/cpdf_rendershading.cpp
+++ b/core/fpdfapi/render/cpdf_rendershading.cpp
@@ -529,124 +529,100 @@
}
}
-struct CoonBezierCoeff {
- void InitFromPoints(float p0, float p1, float p2, float p3) {
- a = -p0 + 3 * p1 - 3 * p2 + p3;
- b = 3 * p0 - 6 * p1 + 3 * p2;
- c = -3 * p0 + 3 * p1;
- d = p0;
+struct CubicBezierPatch {
+ bool IsSmall() const {
+ CFX_FloatRect bbox = CFX_FloatRect::GetBBox(
+ fxcrt::reinterpret_span<const CFX_PointF>(pdfium::span(points)));
+ return bbox.Width() < 2 && bbox.Height() < 2;
}
- void InitFromBezierInterpolation(const CoonBezierCoeff& C1,
- const CoonBezierCoeff& C2,
- const CoonBezierCoeff& D1,
- const CoonBezierCoeff& D2) {
- a = (D1.a + D2.a) / 2;
- b = (D1.b + D2.b) / 2;
- c = (D1.c + D2.c) / 2 - (C1.a / 8 + C1.b / 4 + C1.c / 2) +
- (C2.a / 8 + C2.b / 4) + (-C1.d + D2.d) / 2 - (C2.a + C2.b) / 2;
- d = C1.a / 8 + C1.b / 4 + C1.c / 2 + C1.d;
+ void GetBoundary(pdfium::span<CFX_Path::Point> boundary) {
+ // Returns a cubic bezier path consisting of the outer control points.
+ // Note that patch boundary does not always contain all patch points,
+ // but for "small" patches it's reasonably close.
+ // TODO(thakis): The outer control points don't always contain all
+ // points in the patch, e.g. for a single-color tensor product patch
+ // where the "inner" control points (points[1][1], points[2][1],
+ // points[1][2], points[2][2]) are far outside the "outer" ones.
+ // Make a bezier patch stress test and fix this by continuing to
+ // subdivide if the inner points are outside.
+ boundary[0].point_ = points[0][0];
+ boundary[1].point_ = points[0][1];
+ boundary[2].point_ = points[0][2];
+ boundary[3].point_ = points[0][3];
+ boundary[4].point_ = points[1][3];
+ boundary[5].point_ = points[2][3];
+ boundary[6].point_ = points[3][3];
+ boundary[7].point_ = points[3][2];
+ boundary[8].point_ = points[3][1];
+ boundary[9].point_ = points[3][0];
+ boundary[10].point_ = points[2][0];
+ boundary[11].point_ = points[1][0];
+ boundary[12].point_ = points[0][0];
}
- CoonBezierCoeff first_half() const {
- CoonBezierCoeff result;
- result.a = a / 8;
- result.b = b / 4;
- result.c = c / 2;
- result.d = d;
- return result;
- }
+ void SubdivideVertical(CubicBezierPatch& top, CubicBezierPatch& bottom) {
+ for (int x = 0; x < 4; ++x) {
+ std::array<CFX_PointF, 3> level1 = {
+ 0.5f * (points[x][0] + points[x][1]),
+ 0.5f * (points[x][1] + points[x][2]),
+ 0.5f * (points[x][2] + points[x][3]),
+ };
+ std::array<CFX_PointF, 2> level2 = {
+ 0.5f * (level1[0] + level1[1]),
+ 0.5f * (level1[1] + level1[2]),
+ };
+ CFX_PointF level3 = 0.5f * (level2[0] + level2[1]);
- CoonBezierCoeff second_half() const {
- CoonBezierCoeff result;
- result.a = a / 8;
- result.b = 3 * a / 8 + b / 4;
- result.c = 3 * a / 8 + b / 2 + c / 2;
- result.d = a / 8 + b / 4 + c / 2 + d;
- return result;
- }
+ top.points[x][0] = points[x][0];
+ top.points[x][1] = level1[0];
+ top.points[x][2] = level2[0];
+ top.points[x][3] = level3;
- void GetPoints(pdfium::span<float, 4> p) const {
- p[0] = d;
- p[1] = c / 3 + p[0];
- p[2] = b / 3 - p[0] + 2 * p[1];
- p[3] = a + p[0] - 3 * p[1] + 3 * p[2];
- }
-
- float Distance() const {
- float dis = a + b + c;
- return dis < 0 ? -dis : dis;
- }
-
- float a;
- float b;
- float c;
- float d;
-};
-
-struct CoonBezier {
- void InitFromPoints(float x0,
- float y0,
- float x1,
- float y1,
- float x2,
- float y2,
- float x3,
- float y3) {
- x.InitFromPoints(x0, x1, x2, x3);
- y.InitFromPoints(y0, y1, y2, y3);
- }
-
- void InitFromBezierInterpolation(const CoonBezier& C1,
- const CoonBezier& C2,
- const CoonBezier& D1,
- const CoonBezier& D2) {
- x.InitFromBezierInterpolation(C1.x, C2.x, D1.x, D2.x);
- y.InitFromBezierInterpolation(C1.y, C2.y, D1.y, D2.y);
- }
-
- CoonBezier first_half() const {
- CoonBezier result;
- result.x = x.first_half();
- result.y = y.first_half();
- return result;
- }
-
- CoonBezier second_half() const {
- CoonBezier result;
- result.x = x.second_half();
- result.y = y.second_half();
- return result;
- }
-
- void GetPoints(pdfium::span<CFX_Path::Point> path_points) const {
- static constexpr size_t kPointsCount = 4;
- std::array<float, kPointsCount> points_x;
- std::array<float, kPointsCount> points_y;
- x.GetPoints(points_x);
- y.GetPoints(points_y);
- for (size_t i = 0; i < kPointsCount; ++i) {
- path_points[i].point_ = {points_x[i], points_y[i]};
+ bottom.points[x][0] = level3;
+ bottom.points[x][1] = level2[1];
+ bottom.points[x][2] = level1[2];
+ bottom.points[x][3] = points[x][3];
}
}
- void GetPointsReverse(pdfium::span<CFX_Path::Point> path_points) const {
- static constexpr size_t kPointsCount = 4;
- std::array<float, kPointsCount> points_x;
- std::array<float, kPointsCount> points_y;
- x.GetPoints(points_x);
- y.GetPoints(points_y);
- for (size_t i = 0; i < kPointsCount; ++i) {
- size_t reverse_index = kPointsCount - i - 1;
- path_points[i].point_ = {points_x[reverse_index],
- points_y[reverse_index]};
+ void SubdivideHorizontal(CubicBezierPatch& left, CubicBezierPatch& right) {
+ for (int y = 0; y < 4; ++y) {
+ std::array<CFX_PointF, 3> level1 = {
+ 0.5f * (points[0][y] + points[1][y]),
+ 0.5f * (points[1][y] + points[2][y]),
+ 0.5f * (points[2][y] + points[3][y]),
+ };
+ std::array<CFX_PointF, 2> level2 = {
+ 0.5f * (level1[0] + level1[1]),
+ (1.0f / 2.0f) * (level1[1] + level1[2]),
+ };
+ CFX_PointF level3 = 0.5f * (level2[0] + level2[1]);
+
+ left.points[0][y] = points[0][y];
+ left.points[1][y] = level1[0];
+ left.points[2][y] = level2[0];
+ left.points[3][y] = level3;
+
+ right.points[0][y] = level3;
+ right.points[1][y] = level2[1];
+ right.points[2][y] = level1[2];
+ right.points[3][y] = points[3][y];
}
}
- float Distance() const { return x.Distance() + y.Distance(); }
+ void Subdivide(CubicBezierPatch& top_left,
+ CubicBezierPatch& bottom_left,
+ CubicBezierPatch& top_right,
+ CubicBezierPatch& bottom_right) {
+ CubicBezierPatch top;
+ CubicBezierPatch bottom;
+ SubdivideVertical(top, bottom);
+ top.SubdivideHorizontal(top_left, top_right);
+ bottom.SubdivideHorizontal(bottom_left, bottom_right);
+ }
- CoonBezierCoeff x;
- CoonBezierCoeff y;
+ std::array<std::array<CFX_PointF, 4>, 4> points;
};
int Interpolate(int p1, int p2, int delta1, int delta2, bool* overflow) {
@@ -708,12 +684,9 @@
int y_scale,
int left,
int bottom,
- CoonBezier C1,
- CoonBezier C2,
- CoonBezier D1,
- CoonBezier D2) {
- bool bSmall = C1.Distance() < 2 && C2.Distance() < 2 && D1.Distance() < 2 &&
- D2.Distance() < 2;
+ CubicBezierPatch patch) {
+ bool bSmall = patch.IsSmall();
+
CoonColor div_colors[4];
int d_bottom = 0;
int d_left = 0;
@@ -746,10 +719,7 @@
(d_bottom < kCoonColorThreshold && d_left < kCoonColorThreshold &&
d_top < kCoonColorThreshold && d_right < kCoonColorThreshold)) {
pdfium::span<CFX_Path::Point> points = path.GetPoints();
- C1.GetPoints(points.subspan<0u, 4u>());
- D2.GetPoints(points.subspan<3u, 4u>());
- C2.GetPointsReverse(points.subspan<6u, 4u>());
- D1.GetPointsReverse(points.subspan<9u, 4u>());
+ patch.GetBoundary(points);
CFX_FillRenderOptions fill_options(
CFX_FillRenderOptions::WindingOptions());
fill_options.full_cover = true;
@@ -763,45 +733,36 @@
0, fill_options);
} else {
if (d_bottom < kCoonColorThreshold && d_top < kCoonColorThreshold) {
- CoonBezier m1;
- m1.InitFromBezierInterpolation(D1, D2, C1, C2);
+ CubicBezierPatch top_patch;
+ CubicBezierPatch bottom_patch;
+ patch.SubdivideVertical(top_patch, bottom_patch);
y_scale *= 2;
bottom *= 2;
- Draw(x_scale, y_scale, left, bottom, C1, m1, D1.first_half(),
- D2.first_half());
- Draw(x_scale, y_scale, left, bottom + 1, m1, C2, D1.second_half(),
- D2.second_half());
+ Draw(x_scale, y_scale, left, bottom, top_patch);
+ Draw(x_scale, y_scale, left, bottom + 1, bottom_patch);
} else if (d_left < kCoonColorThreshold &&
d_right < kCoonColorThreshold) {
- CoonBezier m2;
- m2.InitFromBezierInterpolation(C1, C2, D1, D2);
+ CubicBezierPatch left_patch;
+ CubicBezierPatch right_patch;
+ patch.SubdivideHorizontal(left_patch, right_patch);
x_scale *= 2;
left *= 2;
- Draw(x_scale, y_scale, left, bottom, C1.first_half(), C2.first_half(),
- D1, m2);
- Draw(x_scale, y_scale, left + 1, bottom, C1.second_half(),
- C2.second_half(), m2, D2);
+ Draw(x_scale, y_scale, left, bottom, left_patch);
+ Draw(x_scale, y_scale, left + 1, bottom, right_patch);
} else {
- CoonBezier m1;
- CoonBezier m2;
- m1.InitFromBezierInterpolation(D1, D2, C1, C2);
- m2.InitFromBezierInterpolation(C1, C2, D1, D2);
- CoonBezier m1f = m1.first_half();
- CoonBezier m1s = m1.second_half();
- CoonBezier m2f = m2.first_half();
- CoonBezier m2s = m2.second_half();
+ CubicBezierPatch top_left;
+ CubicBezierPatch bottom_left;
+ CubicBezierPatch top_right;
+ CubicBezierPatch bottom_right;
+ patch.Subdivide(top_left, bottom_left, top_right, bottom_right);
x_scale *= 2;
y_scale *= 2;
left *= 2;
bottom *= 2;
- Draw(x_scale, y_scale, left, bottom, C1.first_half(), m1f,
- D1.first_half(), m2f);
- Draw(x_scale, y_scale, left, bottom + 1, m1f, C2.first_half(),
- D1.second_half(), m2s);
- Draw(x_scale, y_scale, left + 1, bottom, C1.second_half(), m1s, m2f,
- D2.first_half());
- Draw(x_scale, y_scale, left + 1, bottom + 1, m1s, C2.second_half(), m2s,
- D2.second_half());
+ Draw(x_scale, y_scale, left, bottom, top_left);
+ Draw(x_scale, y_scale, left, bottom + 1, bottom_left);
+ Draw(x_scale, y_scale, left + 1, bottom, top_right);
+ Draw(x_scale, y_scale, left + 1, bottom + 1, bottom_right);
}
}
}
@@ -836,15 +797,15 @@
return;
}
- PatchDrawer patch;
- patch.alpha = alpha;
- patch.pDevice = &device;
- patch.bNoPathSmooth = bNoPathSmooth;
+ PatchDrawer patch_drawer;
+ patch_drawer.alpha = alpha;
+ patch_drawer.pDevice = &device;
+ patch_drawer.bNoPathSmooth = bNoPathSmooth;
for (int i = 0; i < 13; i++) {
- patch.path.AppendPoint(CFX_PointF(), i == 0
- ? CFX_Path::Point::Type::kMove
- : CFX_Path::Point::Type::kBezier);
+ patch_drawer.path.AppendPoint(
+ CFX_PointF(),
+ i == 0 ? CFX_Path::Point::Type::kMove : CFX_Path::Point::Type::kBezier);
}
std::array<CFX_PointF, 16> coords;
@@ -866,10 +827,10 @@
}
fxcrt::Copy(tempCoords, coords);
std::array<CoonColor, 2> tempColors = {{
- patch.patch_colors[flag],
- patch.patch_colors[(flag + 1) % 4],
+ patch_drawer.patch_colors[flag],
+ patch_drawer.patch_colors[(flag + 1) % 4],
}};
- fxcrt::Copy(tempColors, patch.patch_colors);
+ fxcrt::Copy(tempColors, patch_drawer.patch_colors);
}
for (i = iStartPoint; i < point_count; i++) {
if (!stream.CanReadCoords()) {
@@ -884,9 +845,12 @@
}
FX_RGB_STRUCT<float> rgb = stream.ReadColor();
- patch.patch_colors[i].comp[0] = static_cast<int32_t>(rgb.red * 255);
- patch.patch_colors[i].comp[1] = static_cast<int32_t>(rgb.green * 255);
- patch.patch_colors[i].comp[2] = static_cast<int32_t>(rgb.blue * 255);
+ patch_drawer.patch_colors[i].comp[0] =
+ static_cast<int32_t>(rgb.red * 255);
+ patch_drawer.patch_colors[i].comp[1] =
+ static_cast<int32_t>(rgb.green * 255);
+ patch_drawer.patch_colors[i].comp[2] =
+ static_cast<int32_t>(rgb.blue * 255);
}
CFX_FloatRect bbox = CFX_FloatRect::GetBBox(
@@ -895,19 +859,56 @@
bbox.top <= 0 || bbox.bottom >= (float)pBitmap->GetHeight()) {
continue;
}
- CoonBezier C1;
- CoonBezier C2;
- CoonBezier D1;
- CoonBezier D2;
- C1.InitFromPoints(coords[0].x, coords[0].y, coords[11].x, coords[11].y,
- coords[10].x, coords[10].y, coords[9].x, coords[9].y);
- C2.InitFromPoints(coords[3].x, coords[3].y, coords[4].x, coords[4].y,
- coords[5].x, coords[5].y, coords[6].x, coords[6].y);
- D1.InitFromPoints(coords[0].x, coords[0].y, coords[1].x, coords[1].y,
- coords[2].x, coords[2].y, coords[3].x, coords[3].y);
- D2.InitFromPoints(coords[9].x, coords[9].y, coords[8].x, coords[8].y,
- coords[7].x, coords[7].y, coords[6].x, coords[6].y);
- patch.Draw(1, 1, 0, 0, C1, C2, D1, D2);
+
+ CubicBezierPatch patch;
+ patch.points[0][0] = coords[0];
+ patch.points[0][1] = coords[1];
+ patch.points[0][2] = coords[2];
+ patch.points[0][3] = coords[3];
+ patch.points[1][3] = coords[4];
+ patch.points[2][3] = coords[5];
+ patch.points[3][3] = coords[6];
+ patch.points[3][2] = coords[7];
+ patch.points[3][1] = coords[8];
+ patch.points[3][0] = coords[9];
+ patch.points[2][0] = coords[10];
+ patch.points[1][0] = coords[11];
+ if (type == kTensorProductPatchMeshShading) {
+ patch.points[1][1] = coords[12];
+ patch.points[1][2] = coords[13];
+ patch.points[2][2] = coords[14];
+ patch.points[2][1] = coords[15];
+ } else {
+ CHECK_EQ(type, kCoonsPatchMeshShading);
+ // These equations are from ISO 32000-2:2020, page 267, in
+ // 8.7.4.5.8 Type 7 (tensor-product patch mesh) shadings:
+ patch.points[1][1] =
+ (1.0f / 9.0f) * (-4.0f * patch.points[0][0] +
+ 6.0f * (patch.points[0][1] + patch.points[1][0]) -
+ 2.0f * (patch.points[0][3] + patch.points[3][0]) +
+ 3.0f * (patch.points[3][1] + patch.points[1][3]) -
+ 1.0f * patch.points[3][3]);
+ patch.points[1][2] =
+ (1.0f / 9.0f) * (-4.0f * patch.points[0][3] +
+ 6.0f * (patch.points[0][2] + patch.points[1][3]) -
+ 2.0f * (patch.points[0][0] + patch.points[3][3]) +
+ 3.0f * (patch.points[3][2] + patch.points[1][0]) -
+ 1.0f * patch.points[3][0]);
+ patch.points[2][1] =
+ (1.0f / 9.0f) * (-4.0f * patch.points[3][0] +
+ 6.0f * (patch.points[3][1] + patch.points[2][0]) -
+ 2.0f * (patch.points[3][3] + patch.points[0][0]) +
+ 3.0f * (patch.points[0][1] + patch.points[2][3]) -
+ 1.0f * patch.points[0][3]);
+ patch.points[2][2] =
+ (1.0f / 9.0f) * (-4.0f * patch.points[3][3] +
+ 6.0f * (patch.points[3][2] + patch.points[2][3]) -
+ 2.0f * (patch.points[3][0] + patch.points[0][3]) +
+ 3.0f * (patch.points[0][2] + patch.points[2][0]) -
+ 1.0f * patch.points[0][0]);
+ }
+
+ patch_drawer.Draw(1, 1, 0, 0, patch);
}
}
diff --git a/core/fxcrt/fx_coordinates.h b/core/fxcrt/fx_coordinates.h
index f024aa8..4288aa8 100644
--- a/core/fxcrt/fx_coordinates.h
+++ b/core/fxcrt/fx_coordinates.h
@@ -48,6 +48,10 @@
using CFX_Point = CFX_PTemplate<int32_t>;
using CFX_PointF = CFX_PTemplate<float>;
+inline CFX_PointF operator*(float f, CFX_PointF p) {
+ return {f * p.x, f * p.y};
+}
+
template <class BaseType>
class CFX_STemplate {
public:
diff --git a/testing/resources/pixel/shade_expected.pdf.0.png b/testing/resources/pixel/shade_expected.pdf.0.png
index 8dc2db9..e275f97 100644
--- a/testing/resources/pixel/shade_expected.pdf.0.png
+++ b/testing/resources/pixel/shade_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/shade_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/shade_expected_gdi_skia.pdf.0.png
index 6d9dc9e..e2372c8 100644
--- a/testing/resources/pixel/shade_expected_gdi_skia.pdf.0.png
+++ b/testing/resources/pixel/shade_expected_gdi_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/shade_expected_skia.pdf.0.png b/testing/resources/pixel/shade_expected_skia.pdf.0.png
index ff519f7..2f9357f 100644
--- a/testing/resources/pixel/shade_expected_skia.pdf.0.png
+++ b/testing/resources/pixel/shade_expected_skia.pdf.0.png
Binary files differ