diff options
Diffstat (limited to 'core/fxge')
-rw-r--r-- | core/fxge/include/fx_ge.h | 5 | ||||
-rw-r--r-- | core/fxge/skia/fx_skia_device.cpp | 301 | ||||
-rw-r--r-- | core/fxge/skia/fx_skia_device.h | 5 | ||||
-rw-r--r-- | core/fxge/skia/fx_skia_device_unittest.cpp | 31 |
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); + } + } +} |