From f9924422a4cd1d1b6d2d4240c83b5c1082da3629 Mon Sep 17 00:00:00 2001 From: caryclark Date: Tue, 12 Apr 2016 05:08:17 -0700 Subject: 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 --- core/fpdfapi/fpdf_page/fpdf_page_func.cpp | 86 +++---- core/fpdfapi/fpdf_page/pageint.h | 28 +++ core/fpdfapi/fpdf_render/fpdf_render_pattern.cpp | 4 +- core/fxge/include/fx_ge.h | 5 +- core/fxge/skia/fx_skia_device.cpp | 301 +++++++++++++++++++++-- core/fxge/skia/fx_skia_device.h | 5 +- core/fxge/skia/fx_skia_device_unittest.cpp | 31 +++ 7 files changed, 375 insertions(+), 85 deletions(-) create mode 100644 core/fxge/skia/fx_skia_device_unittest.cpp (limited to 'core') diff --git a/core/fpdfapi/fpdf_page/fpdf_page_func.cpp b/core/fpdfapi/fpdf_page/fpdf_page_func.cpp index b00ab73af1..31faf6a2d7 100644 --- a/core/fpdfapi/fpdf_page/fpdf_page_func.cpp +++ b/core/fpdfapi/fpdf_page/fpdf_page_func.cpp @@ -491,28 +491,40 @@ static uint32_t _GetBits32(const uint8_t* pData, int bitpos, int nbits) { } return result; } -typedef struct { - FX_FLOAT encode_max, encode_min; - int sizes; -} SampleEncodeInfo; -typedef struct { FX_FLOAT decode_max, decode_min; } SampleDecodeInfo; -class CPDF_SampledFunc : public CPDF_Function { +class CPDF_PSFunc : public CPDF_Function { public: - CPDF_SampledFunc(); - ~CPDF_SampledFunc() override; - + CPDF_PSFunc() : CPDF_Function(Type::kType4PostScript) {} // CPDF_Function FX_BOOL v_Init(CPDF_Object* pObj) override; FX_BOOL v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const override; - SampleEncodeInfo* m_pEncodeInfo; - SampleDecodeInfo* m_pDecodeInfo; - uint32_t m_nBitsPerSample; - uint32_t m_SampleMax; - CPDF_StreamAcc* m_pSampleStream; + private: + CPDF_PSEngine m_PS; }; +FX_BOOL CPDF_PSFunc::v_Init(CPDF_Object* pObj) { + CPDF_StreamAcc acc; + acc.LoadAllData(pObj->AsStream(), FALSE); + return m_PS.Parse(reinterpret_cast(acc.GetData()), + acc.GetSize()); +} + +FX_BOOL CPDF_PSFunc::v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const { + CPDF_PSEngine& PS = const_cast(m_PS); + PS.Reset(); + for (uint32_t i = 0; i < m_nInputs; i++) + PS.Push(inputs[i]); + PS.Execute(); + if (PS.GetStackSize() < m_nOutputs) + return FALSE; + for (uint32_t i = 0; i < m_nOutputs; i++) + results[m_nOutputs - i - 1] = PS.Pop(); + return TRUE; +} + +} // namespace + CPDF_SampledFunc::CPDF_SampledFunc() : CPDF_Function(Type::kType0Sampled) { m_pSampleStream = NULL; m_pEncodeInfo = NULL; @@ -524,6 +536,7 @@ CPDF_SampledFunc::~CPDF_SampledFunc() { FX_Free(m_pEncodeInfo); FX_Free(m_pDecodeInfo); } + FX_BOOL CPDF_SampledFunc::v_Init(CPDF_Object* pObj) { CPDF_Stream* pStream = pObj->AsStream(); if (!pStream) @@ -579,13 +592,14 @@ FX_BOOL CPDF_SampledFunc::v_Init(CPDF_Object* pObj) { } return TRUE; } + FX_BOOL CPDF_SampledFunc::v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const { int pos = 0; CFX_FixedBufGrow encoded_input_buf(m_nInputs); FX_FLOAT* encoded_input = encoded_input_buf; - CFX_FixedBufGrow int_buf(m_nInputs * 2); - int* index = int_buf; - int* blocksize = index + m_nInputs; + CFX_FixedBufGrow int_buf(m_nInputs * 2); + uint32_t* index = int_buf; + uint32_t* blocksize = index + m_nInputs; for (uint32_t i = 0; i < m_nInputs; i++) { if (i == 0) blocksize[i] = 1; @@ -594,11 +608,8 @@ FX_BOOL CPDF_SampledFunc::v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const { encoded_input[i] = PDF_Interpolate( inputs[i], m_pDomains[i * 2], m_pDomains[i * 2 + 1], m_pEncodeInfo[i].encode_min, m_pEncodeInfo[i].encode_max); - index[i] = (int)encoded_input[i]; - if (index[i] < 0) - index[i] = 0; - else if (index[i] > m_pEncodeInfo[i].sizes - 1) - index[i] = m_pEncodeInfo[i].sizes - 1; + index[i] = std::min((uint32_t)std::max(0.f, encoded_input[i]), + m_pEncodeInfo[i].sizes - 1); pos += index[i] * blocksize[i]; } FX_SAFE_INT32 bits_to_output = m_nOutputs; @@ -650,37 +661,6 @@ FX_BOOL CPDF_SampledFunc::v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const { return TRUE; } -class CPDF_PSFunc : public CPDF_Function { - public: - // CPDF_Function - CPDF_PSFunc() : CPDF_Function(Type::kType4PostScript) {} - FX_BOOL v_Init(CPDF_Object* pObj) override; - FX_BOOL v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const override; - - CPDF_PSEngine m_PS; -}; - -FX_BOOL CPDF_PSFunc::v_Init(CPDF_Object* pObj) { - CPDF_Stream* pStream = pObj->AsStream(); - CPDF_StreamAcc acc; - acc.LoadAllData(pStream, FALSE); - return m_PS.Parse((const FX_CHAR*)acc.GetData(), acc.GetSize()); -} -FX_BOOL CPDF_PSFunc::v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const { - CPDF_PSEngine& PS = (CPDF_PSEngine&)m_PS; - PS.Reset(); - for (uint32_t i = 0; i < m_nInputs; i++) - PS.Push(inputs[i]); - PS.Execute(); - if (PS.GetStackSize() < m_nOutputs) - return FALSE; - for (uint32_t i = 0; i < m_nOutputs; i++) - results[m_nOutputs - i - 1] = PS.Pop(); - return TRUE; -} - -} // namespace - CPDF_ExpIntFunc::CPDF_ExpIntFunc() : CPDF_Function(Type::kType2ExpotentialInterpolation) { m_pBeginValues = NULL; diff --git a/core/fpdfapi/fpdf_page/pageint.h b/core/fpdfapi/fpdf_page/pageint.h index e6496cfd20..98a05d5495 100644 --- a/core/fpdfapi/fpdf_page/pageint.h +++ b/core/fpdfapi/fpdf_page/pageint.h @@ -393,6 +393,7 @@ class CPDF_Function { uint32_t CountInputs() const { return m_nInputs; } uint32_t CountOutputs() const { return m_nOutputs; } FX_FLOAT GetDomain(int i) const { return m_pDomains[i]; } + FX_FLOAT GetRange(int i) const { return m_pRanges[i]; } Type GetType() const { return m_Type; } protected: @@ -423,6 +424,33 @@ class CPDF_ExpIntFunc : public CPDF_Function { FX_FLOAT* m_pEndValues; }; +class CPDF_SampledFunc : public CPDF_Function { + public: + struct SampleEncodeInfo { + FX_FLOAT encode_max; + FX_FLOAT encode_min; + uint32_t sizes; + }; + + struct SampleDecodeInfo { + FX_FLOAT decode_max; + FX_FLOAT decode_min; + }; + + CPDF_SampledFunc(); + ~CPDF_SampledFunc() override; + + // CPDF_Function + FX_BOOL v_Init(CPDF_Object* pObj) override; + FX_BOOL v_Call(FX_FLOAT* inputs, FX_FLOAT* results) const override; + + SampleEncodeInfo* m_pEncodeInfo; + SampleDecodeInfo* m_pDecodeInfo; + uint32_t m_nBitsPerSample; + uint32_t m_SampleMax; + CPDF_StreamAcc* m_pSampleStream; +}; + class CPDF_StitchFunc : public CPDF_Function { public: CPDF_StitchFunc(); diff --git a/core/fpdfapi/fpdf_render/fpdf_render_pattern.cpp b/core/fpdfapi/fpdf_render/fpdf_render_pattern.cpp index 3f98279dbf..48f9f5d98a 100644 --- a/core/fpdfapi/fpdf_render/fpdf_render_pattern.cpp +++ b/core/fpdfapi/fpdf_render/fpdf_render_pattern.cpp @@ -855,8 +855,8 @@ void CPDF_RenderStatus::DrawShading(CPDF_ShadingPattern* pPattern, clip_rect.Intersect(rect.GetOutterRect()); } if (m_pDevice->GetDeviceCaps(FXDC_RENDER_CAPS) & FXRC_SHADING && - m_pDevice->GetDeviceDriver()->DrawShading(pPattern, pMatrix, alpha, - bAlphaMode)) { + m_pDevice->GetDeviceDriver()->DrawShading(pPattern, pMatrix, clip_rect, + alpha, bAlphaMode)) { return; } CPDF_DeviceBuffer buffer; 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* 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* skColors, + SkTDArray* 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(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* skColors, SkTDArray* 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 skColors; SkTDArray 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); + } + } +} -- cgit v1.2.3