summaryrefslogtreecommitdiff
path: root/core/fxge
diff options
context:
space:
mode:
authorcaryclark <caryclark@google.com>2016-04-12 05:08:17 -0700
committerCommit bot <commit-bot@chromium.org>2016-04-12 05:08:17 -0700
commitf9924422a4cd1d1b6d2d4240c83b5c1082da3629 (patch)
tree94b978e3ee586645eccb6ad6c0d35d3ba7c808b1 /core/fxge
parent822484b5f88d92dbcd567b1c2da38af1e720b56f (diff)
downloadpdfium-f9924422a4cd1d1b6d2d4240c83b5c1082da3629.tar.xz
flesh out gradient shaders
Using these webpages as guides http://www.globelegislators.org/pdfjs/test/pdfs/alphatrans.pdf http://www.antennahouse.com/antenna1/wp-content/uploads/2015/07/background-image-gradient-1.pdf flesh out the gradient shaders to include the PostScript Type 0 sampling function and to include radial gradients. This CL makes rendering these pages agree with Adobe Reader output. Some of these examples use an Extend array to clip the gradient. Skia does not currently support this natively, so construct the clip manually for now. Other PDF pages may construct gradients using alternate mechanisms -- this code will continue to be refactored as those come to light. In particular, this CL sets up the clip and matrix differently for axial gradients and radial gradients -- while it makes sense to do it one way only, I prefer to defer until I have more examples to work with. R=dsinclair@chromium.org,tsepez@chromium.org Review URL: https://codereview.chromium.org/1870463002
Diffstat (limited to 'core/fxge')
-rw-r--r--core/fxge/include/fx_ge.h5
-rw-r--r--core/fxge/skia/fx_skia_device.cpp301
-rw-r--r--core/fxge/skia/fx_skia_device.h5
-rw-r--r--core/fxge/skia/fx_skia_device_unittest.cpp31
4 files changed, 312 insertions, 30 deletions
diff --git a/core/fxge/include/fx_ge.h b/core/fxge/include/fx_ge.h
index 4530677119..2a3a0253a3 100644
--- a/core/fxge/include/fx_ge.h
+++ b/core/fxge/include/fx_ge.h
@@ -603,8 +603,9 @@ class IFX_RenderDeviceDriver {
virtual int GetDriverType() const { return 0; }
virtual void ClearDriver() {}
- virtual FX_BOOL DrawShading(CPDF_ShadingPattern* pPattern,
- CFX_Matrix* pMatrix,
+ virtual FX_BOOL DrawShading(const CPDF_ShadingPattern* pPattern,
+ const CFX_Matrix* pMatrix,
+ const FX_RECT& clip_rect,
int alpha,
FX_BOOL bAlphaMode) {
return false;
diff --git a/core/fxge/skia/fx_skia_device.cpp b/core/fxge/skia/fx_skia_device.cpp
index ba671c2511..e617630bdc 100644
--- a/core/fxge/skia/fx_skia_device.cpp
+++ b/core/fxge/skia/fx_skia_device.cpp
@@ -11,6 +11,7 @@
#include "core/fpdfapi/fpdf_page/pageint.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_array.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_dictionary.h"
+#include "core/fpdfapi/fpdf_parser/include/cpdf_stream_acc.h"
#include "core/fxge/skia/fx_skia_device.h"
#include "third_party/skia/include/core/SkCanvas.h"
@@ -177,6 +178,90 @@ bool AddColors(const CPDF_Function* pFunc, SkTDArray<SkColor>* skColors) {
return true;
}
+uint32_t GetBits32(const uint8_t* pData, int bitpos, int nbits) {
+ ASSERT(0 < nbits && nbits <= 32);
+ const uint8_t* dataPtr = &pData[bitpos / 8];
+ int bitShift;
+ int bitMask;
+ int dstShift;
+ int bitCount = bitpos & 0x07;
+ if (nbits < 8 && nbits + bitCount <= 8) {
+ bitShift = 8 - nbits - bitCount;
+ bitMask = (1 << nbits) - 1;
+ dstShift = 0;
+ } else {
+ bitShift = 0;
+ int bitOffset = 8 - bitCount;
+ bitMask = (1 << SkTMin(bitOffset, nbits)) - 1;
+ dstShift = nbits - bitOffset;
+ }
+ uint32_t result = (uint32_t)(*dataPtr++ >> bitShift & bitMask) << dstShift;
+ while (dstShift >= 8) {
+ dstShift -= 8;
+ result |= *dataPtr++ << dstShift;
+ }
+ if (dstShift > 0) {
+ bitShift = 8 - dstShift;
+ bitMask = (1 << dstShift) - 1;
+ result |= *dataPtr++ >> bitShift & bitMask;
+ }
+ return result;
+}
+
+uint8_t FloatToByte(FX_FLOAT f) {
+ ASSERT(0 <= f && f <= 1);
+ return (uint8_t)(f * 255.99f);
+}
+
+bool AddSamples(const CPDF_Function* pFunc,
+ SkTDArray<SkColor>* skColors,
+ SkTDArray<SkScalar>* skPos) {
+ if (pFunc->CountInputs() != 1)
+ return false;
+ if (pFunc->CountOutputs() != 3) // expect rgb
+ return false;
+ ASSERT(CPDF_Function::Type::kType0Sampled == pFunc->GetType());
+ const CPDF_SampledFunc* sampledFunc =
+ static_cast<const CPDF_SampledFunc*>(pFunc);
+ if (!sampledFunc->m_pEncodeInfo)
+ return false;
+ const CPDF_SampledFunc::SampleEncodeInfo& encodeInfo =
+ sampledFunc->m_pEncodeInfo[0];
+ if (encodeInfo.encode_min != 0)
+ return false;
+ if (encodeInfo.encode_max != encodeInfo.sizes - 1)
+ return false;
+ uint32_t sampleSize = sampledFunc->m_nBitsPerSample;
+ uint32_t sampleCount = encodeInfo.sizes;
+ if (sampleCount != 1 << sampleSize)
+ return false;
+ if (sampledFunc->m_pSampleStream->GetSize() <
+ sampleCount * 3 * sampleSize / 8) {
+ return false;
+ }
+ FX_FLOAT colorsMin[3];
+ FX_FLOAT colorsMax[3];
+ for (int i = 0; i < 3; ++i) {
+ colorsMin[i] = sampledFunc->GetRange(i * 2);
+ colorsMax[i] = sampledFunc->GetRange(i * 2 + 1);
+ }
+ const uint8_t* pSampleData = sampledFunc->m_pSampleStream->GetData();
+ for (uint32_t i = 0; i < sampleCount; ++i) {
+ FX_FLOAT floatColors[3];
+ for (uint32_t j = 0; j < 3; ++j) {
+ int sample = GetBits32(pSampleData, (i * 3 + j) * sampleSize, sampleSize);
+ FX_FLOAT interp = (FX_FLOAT)sample / (sampleCount - 1);
+ floatColors[j] = colorsMin[j] + (colorsMax[j] - colorsMin[j]) * interp;
+ }
+ SkColor color =
+ SkPackARGB32(0xFF, FloatToByte(floatColors[0]),
+ FloatToByte(floatColors[1]), FloatToByte(floatColors[2]));
+ skColors->push(color);
+ skPos->push((FX_FLOAT)i / (sampleCount - 1));
+ }
+ return true;
+}
+
bool AddStitching(const CPDF_Function* pFunc,
SkTDArray<SkColor>* skColors,
SkTDArray<SkScalar>* skPos) {
@@ -304,6 +389,99 @@ void RgbByteOrderTransferBitmap(CFX_DIBitmap* pBitmap,
}
}
+// see https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
+SkScalar LineSide(const SkPoint line[2], const SkPoint& pt) {
+ return (line[1].fY - line[0].fY) * pt.fX - (line[1].fX - line[0].fX) * pt.fY +
+ line[1].fX * line[0].fY - line[1].fY * line[0].fX;
+}
+
+SkPoint IntersectSides(const SkPoint& parallelPt,
+ const SkVector& paraRay,
+ const SkPoint& perpendicularPt) {
+ SkVector perpRay = {paraRay.fY, -paraRay.fX};
+ SkScalar denom = perpRay.fY * paraRay.fX - paraRay.fY * perpRay.fX;
+ if (!denom) {
+ SkPoint zeroPt = {0, 0};
+ return zeroPt;
+ }
+ SkVector ab0 = parallelPt - perpendicularPt;
+ SkScalar numerA = ab0.fY * perpRay.fX - perpRay.fY * ab0.fX;
+ numerA /= denom;
+ SkPoint result = {parallelPt.fX + paraRay.fX * numerA,
+ parallelPt.fY + paraRay.fY * numerA};
+ return result;
+}
+
+void ClipAngledGradient(const SkPoint pts[2],
+ SkPoint rectPts[4],
+ bool clipStart,
+ bool clipEnd,
+ SkPath* clip) {
+ // find the corners furthest from the gradient perpendiculars
+ SkScalar minPerpDist = SK_ScalarMax;
+ SkScalar maxPerpDist = SK_ScalarMin;
+ int minPerpPtIndex = -1;
+ int maxPerpPtIndex = -1;
+ SkVector slope = pts[1] - pts[0];
+ SkPoint startPerp[2] = {pts[0], {pts[0].fX + slope.fY, pts[0].fY - slope.fX}};
+ SkPoint endPerp[2] = {pts[1], {pts[1].fX + slope.fY, pts[1].fY - slope.fX}};
+ for (int i = 0; i < 4; ++i) {
+ SkScalar sDist = LineSide(startPerp, rectPts[i]);
+ SkScalar eDist = LineSide(endPerp, rectPts[i]);
+ if (sDist * eDist <= 0) // if the signs are different,
+ continue; // the point is inside the gradient
+ if (sDist < 0) {
+ SkScalar smaller = SkTMin(sDist, eDist);
+ if (minPerpDist > smaller) {
+ minPerpDist = smaller;
+ minPerpPtIndex = i;
+ }
+ } else {
+ SkScalar larger = SkTMax(sDist, eDist);
+ if (maxPerpDist < larger) {
+ maxPerpDist = larger;
+ maxPerpPtIndex = i;
+ }
+ }
+ }
+ if (minPerpPtIndex < 0 && maxPerpPtIndex < 0) // nothing's outside
+ return;
+ // determine if negative distances are before start or after end
+ SkPoint beforeStart = {pts[0].fX * 2 - pts[1].fX, pts[0].fY * 2 - pts[1].fY};
+ bool beforeNeg = LineSide(startPerp, beforeStart) < 0;
+ const SkPoint& startEdgePt =
+ clipStart ? pts[0] : beforeNeg ? rectPts[minPerpPtIndex]
+ : rectPts[maxPerpPtIndex];
+ const SkPoint& endEdgePt = clipEnd ? pts[1] : beforeNeg
+ ? rectPts[maxPerpPtIndex]
+ : rectPts[minPerpPtIndex];
+ // find the corners that bound the gradient
+ SkScalar minDist = SK_ScalarMax;
+ SkScalar maxDist = SK_ScalarMin;
+ int minBounds = -1;
+ int maxBounds = -1;
+ for (int i = 0; i < 4; ++i) {
+ SkScalar dist = LineSide(pts, rectPts[i]);
+ if (minDist > dist) {
+ minDist = dist;
+ minBounds = i;
+ }
+ if (maxDist < dist) {
+ maxDist = dist;
+ maxBounds = i;
+ }
+ }
+ ASSERT(minBounds >= 0);
+ ASSERT(maxBounds != minBounds && maxBounds >= 0);
+ // construct a clip parallel to the gradient that goes through
+ // rectPts[minBounds] and rectPts[maxBounds] and perpendicular to the
+ // gradient that goes through startEdgePt, endEdgePt.
+ clip->moveTo(IntersectSides(rectPts[minBounds], slope, startEdgePt));
+ clip->lineTo(IntersectSides(rectPts[minBounds], slope, endEdgePt));
+ clip->lineTo(IntersectSides(rectPts[maxBounds], slope, endEdgePt));
+ clip->lineTo(IntersectSides(rectPts[maxBounds], slope, startEdgePt));
+}
+
} // namespace
// convert a stroking path to scanlines
@@ -604,11 +782,17 @@ FX_BOOL CFX_SkiaDeviceDriver::FillRect(const FX_RECT* pRect,
return TRUE;
}
-FX_BOOL CFX_SkiaDeviceDriver::DrawShading(CPDF_ShadingPattern* pPattern,
- CFX_Matrix* pMatrix,
+FX_BOOL CFX_SkiaDeviceDriver::DrawShading(const CPDF_ShadingPattern* pPattern,
+ const CFX_Matrix* pMatrix,
+ const FX_RECT& clip_rect,
int alpha,
FX_BOOL bAlphaMode) {
- CPDF_Function** pFuncs = pPattern->m_pFunctions;
+ if (kAxialShading != pPattern->m_ShadingType &&
+ kRadialShading != pPattern->m_ShadingType) {
+ // TODO(caryclark) more types
+ return false;
+ }
+ CPDF_Function* const* pFuncs = pPattern->m_pFunctions;
int nFuncs = pPattern->m_nFuncs;
if (nFuncs != 1) // TODO(caryclark) remove this restriction
return false;
@@ -616,23 +800,8 @@ FX_BOOL CFX_SkiaDeviceDriver::DrawShading(CPDF_ShadingPattern* pPattern,
CPDF_Array* pCoords = pDict->GetArrayBy("Coords");
if (!pCoords)
return true;
- FX_FLOAT start_x = pCoords->GetNumberAt(0);
- FX_FLOAT start_y = pCoords->GetNumberAt(1);
- FX_FLOAT end_x = pCoords->GetNumberAt(2);
- FX_FLOAT end_y = pCoords->GetNumberAt(3);
- FX_FLOAT t_min = 0;
- FX_FLOAT t_max = 1;
- CPDF_Array* pArray = pDict->GetArrayBy("Domain");
- if (pArray) {
- t_min = pArray->GetNumberAt(0);
- t_max = pArray->GetNumberAt(1);
- }
- FX_BOOL bStartExtend = FALSE, bEndExtend = FALSE;
- pArray = pDict->GetArrayBy("Extend");
- if (pArray) {
- bStartExtend = pArray->GetIntegerAt(0);
- bEndExtend = pArray->GetIntegerAt(1);
- }
+ // TODO(caryclark) Respect Domain[0], Domain[1]. (Don't know what they do
+ // yet.)
SkTDArray<SkColor> skColors;
SkTDArray<SkScalar> skPos;
for (int j = 0; j < nFuncs; j++) {
@@ -640,6 +809,14 @@ FX_BOOL CFX_SkiaDeviceDriver::DrawShading(CPDF_ShadingPattern* pPattern,
if (!pFunc)
continue;
switch (pFunc->GetType()) {
+ case CPDF_Function::Type::kType0Sampled:
+ /* TODO(caryclark)
+ Type 0 Sampled Functions in PostScript can also have an Order integer
+ in the dictionary. PDFium doesn't appear to check for this anywhere.
+ */
+ if (!AddSamples(pFunc, &skColors, &skPos))
+ return false;
+ break;
case CPDF_Function::Type::kType2ExpotentialInterpolation:
if (!AddColors(pFunc, &skColors))
return false;
@@ -654,17 +831,89 @@ FX_BOOL CFX_SkiaDeviceDriver::DrawShading(CPDF_ShadingPattern* pPattern,
return false;
}
}
- SkMatrix skMatrix = ToSkMatrix(*pMatrix);
- SkPoint pts[] = {{start_x, start_y}, {end_x, end_y}};
+ CPDF_Array* pArray = pDict->GetArrayBy("Extend");
+ bool clipStart = !pArray || !pArray->GetIntegerAt(0);
+ bool clipEnd = !pArray || !pArray->GetIntegerAt(1);
SkPaint paint;
paint.setAntiAlias(true);
- paint.setShader(SkGradientShader::MakeLinear(pts, skColors.begin(),
- skPos.begin(), skColors.count(),
- SkShader::kClamp_TileMode));
paint.setAlpha(alpha);
+ SkMatrix skMatrix = ToSkMatrix(*pMatrix);
+ SkRect skRect = SkRect::MakeLTRB(clip_rect.left, clip_rect.top,
+ clip_rect.right, clip_rect.bottom);
+ SkPath skClip;
+ SkPath skPath;
+ if (kAxialShading == pPattern->m_ShadingType) {
+ FX_FLOAT start_x = pCoords->GetNumberAt(0);
+ FX_FLOAT start_y = pCoords->GetNumberAt(1);
+ FX_FLOAT end_x = pCoords->GetNumberAt(2);
+ FX_FLOAT end_y = pCoords->GetNumberAt(3);
+ SkPoint pts[] = {{start_x, start_y}, {end_x, end_y}};
+ skMatrix.mapPoints(pts, SK_ARRAY_COUNT(pts));
+ paint.setShader(SkGradientShader::MakeLinear(
+ pts, skColors.begin(), skPos.begin(), skColors.count(),
+ SkShader::kClamp_TileMode));
+ if (clipStart || clipEnd) {
+ // if the gradient is horizontal or vertical, modify the draw rectangle
+ if (pts[0].fX == pts[1].fX) { // vertical
+ if (pts[0].fY > pts[1].fY) {
+ SkTSwap(pts[0].fY, pts[1].fY);
+ SkTSwap(clipStart, clipEnd);
+ }
+ if (clipStart)
+ skRect.fTop = SkTMax(skRect.fTop, pts[0].fY);
+ if (clipEnd)
+ skRect.fBottom = SkTMin(skRect.fBottom, pts[1].fY);
+ } else if (pts[0].fY == pts[1].fY) { // horizontal
+ if (pts[0].fX > pts[1].fX) {
+ SkTSwap(pts[0].fX, pts[1].fX);
+ SkTSwap(clipStart, clipEnd);
+ }
+ if (clipStart)
+ skRect.fLeft = SkTMax(skRect.fLeft, pts[0].fX);
+ if (clipEnd)
+ skRect.fRight = SkTMin(skRect.fRight, pts[1].fX);
+ } else { // if the gradient is angled and contained by the rect, clip
+ SkPoint rectPts[4] = {{skRect.fLeft, skRect.fTop},
+ {skRect.fRight, skRect.fTop},
+ {skRect.fRight, skRect.fBottom},
+ {skRect.fLeft, skRect.fBottom}};
+ ClipAngledGradient(pts, rectPts, clipStart, clipEnd, &skClip);
+ }
+ }
+ skPath.addRect(skRect);
+ skMatrix.setIdentity();
+ } else {
+ ASSERT(kRadialShading == pPattern->m_ShadingType);
+ FX_FLOAT start_x = pCoords->GetNumberAt(0);
+ FX_FLOAT start_y = pCoords->GetNumberAt(1);
+ FX_FLOAT start_r = pCoords->GetNumberAt(2);
+ FX_FLOAT end_x = pCoords->GetNumberAt(3);
+ FX_FLOAT end_y = pCoords->GetNumberAt(4);
+ FX_FLOAT end_r = pCoords->GetNumberAt(5);
+ SkPoint pts[] = {{start_x, start_y}, {end_x, end_y}};
+
+ paint.setShader(SkGradientShader::MakeTwoPointConical(
+ pts[0], start_r, pts[1], end_r, skColors.begin(), skPos.begin(),
+ skColors.count(), SkShader::kClamp_TileMode));
+ if (clipStart || clipEnd) {
+ if (clipStart && start_r)
+ skClip.addCircle(pts[0].fX, pts[0].fY, start_r);
+ if (clipEnd)
+ skClip.addCircle(pts[1].fX, pts[1].fY, end_r, SkPath::kCCW_Direction);
+ else
+ skClip.setFillType(SkPath::kInverseWinding_FillType);
+ skClip.transform(skMatrix);
+ }
+ SkMatrix inverse;
+ skMatrix.invert(&inverse);
+ skPath.addRect(skRect);
+ skPath.transform(inverse);
+ }
m_pCanvas->save();
+ if (!skClip.isEmpty())
+ m_pCanvas->clipPath(skClip);
m_pCanvas->concat(skMatrix);
- m_pCanvas->drawRect(SkRect::MakeWH(1, 1), paint);
+ m_pCanvas->drawPath(skPath, paint);
m_pCanvas->restore();
return true;
}
diff --git a/core/fxge/skia/fx_skia_device.h b/core/fxge/skia/fx_skia_device.h
index 05a102489e..ef66b94ae2 100644
--- a/core/fxge/skia/fx_skia_device.h
+++ b/core/fxge/skia/fx_skia_device.h
@@ -128,8 +128,9 @@ class CFX_SkiaDeviceDriver : public IFX_RenderDeviceDriver {
int alpha_flag = 0,
void* pIccTransform = NULL) override;
- FX_BOOL DrawShading(CPDF_ShadingPattern* pPattern,
- CFX_Matrix* pMatrix,
+ FX_BOOL DrawShading(const CPDF_ShadingPattern* pPattern,
+ const CFX_Matrix* pMatrix,
+ const FX_RECT& clip_rect,
int alpha,
FX_BOOL bAlphaMode) override;
diff --git a/core/fxge/skia/fx_skia_device_unittest.cpp b/core/fxge/skia/fx_skia_device_unittest.cpp
new file mode 100644
index 0000000000..57a4ec140b
--- /dev/null
+++ b/core/fxge/skia/fx_skia_device_unittest.cpp
@@ -0,0 +1,31 @@
+// Copyright 2016 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fxge/skia/fx_skia_device.cpp"
+#include "testing/fx_string_testhelpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+uint32_t _GetBits32(const uint8_t* pData, int bitpos, int nbits) {
+ int result = 0;
+ for (int i = 0; i < nbits; i++)
+ if (pData[(bitpos + i) / 8] & (1 << (7 - (bitpos + i) % 8))) {
+ result |= 1 << (nbits - i - 1);
+ }
+ return result;
+}
+
+} // namespace
+
+TEST(fxge, GetBits32) {
+ unsigned char data[] = {0xDE, 0x3F, 0xB1, 0x7C, 0x12, 0x9A, 0x04, 0x56};
+ for (int nbits = 1; nbits <= 32; ++nbits) {
+ for (int bitpos = 0; bitpos < (int)sizeof(data) * 8 - nbits; ++bitpos) {
+ uint32_t ref = _GetBits32(data, bitpos, nbits);
+ uint32_t test = GetBits32(data, bitpos, nbits);
+ EXPECT_TRUE(ref == test);
+ }
+ }
+}