Index: core/fxge/skia/fx_skia_device.cpp |
diff --git a/core/fxge/skia/fx_skia_device.cpp b/core/fxge/skia/fx_skia_device.cpp |
index ba671c2511183d9d394965387d550da6f5e9816e..e617630bdcb6aef8dabbb06717e7265b7167b801 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; |
} |