// Copyright 2014 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. #if defined(_SKIA_SUPPORT_) #include #include #include "core/fxcodec/include/fx_codec.h" #include "core/fxcrt/include/fx_memory.h" #include "core/fpdfapi/fpdf_page/cpdf_shadingpattern.h" #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/include/cfx_fontcache.h" #include "core/fxge/include/cfx_fxgedevice.h" #include "core/fxge/include/cfx_gemodule.h" #include "core/fxge/include/cfx_graphstatedata.h" #include "core/fxge/include/cfx_pathdata.h" #include "core/fxge/include/cfx_renderdevice.h" #include "core/fxge/skia/fx_skia_device.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/core/SkColorPriv.h" #include "third_party/skia/include/core/SkMaskFilter.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/core/SkPictureRecorder.h" #include "third_party/skia/include/core/SkShader.h" #include "third_party/skia/include/core/SkStream.h" #include "third_party/skia/include/core/SkTypeface.h" #include "third_party/skia/include/effects/SkDashPathEffect.h" #include "third_party/skia/include/effects/SkGradientShader.h" #include "third_party/skia/include/pathops/SkPathOps.h" #ifdef SK_DEBUG #include "third_party/skia/include/core/SkClipStack.h" #endif namespace { #define SHOW_SKIA_PATH 0 // set to 1 to print the path contents #define DRAW_SKIA_CLIP 0 // set to 1 to draw a green rectangle around the clip void DebugShowSkiaPath(const SkPath& path) { #if SHOW_SKIA_PATH char buffer[4096]; sk_bzero(buffer, sizeof(buffer)); SkMemoryWStream stream(buffer, sizeof(buffer)); path.dump(&stream, false, false); printf("%s\n", buffer); #endif // SHOW_SKIA_PATH } void DebugShowCanvasMatrix(const SkCanvas* canvas) { #if SHOW_SKIA_PATH SkMatrix matrix = canvas->getTotalMatrix(); SkScalar m[9]; matrix.get9(m); printf("(%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); #endif // SHOW_SKIA_PATH } #if DRAW_SKIA_CLIP SkPaint DebugClipPaint() { SkPaint paint; paint.setAntiAlias(true); paint.setColor(SK_ColorGREEN); paint.setStyle(SkPaint::kStroke_Style); return paint; } void DebugDrawSkiaClipRect(SkCanvas* canvas, const SkRect& rect) { SkPaint paint = DebugClipPaint(); canvas->drawRect(rect, paint); } void DebugDrawSkiaClipPath(SkCanvas* canvas, const SkPath& path) { SkPaint paint = DebugClipPaint(); canvas->drawPath(path, paint); } #else // DRAW_SKIA_CLIP void DebugDrawSkiaClipRect(SkCanvas* canvas, const SkRect& rect) {} void DebugDrawSkiaClipPath(SkCanvas* canvas, const SkPath& path) {} #endif // DRAW_SKIA_CLIP #undef SHOW_SKIA_PATH #undef DRAW_SKIA_CLIP static void DebugValidate(const CFX_DIBitmap* bitmap, const CFX_DIBitmap* device) { if (bitmap) { SkASSERT(bitmap->GetBPP() == 8 || bitmap->GetBPP() == 32); if (bitmap->GetBPP() == 32) { bitmap->DebugVerifyBitmapIsPreMultiplied(); } } if (device) { SkASSERT(device->GetBPP() == 8 || device->GetBPP() == 32); if (device->GetBPP() == 32) { device->DebugVerifyBitmapIsPreMultiplied(); } } } SkPath BuildPath(const CFX_PathData* pPathData) { SkPath skPath; const CFX_PathData* pFPath = pPathData; int nPoints = pFPath->GetPointCount(); FX_PATHPOINT* pPoints = pFPath->GetPoints(); for (int i = 0; i < nPoints; i++) { FX_FLOAT x = pPoints[i].m_PointX; FX_FLOAT y = pPoints[i].m_PointY; int point_type = pPoints[i].m_Flag & FXPT_TYPE; if (point_type == FXPT_MOVETO) { skPath.moveTo(x, y); } else if (point_type == FXPT_LINETO) { skPath.lineTo(x, y); } else if (point_type == FXPT_BEZIERTO) { FX_FLOAT x2 = pPoints[i + 1].m_PointX, y2 = pPoints[i + 1].m_PointY; FX_FLOAT x3 = pPoints[i + 2].m_PointX, y3 = pPoints[i + 2].m_PointY; skPath.cubicTo(x, y, x2, y2, x3, y3); i += 2; } if (pPoints[i].m_Flag & FXPT_CLOSEFIGURE) skPath.close(); } return skPath; } SkMatrix ToSkMatrix(const CFX_Matrix& m) { SkMatrix skMatrix; skMatrix.setAll(m.a, m.b, m.e, m.c, m.d, m.f, 0, 0, 1); return skMatrix; } // use when pdf's y-axis points up insead of down SkMatrix ToFlippedSkMatrix(const CFX_Matrix& m, SkScalar flip) { SkMatrix skMatrix; skMatrix.setAll(m.a * flip, -m.c * flip, m.e, m.b * flip, -m.d * flip, m.f, 0, 0, 1); return skMatrix; } SkXfermode::Mode GetSkiaBlendMode(int blend_type) { switch (blend_type) { case FXDIB_BLEND_MULTIPLY: return SkXfermode::kMultiply_Mode; case FXDIB_BLEND_SCREEN: return SkXfermode::kScreen_Mode; case FXDIB_BLEND_OVERLAY: return SkXfermode::kOverlay_Mode; case FXDIB_BLEND_DARKEN: return SkXfermode::kDarken_Mode; case FXDIB_BLEND_LIGHTEN: return SkXfermode::kLighten_Mode; case FXDIB_BLEND_COLORDODGE: return SkXfermode::kColorDodge_Mode; case FXDIB_BLEND_COLORBURN: return SkXfermode::kColorBurn_Mode; case FXDIB_BLEND_HARDLIGHT: return SkXfermode::kHardLight_Mode; case FXDIB_BLEND_SOFTLIGHT: return SkXfermode::kSoftLight_Mode; case FXDIB_BLEND_DIFFERENCE: return SkXfermode::kDifference_Mode; case FXDIB_BLEND_EXCLUSION: return SkXfermode::kExclusion_Mode; case FXDIB_BLEND_HUE: return SkXfermode::kHue_Mode; case FXDIB_BLEND_SATURATION: return SkXfermode::kSaturation_Mode; case FXDIB_BLEND_COLOR: return SkXfermode::kColor_Mode; case FXDIB_BLEND_LUMINOSITY: return SkXfermode::kLuminosity_Mode; case FXDIB_BLEND_NORMAL: default: return SkXfermode::kSrcOver_Mode; } } bool AddColors(const CPDF_ExpIntFunc* pFunc, SkTDArray* skColors) { if (pFunc->CountInputs() != 1) return false; if (pFunc->m_Exponent != 1) return false; if (pFunc->m_nOrigOutputs != 3) return false; skColors->push( SkColorSetARGB(0xFF, SkUnitScalarClampToByte(pFunc->m_pBeginValues[0]), SkUnitScalarClampToByte(pFunc->m_pBeginValues[1]), SkUnitScalarClampToByte(pFunc->m_pBeginValues[2]))); skColors->push( SkColorSetARGB(0xFF, SkUnitScalarClampToByte(pFunc->m_pEndValues[0]), SkUnitScalarClampToByte(pFunc->m_pEndValues[1]), SkUnitScalarClampToByte(pFunc->m_pEndValues[2]))); return true; } uint8_t FloatToByte(FX_FLOAT f) { ASSERT(0 <= f && f <= 1); return (uint8_t)(f * 255.99f); } bool AddSamples(const CPDF_SampledFunc* pFunc, SkTDArray* skColors, SkTDArray* skPos) { if (pFunc->CountInputs() != 1) return false; if (pFunc->CountOutputs() != 3) // expect rgb return false; if (pFunc->GetEncodeInfo().empty()) return false; const CPDF_SampledFunc::SampleEncodeInfo& encodeInfo = pFunc->GetEncodeInfo()[0]; if (encodeInfo.encode_min != 0) return false; if (encodeInfo.encode_max != encodeInfo.sizes - 1) return false; uint32_t sampleSize = pFunc->GetBitsPerSample(); uint32_t sampleCount = encodeInfo.sizes; if (sampleCount != 1U << sampleSize) return false; if (pFunc->GetSampleStream()->GetSize() < sampleCount * 3 * sampleSize / 8) return false; FX_FLOAT colorsMin[3]; FX_FLOAT colorsMax[3]; for (int i = 0; i < 3; ++i) { colorsMin[i] = pFunc->GetRange(i * 2); colorsMax[i] = pFunc->GetRange(i * 2 + 1); } const uint8_t* pSampleData = pFunc->GetSampleStream()->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_StitchFunc* pFunc, SkTDArray* skColors, SkTDArray* skPos) { int inputs = pFunc->CountInputs(); FX_FLOAT boundsStart = pFunc->GetDomain(0); const auto& subFunctions = pFunc->GetSubFunctions(); for (int i = 0; i < inputs; ++i) { const CPDF_ExpIntFunc* pSubFunc = subFunctions[i]->ToExpIntFunc(); if (!pSubFunc) return false; if (!AddColors(pSubFunc, skColors)) return false; FX_FLOAT boundsEnd = i < inputs - 1 ? pFunc->GetBound(i) : pFunc->GetDomain(1); skPos->push(boundsStart); skPos->push(boundsEnd); boundsStart = boundsEnd; } return true; } // 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)); } void SetBitmapMatrix(const CFX_Matrix* pMatrix, int width, int height, SkMatrix* skMatrix) { const CFX_Matrix& m = *pMatrix; skMatrix->setAll(m.a / width, -m.c / height, m.c + m.e, m.b / width, -m.d / height, m.d + m.f, 0, 0, 1); } void SetBitmapPaint(bool isAlphaMask, uint32_t argb, int bitmap_alpha, int blend_type, SkPaint* paint) { paint->setAntiAlias(true); if (isAlphaMask) { paint->setColorFilter( SkColorFilter::MakeModeFilter(argb, SkXfermode::kSrc_Mode)); } // paint->setFilterQuality(kHigh_SkFilterQuality); paint->setXfermodeMode(GetSkiaBlendMode(blend_type)); paint->setAlpha(bitmap_alpha); } bool Upsample(const CFX_DIBSource* pSource, std::unique_ptr& dst8Storage, std::unique_ptr& dst32Storage, SkColorTable** ctPtr, SkBitmap* skBitmap, int* widthPtr, int* heightPtr, bool forceAlpha) { void* buffer = pSource->GetBuffer(); if (!buffer) return false; SkColorType colorType = forceAlpha || pSource->IsAlphaMask() ? SkColorType::kAlpha_8_SkColorType : SkColorType::kGray_8_SkColorType; SkAlphaType alphaType = pSource->IsAlphaMask() ? kPremul_SkAlphaType : kOpaque_SkAlphaType; int width = pSource->GetWidth(); int height = pSource->GetHeight(); int rowBytes = pSource->GetPitch(); switch (pSource->GetBPP()) { case 1: { dst8Storage.reset(FX_Alloc2D(uint8_t, width, height)); uint8_t* dst8Pixels = dst8Storage.get(); for (int y = 0; y < height; ++y) { const uint8_t* srcRow = static_cast(buffer) + y * rowBytes; uint8_t* dstRow = dst8Pixels + y * width; for (int x = 0; x < width; ++x) dstRow[x] = srcRow[x >> 3] & (1 << (~x & 0x07)) ? 0xFF : 0x00; } buffer = dst8Storage.get(); rowBytes = width; break; } case 8: if (pSource->GetPalette()) { *ctPtr = new SkColorTable(pSource->GetPalette(), pSource->GetPaletteSize()); colorType = SkColorType::kIndex_8_SkColorType; } break; case 24: { dst32Storage.reset(FX_Alloc2D(uint32_t, width, height)); uint32_t* dst32Pixels = dst32Storage.get(); for (int y = 0; y < height; ++y) { const uint8_t* srcRow = static_cast(buffer) + y * rowBytes; uint32_t* dstRow = dst32Pixels + y * width; for (int x = 0; x < width; ++x) { dstRow[x] = SkPackARGB32(0xFF, srcRow[x * 3 + 2], srcRow[x * 3 + 1], srcRow[x * 3 + 0]); } } buffer = dst32Storage.get(); rowBytes = width * sizeof(uint32_t); colorType = SkColorType::kN32_SkColorType; alphaType = kOpaque_SkAlphaType; break; } case 32: colorType = SkColorType::kN32_SkColorType; alphaType = kPremul_SkAlphaType; pSource->DebugVerifyBitmapIsPreMultiplied(buffer); break; default: SkASSERT(0); // TODO(caryclark) ensure that all cases are covered colorType = SkColorType::kUnknown_SkColorType; } SkImageInfo imageInfo = SkImageInfo::Make(width, height, colorType, alphaType); skBitmap->installPixels(imageInfo, buffer, rowBytes, *ctPtr, nullptr, nullptr); *widthPtr = width; *heightPtr = height; return true; } } // namespace // Encapsulate the state used for successive text and path draws so that // they can be combined. class SkiaState { public: enum class Clip { kSave, kPath, }; // mark all cached state as uninitialized SkiaState() : m_pFont(nullptr), m_pCache(nullptr), m_fontSize(0), m_fillColor(0), m_strokeColor(0), m_blendType(0), m_commandIndex(0), m_drawText(false), m_drawPath(false), m_fillPath(false), m_debugDisable(false) {} bool DrawPath(const CFX_PathData* pPathData, const CFX_Matrix* pMatrix, const CFX_GraphStateData* pDrawState, uint32_t fill_color, uint32_t stroke_color, int fill_mode, int blend_type, CFX_SkiaDeviceDriver* pDriver) { if (m_debugDisable) return false; if (m_commandIndex < m_commands.count()) FlushCommands(pDriver); if (m_drawText) FlushText(pDriver); if (m_drawPath && DrawChanged(pMatrix, pDrawState, fill_color, stroke_color, fill_mode, blend_type)) { FlushPath(pDriver); } if (!m_drawPath) { m_skPath.reset(); m_fillPath = (fill_mode & 3) && fill_color; m_skPath.setFillType((fill_mode & 3) == FXFILL_ALTERNATE ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType); if (pDrawState) m_drawState.Copy(*pDrawState); m_fillColor = fill_color; m_strokeColor = stroke_color; m_blendType = blend_type; if (pMatrix) m_drawMatrix = *pMatrix; } SkPath skPath = BuildPath(pPathData); SkPoint delta; if (MatrixOffset(pMatrix, &delta)) skPath.offset(delta.fX, delta.fY); m_skPath.addPath(skPath); m_drawPath = true; return true; } void FlushPath(CFX_SkiaDeviceDriver* pDriver) { SkMatrix skMatrix = ToSkMatrix(m_drawMatrix); SkPaint skPaint; skPaint.setAntiAlias(true); int stroke_alpha = FXARGB_A(m_strokeColor); if (stroke_alpha) pDriver->PaintStroke(&skPaint, &m_drawState, skMatrix); SkCanvas* skCanvas = pDriver->SkiaCanvas(); skCanvas->save(); skCanvas->concat(skMatrix); if (m_fillPath) { SkPath strokePath; const SkPath* fillPath = &m_skPath; if (stroke_alpha) { SkAlpha fillA = SkColorGetA(m_fillColor); SkAlpha strokeA = SkColorGetA(m_strokeColor); if (fillA && fillA < 0xFF && strokeA && strokeA < 0xFF) { skPaint.getFillPath(m_skPath, &strokePath); if (Op(m_skPath, strokePath, SkPathOp::kDifference_SkPathOp, &strokePath)) { fillPath = &strokePath; } } } skPaint.setStyle(SkPaint::kFill_Style); skPaint.setColor(m_fillColor); skCanvas->drawPath(*fillPath, skPaint); } if (stroke_alpha) { DebugShowSkiaPath(m_skPath); DebugShowCanvasMatrix(skCanvas); skPaint.setStyle(SkPaint::kStroke_Style); skPaint.setColor(m_strokeColor); skCanvas->drawPath(m_skPath, skPaint); } skCanvas->restore(); m_drawPath = false; } bool DrawText(int nChars, const FXTEXT_CHARPOS* pCharPos, CFX_Font* pFont, CFX_FontCache* pCache, const CFX_Matrix* pMatrix, FX_FLOAT font_size, uint32_t color, CFX_SkiaDeviceDriver* pDriver) { if (m_debugDisable) return false; if (m_commandIndex < m_commands.count()) FlushCommands(pDriver); if (m_drawPath) FlushPath(pDriver); if (m_drawText && FontChanged(pFont, pCache, pMatrix, font_size, color)) FlushText(pDriver); if (!m_drawText) { m_positions.setCount(0); m_glyphs.setCount(0); m_pFont = pFont; m_pCache = pCache; m_fontSize = font_size; m_fillColor = color; m_drawMatrix = *pMatrix; } int count = m_positions.count(); m_positions.setCount(nChars + count); m_glyphs.setCount(nChars + count); SkScalar flip = m_fontSize < 0 ? -1 : 1; for (int index = 0; index < nChars; ++index) { const FXTEXT_CHARPOS& cp = pCharPos[index]; m_positions[index + count] = {cp.m_OriginX * flip, cp.m_OriginY * flip}; m_glyphs[index + count] = (uint16_t)cp.m_GlyphIndex; } SkPoint delta; if (MatrixOffset(pMatrix, &delta)) { for (int index = 0; index < nChars; ++index) m_positions[index + count].offset(delta.fX * flip, -delta.fY * flip); } m_drawText = true; return true; } void FlushText(CFX_SkiaDeviceDriver* pDriver) { SkScalar flip = m_fontSize < 0 ? -1 : 1; SkMatrix skMatrix = ToFlippedSkMatrix(m_drawMatrix, flip); SkPaint skPaint; skPaint.setAntiAlias(true); skPaint.setColor(m_fillColor); if (m_pFont->GetFace() && m_pCache) { // exclude placeholder test fonts sk_sp typeface(SkSafeRef(m_pCache->GetDeviceCache(m_pFont))); skPaint.setTypeface(typeface); } skPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); skPaint.setTextSize(m_fontSize); skPaint.setSubpixelText(true); skPaint.setHinting(SkPaint::kNo_Hinting); SkCanvas* skCanvas = pDriver->SkiaCanvas(); skCanvas->save(); skCanvas->concat(skMatrix); skCanvas->drawPosText(m_glyphs.begin(), m_glyphs.count() * 2, m_positions.begin(), skPaint); skCanvas->restore(); m_drawText = false; } bool SetClipFill(const CFX_PathData* pPathData, const CFX_Matrix* pMatrix, int fill_mode, CFX_SkiaDeviceDriver* pDriver) { if (m_debugDisable) return false; SkPath skClipPath = BuildPath(pPathData); skClipPath.setFillType((fill_mode & 3) == FXFILL_ALTERNATE ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType); SkMatrix skMatrix = ToSkMatrix(*pMatrix); skClipPath.transform(skMatrix); return SetClip(skClipPath, pDriver); } bool SetClip(const SkPath& skClipPath, CFX_SkiaDeviceDriver* pDriver) { if (m_commandIndex < m_commands.count()) { if (m_commands[m_commandIndex] == Clip::kPath && m_clips[m_commandIndex] == skClipPath) { ++m_commandIndex; return true; } FlushCommands(pDriver); } Flush(pDriver); m_commands.push(Clip::kPath); ++m_commandIndex; m_clips.push_back(skClipPath); return false; } bool SetClipStroke(const CFX_PathData* pPathData, const CFX_Matrix* pMatrix, const CFX_GraphStateData* pGraphState, CFX_SkiaDeviceDriver* pDriver) { if (m_debugDisable) return false; SkPath skPath = BuildPath(pPathData); SkMatrix skMatrix = ToSkMatrix(*pMatrix); SkPaint skPaint; pDriver->PaintStroke(&skPaint, pGraphState, skMatrix); SkPath dst_path; skPaint.getFillPath(skPath, &dst_path); dst_path.transform(skMatrix); return SetClip(dst_path, pDriver); } bool MatrixOffset(const CFX_Matrix* pMatrix, SkPoint* delta) { CFX_Matrix identityMatrix; if (!pMatrix) pMatrix = &identityMatrix; delta->set(pMatrix->e - m_drawMatrix.e, pMatrix->f - m_drawMatrix.f); if (!delta->fX && !delta->fY) return true; SkMatrix drawMatrix = ToSkMatrix(m_drawMatrix); if (!(drawMatrix.getType() & ~SkMatrix::kTranslate_Mask)) return true; SkMatrix invDrawMatrix; if (!drawMatrix.invert(&invDrawMatrix)) return false; SkMatrix invNewMatrix; SkMatrix newMatrix = ToSkMatrix(*pMatrix); if (!newMatrix.invert(&invNewMatrix)) return false; delta->set(invDrawMatrix.getTranslateX() - invNewMatrix.getTranslateX(), invDrawMatrix.getTranslateY() - invNewMatrix.getTranslateY()); return true; } void FlushCommands(CFX_SkiaDeviceDriver* pDriver) { if (m_commandIndex == m_commands.count()) return; if (m_commandIndex < m_commands.count()) pDriver->SkiaCanvas()->restore(); int index = m_commands.count() - 1; if (m_commandIndex == index && m_commands[index] == Clip::kSave) return; for (; index > m_commandIndex; --index) { if (m_commands[index] == Clip::kSave) pDriver->SkiaCanvas()->restore(); } if (m_commandIndex > 0) pDriver->SkiaCanvas()->save(); while (index > 0 && m_commands[index] != Clip::kSave) --index; while (++index < m_commandIndex) { SkASSERT(m_commands[index] == Clip::kPath); pDriver->SkiaCanvas()->clipPath(m_clips[index], SkRegion::kIntersect_Op, true); } m_commands.setCount(m_commandIndex); m_clips.resize_back(m_commandIndex); } // returns true if caller should apply command to skia canvas bool ClipSave(CFX_SkiaDeviceDriver* pDriver) { if (m_debugDisable) return false; int count = m_commands.count(); if (m_commandIndex < count) { if (m_commands[m_commandIndex] == Clip::kSave) { ++m_commandIndex; return true; } FlushCommands(pDriver); } Flush(pDriver); m_commands.push(Clip::kSave); ++m_commandIndex; m_clips.push_back(m_skEmptyPath); return false; } bool ClipRestore(CFX_SkiaDeviceDriver* pDriver) { if (m_debugDisable) return false; while (m_commandIndex > 0) { if (m_commands[--m_commandIndex] == Clip::kSave) return true; } Flush(pDriver); return false; } bool DrawChanged(const CFX_Matrix* pMatrix, const CFX_GraphStateData* pState, uint32_t fill_color, uint32_t stroke_color, int fill_mode, int blend_type) { return MatrixChanged(pMatrix, m_drawMatrix) || StateChanged(pState, m_drawState) || fill_color != m_fillColor || stroke_color != m_strokeColor || ((fill_mode & 3) == FXFILL_ALTERNATE) != (m_skPath.getFillType() == SkPath::kEvenOdd_FillType) || blend_type != m_blendType; } bool FontChanged(CFX_Font* pFont, CFX_FontCache* pCache, const CFX_Matrix* pMatrix, FX_FLOAT font_size, uint32_t color) { return pFont != m_pFont || pCache != m_pCache || MatrixChanged(pMatrix, m_drawMatrix) || font_size != m_fontSize || color != m_fillColor; } bool MatrixChanged(const CFX_Matrix* pMatrix, const CFX_Matrix& refMatrix) { CFX_Matrix identityMatrix; if (!pMatrix) pMatrix = &identityMatrix; return pMatrix->a != refMatrix.a || pMatrix->b != refMatrix.b || pMatrix->c != refMatrix.c || pMatrix->d != refMatrix.d; } bool StateChanged(const CFX_GraphStateData* pState, const CFX_GraphStateData& refState) { CFX_GraphStateData identityState; if (!pState) pState = &identityState; return pState->m_LineWidth != refState.m_LineWidth || pState->m_LineCap != refState.m_LineCap || pState->m_LineJoin != refState.m_LineJoin || pState->m_MiterLimit != refState.m_MiterLimit || DashChanged(pState, refState); } bool DashChanged(const CFX_GraphStateData* pState, const CFX_GraphStateData& refState) { bool dashArray = pState && pState->m_DashArray; if (!dashArray && !refState.m_DashArray) return false; if (!dashArray || !refState.m_DashArray) return true; if (pState->m_DashPhase != refState.m_DashPhase || pState->m_DashCount != refState.m_DashCount) { return true; } for (int index = 0; index < pState->m_DashCount; ++index) { if (pState->m_DashArray[index] != refState.m_DashArray[index]) return false; } return true; } void Flush(CFX_SkiaDeviceDriver* pDriver) { if (m_drawPath) FlushPath(pDriver); if (m_drawText) FlushText(pDriver); } #ifdef SK_DEBUG void Dump(const CFX_SkiaDeviceDriver* pDriver) const { SkDebugf("\n\nSkia Save Count %d:\n", pDriver->m_pCanvas->getSaveCount()); pDriver->m_pCanvas->getClipStack()->dump(); SkDebugf("Cache:\n"); for (int index = 0; index < m_commands.count(); ++index) { SkDebugf("%s ", m_commandIndex == index ? "-->" : " "); switch (m_commands[index]) { case Clip::kSave: SkDebugf("Save\n"); break; case Clip::kPath: m_clips[index].dump(); break; default: SkDebugf("unknown\n"); } } if (m_commandIndex == m_commands.count()) SkDebugf("-->\n"); } #endif private: SkTArray m_clips; // stack of clips that may be reused SkTDArray m_commands; // stack of clip-related commands SkTDArray m_positions; // accumulator for text positions SkTDArray m_glyphs; // accumulator for text glyphs SkPath m_skPath; // accumulator for path contours SkPath m_skEmptyPath; // used as placehold in the clips array CFX_Matrix m_drawMatrix; CFX_GraphStateData m_clipState; CFX_GraphStateData m_drawState; CFX_Matrix m_clipMatrix; CFX_Font* m_pFont; CFX_FontCache* m_pCache; FX_FLOAT m_fontSize; uint32_t m_fillColor; uint32_t m_strokeColor; int m_blendType; int m_commandIndex; // active position in clip command stack bool m_drawText; bool m_drawPath; bool m_fillPath; bool m_debugDisable; // turn off cache for debugging }; // convert a stroking path to scanlines void CFX_SkiaDeviceDriver::PaintStroke(SkPaint* spaint, const CFX_GraphStateData* pGraphState, const SkMatrix& matrix) { SkPaint::Cap cap; switch (pGraphState->m_LineCap) { case CFX_GraphStateData::LineCapRound: cap = SkPaint::kRound_Cap; break; case CFX_GraphStateData::LineCapSquare: cap = SkPaint::kSquare_Cap; break; default: cap = SkPaint::kButt_Cap; break; } SkPaint::Join join; switch (pGraphState->m_LineJoin) { case CFX_GraphStateData::LineJoinRound: join = SkPaint::kRound_Join; break; case CFX_GraphStateData::LineJoinBevel: join = SkPaint::kBevel_Join; break; default: join = SkPaint::kMiter_Join; break; } SkMatrix inverse; if (!matrix.invert(&inverse)) return; // give up if the matrix is degenerate, and not invertable inverse.set(SkMatrix::kMTransX, 0); inverse.set(SkMatrix::kMTransY, 0); SkVector deviceUnits[2] = {{0, 1}, {1, 0}}; inverse.mapPoints(deviceUnits, SK_ARRAY_COUNT(deviceUnits)); FX_FLOAT width = SkTMax(pGraphState->m_LineWidth, SkTMin(deviceUnits[0].length(), deviceUnits[1].length())); if (pGraphState->m_DashArray) { int count = (pGraphState->m_DashCount + 1) / 2; SkScalar* intervals = FX_Alloc2D(SkScalar, count, sizeof(SkScalar)); // Set dash pattern for (int i = 0; i < count; i++) { FX_FLOAT on = pGraphState->m_DashArray[i * 2]; if (on <= 0.000001f) on = 1.f / 10; FX_FLOAT off = i * 2 + 1 == pGraphState->m_DashCount ? on : pGraphState->m_DashArray[i * 2 + 1]; if (off < 0) off = 0; intervals[i * 2] = on; intervals[i * 2 + 1] = off; } spaint->setPathEffect( SkDashPathEffect::Make(intervals, count * 2, pGraphState->m_DashPhase)); } spaint->setStyle(SkPaint::kStroke_Style); spaint->setAntiAlias(true); spaint->setStrokeWidth(width); spaint->setStrokeMiter(pGraphState->m_MiterLimit); spaint->setStrokeCap(cap); spaint->setStrokeJoin(join); } CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(CFX_DIBitmap* pBitmap, FX_BOOL bRgbByteOrder, CFX_DIBitmap* pOriDevice, FX_BOOL bGroupKnockout) : m_pBitmap(pBitmap), m_pOriDevice(pOriDevice), m_pRecorder(nullptr), m_pCache(new SkiaState), m_bGroupKnockout(bGroupKnockout) { SkBitmap skBitmap; SkASSERT(pBitmap->GetBPP() == 8 || pBitmap->GetBPP() == 32); SkImageInfo imageInfo = SkImageInfo::Make( pBitmap->GetWidth(), pBitmap->GetHeight(), pBitmap->GetBPP() == 8 ? kAlpha_8_SkColorType : kN32_SkColorType, kOpaque_SkAlphaType); skBitmap.installPixels(imageInfo, pBitmap->GetBuffer(), pBitmap->GetPitch(), nullptr, /* to do : set color table */ nullptr, nullptr); m_pCanvas = new SkCanvas(skBitmap); if (m_bGroupKnockout) SkDebugf(""); // FIXME(caryclark) suppress 'm_bGroupKnockout is unused' } CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(int size_x, int size_y) : m_pBitmap(nullptr), m_pOriDevice(nullptr), m_pRecorder(new SkPictureRecorder), m_pCache(new SkiaState), m_bGroupKnockout(FALSE) { m_pRecorder->beginRecording(SkIntToScalar(size_x), SkIntToScalar(size_y)); m_pCanvas = m_pRecorder->getRecordingCanvas(); } CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(SkPictureRecorder* recorder) : m_pBitmap(nullptr), m_pOriDevice(nullptr), m_pRecorder(recorder), m_pCache(new SkiaState), m_bGroupKnockout(FALSE) { m_pCanvas = m_pRecorder->getRecordingCanvas(); } CFX_SkiaDeviceDriver::~CFX_SkiaDeviceDriver() { Flush(); if (!m_pRecorder) delete m_pCanvas; } void CFX_SkiaDeviceDriver::Flush() { m_pCache->Flush(this); m_pCache->FlushCommands(this); } FX_BOOL CFX_SkiaDeviceDriver::DrawDeviceText(int nChars, const FXTEXT_CHARPOS* pCharPos, CFX_Font* pFont, CFX_FontCache* pCache, const CFX_Matrix* pObject2Device, FX_FLOAT font_size, uint32_t color) { if (!pCache) pCache = CFX_GEModule::Get()->GetFontCache(); if (m_pCache->DrawText(nChars, pCharPos, pFont, pCache, pObject2Device, font_size, color, this)) { return TRUE; } sk_sp typeface( SkSafeRef(pCache ? pCache->GetDeviceCache(pFont) : nullptr)); SkPaint paint; paint.setAntiAlias(true); paint.setColor(color); paint.setTypeface(typeface); paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setHinting(SkPaint::kNo_Hinting); paint.setTextSize(font_size); paint.setSubpixelText(true); m_pCanvas->save(); SkScalar flip = font_size < 0 ? -1 : 1; SkMatrix skMatrix = ToFlippedSkMatrix(*pObject2Device, flip); m_pCanvas->concat(skMatrix); SkTDArray positions; positions.setCount(nChars); SkTDArray glyphs; glyphs.setCount(nChars); for (int index = 0; index < nChars; ++index) { const FXTEXT_CHARPOS& cp = pCharPos[index]; positions[index] = {cp.m_OriginX * flip, cp.m_OriginY * flip}; glyphs[index] = (uint16_t)cp.m_GlyphIndex; } m_pCanvas->drawPosText(glyphs.begin(), nChars * 2, positions.begin(), paint); m_pCanvas->restore(); return TRUE; } int CFX_SkiaDeviceDriver::GetDeviceCaps(int caps_id) const { switch (caps_id) { case FXDC_DEVICE_CLASS: return FXDC_DISPLAY; case FXDC_PIXEL_WIDTH: return m_pCanvas->imageInfo().width(); case FXDC_PIXEL_HEIGHT: return m_pCanvas->imageInfo().height(); case FXDC_BITS_PIXEL: return 32; case FXDC_HORZ_SIZE: case FXDC_VERT_SIZE: return 0; case FXDC_RENDER_CAPS: return FXRC_GET_BITS | FXRC_ALPHA_PATH | FXRC_ALPHA_IMAGE | FXRC_BLEND_MODE | FXRC_SOFT_CLIP | FXRC_ALPHA_OUTPUT | FXRC_FILLSTROKE_PATH | FXRC_SHADING; } return 0; } void CFX_SkiaDeviceDriver::SaveState() { if (!m_pCache->ClipSave(this)) m_pCanvas->save(); } void CFX_SkiaDeviceDriver::RestoreState(bool bKeepSaved) { if (!m_pCache->ClipRestore(this)) m_pCanvas->restore(); if (bKeepSaved) SaveState(); } FX_BOOL CFX_SkiaDeviceDriver::SetClip_PathFill( const CFX_PathData* pPathData, // path info const CFX_Matrix* pObject2Device, // flips object's y-axis int fill_mode // fill mode, WINDING or ALTERNATE ) { CFX_Matrix identity; const CFX_Matrix* deviceMatrix = pObject2Device ? pObject2Device : &identity; if (m_pCache->SetClipFill(pPathData, deviceMatrix, fill_mode, this)) return TRUE; if (pPathData->GetPointCount() == 5 || pPathData->GetPointCount() == 4) { CFX_FloatRect rectf; if (pPathData->IsRect(deviceMatrix, &rectf)) { rectf.Intersect( CFX_FloatRect(0, 0, (FX_FLOAT)GetDeviceCaps(FXDC_PIXEL_WIDTH), (FX_FLOAT)GetDeviceCaps(FXDC_PIXEL_HEIGHT))); // note that PDF's y-axis goes up; Skia's y-axis goes down SkRect skClipRect = SkRect::MakeLTRB(rectf.left, rectf.bottom, rectf.right, rectf.top); DebugDrawSkiaClipRect(m_pCanvas, skClipRect); m_pCanvas->clipRect(skClipRect, SkRegion::kIntersect_Op, true); return TRUE; } } SkPath skClipPath = BuildPath(pPathData); skClipPath.setFillType((fill_mode & 3) == FXFILL_ALTERNATE ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType); SkMatrix skMatrix = ToSkMatrix(*deviceMatrix); skClipPath.transform(skMatrix); DebugShowSkiaPath(skClipPath); DebugDrawSkiaClipPath(m_pCanvas, skClipPath); m_pCanvas->clipPath(skClipPath, SkRegion::kIntersect_Op, true); return TRUE; } FX_BOOL CFX_SkiaDeviceDriver::SetClip_PathStroke( const CFX_PathData* pPathData, // path info const CFX_Matrix* pObject2Device, // optional transformation const CFX_GraphStateData* pGraphState // graphic state, for pen attributes ) { if (m_pCache->SetClipStroke(pPathData, pObject2Device, pGraphState, this)) return TRUE; // build path data SkPath skPath = BuildPath(pPathData); SkMatrix skMatrix = ToSkMatrix(*pObject2Device); SkPaint skPaint; PaintStroke(&skPaint, pGraphState, skMatrix); SkPath dst_path; skPaint.getFillPath(skPath, &dst_path); dst_path.transform(skMatrix); DebugDrawSkiaClipPath(m_pCanvas, dst_path); m_pCanvas->clipPath(dst_path, SkRegion::kIntersect_Op, true); return TRUE; } FX_BOOL CFX_SkiaDeviceDriver::DrawPath( const CFX_PathData* pPathData, // path info const CFX_Matrix* pObject2Device, // optional transformation const CFX_GraphStateData* pGraphState, // graphic state, for pen attributes uint32_t fill_color, // fill color uint32_t stroke_color, // stroke color int fill_mode, // fill mode, WINDING or ALTERNATE. 0 for not filled int blend_type) { if (m_pCache->DrawPath(pPathData, pObject2Device, pGraphState, fill_color, stroke_color, fill_mode, blend_type, this)) { return TRUE; } SkIRect rect; rect.set(0, 0, GetDeviceCaps(FXDC_PIXEL_WIDTH), GetDeviceCaps(FXDC_PIXEL_HEIGHT)); SkMatrix skMatrix; if (pObject2Device) skMatrix = ToSkMatrix(*pObject2Device); else skMatrix.setIdentity(); SkPaint skPaint; skPaint.setAntiAlias(true); int stroke_alpha = FXARGB_A(stroke_color); if (pGraphState && stroke_alpha) PaintStroke(&skPaint, pGraphState, skMatrix); SkPath skPath = BuildPath(pPathData); m_pCanvas->save(); m_pCanvas->concat(skMatrix); if ((fill_mode & 3) && fill_color) { skPath.setFillType((fill_mode & 3) == FXFILL_ALTERNATE ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType); SkPath strokePath; const SkPath* fillPath = &skPath; if (pGraphState && stroke_alpha) { SkAlpha fillA = SkColorGetA(fill_color); SkAlpha strokeA = SkColorGetA(stroke_color); if (fillA && fillA < 0xFF && strokeA && strokeA < 0xFF) { skPaint.getFillPath(skPath, &strokePath); if (Op(skPath, strokePath, SkPathOp::kDifference_SkPathOp, &strokePath)) { fillPath = &strokePath; } } } skPaint.setStyle(SkPaint::kFill_Style); skPaint.setColor(fill_color); m_pCanvas->drawPath(*fillPath, skPaint); } if (pGraphState && stroke_alpha) { DebugShowSkiaPath(skPath); DebugShowCanvasMatrix(m_pCanvas); skPaint.setStyle(SkPaint::kStroke_Style); skPaint.setColor(stroke_color); m_pCanvas->drawPath(skPath, skPaint); } m_pCanvas->restore(); return TRUE; } FX_BOOL CFX_SkiaDeviceDriver::DrawCosmeticLine(FX_FLOAT x1, FX_FLOAT y1, FX_FLOAT x2, FX_FLOAT y2, uint32_t color, int blend_type) { return FALSE; } FX_BOOL CFX_SkiaDeviceDriver::FillRectWithBlend(const FX_RECT* pRect, uint32_t fill_color, int blend_type) { SkPaint spaint; spaint.setAntiAlias(true); spaint.setColor(fill_color); spaint.setXfermodeMode(GetSkiaBlendMode(blend_type)); m_pCanvas->drawRect( SkRect::MakeLTRB(pRect->left, pRect->top, pRect->right, pRect->bottom), spaint); return TRUE; } FX_BOOL CFX_SkiaDeviceDriver::DrawShading(const CPDF_ShadingPattern* pPattern, const CFX_Matrix* pMatrix, const FX_RECT& clip_rect, int alpha, FX_BOOL bAlphaMode) { if (kAxialShading != pPattern->GetShadingType() && kRadialShading != pPattern->GetShadingType()) { // TODO(caryclark) more types return false; } const std::vector>& pFuncs = pPattern->GetFuncs(); int nFuncs = pFuncs.size(); if (nFuncs != 1) // TODO(caryclark) remove this restriction return false; CPDF_Dictionary* pDict = pPattern->GetShadingObject()->GetDict(); CPDF_Array* pCoords = pDict->GetArrayFor("Coords"); if (!pCoords) return true; // 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++) { if (!pFuncs[j]) continue; if (const CPDF_SampledFunc* pSampledFunc = pFuncs[j]->ToSampledFunc()) { /* 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(pSampledFunc, &skColors, &skPos)) return false; } else if (const CPDF_ExpIntFunc* pExpIntFuc = pFuncs[j]->ToExpIntFunc()) { if (!AddColors(pExpIntFuc, &skColors)) return false; skPos.push(0); skPos.push(1); } else if (const CPDF_StitchFunc* pStitchFunc = pFuncs[j]->ToStitchFunc()) { if (!AddStitching(pStitchFunc, &skColors, &skPos)) return false; } else { return false; } } CPDF_Array* pArray = pDict->GetArrayFor("Extend"); bool clipStart = !pArray || !pArray->GetIntegerAt(0); bool clipEnd = !pArray || !pArray->GetIntegerAt(1); SkPaint paint; paint.setAntiAlias(true); 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->GetShadingType()) { 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->GetShadingType()); 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; if (!skMatrix.invert(&inverse)) return false; skPath.addRect(skRect); skPath.transform(inverse); } m_pCanvas->save(); if (!skClip.isEmpty()) m_pCanvas->clipPath(skClip, SkRegion::kIntersect_Op, true); m_pCanvas->concat(skMatrix); m_pCanvas->drawPath(skPath, paint); m_pCanvas->restore(); return true; } uint8_t* CFX_SkiaDeviceDriver::GetBuffer() const { return m_pBitmap->GetBuffer(); } FX_BOOL CFX_SkiaDeviceDriver::GetClipBox(FX_RECT* pRect) { // TODO(caryclark) call m_canvas->getClipDeviceBounds() instead pRect->left = 0; pRect->top = 0; const SkImageInfo& canvasSize = m_pCanvas->imageInfo(); pRect->right = canvasSize.width(); pRect->bottom = canvasSize.height(); return TRUE; } FX_BOOL CFX_SkiaDeviceDriver::GetDIBits(CFX_DIBitmap* pBitmap, int left, int top) { if (!m_pBitmap) return TRUE; uint8_t* srcBuffer = m_pBitmap->GetBuffer(); if (!srcBuffer) return TRUE; int srcWidth = m_pBitmap->GetWidth(); int srcHeight = m_pBitmap->GetHeight(); int srcRowBytes = srcWidth * sizeof(uint32_t); SkImageInfo srcImageInfo = SkImageInfo::Make( srcWidth, srcHeight, SkColorType::kN32_SkColorType, kPremul_SkAlphaType); SkBitmap skSrcBitmap; skSrcBitmap.installPixels(srcImageInfo, srcBuffer, srcRowBytes, nullptr, nullptr, nullptr); SkASSERT(pBitmap); uint8_t* dstBuffer = pBitmap->GetBuffer(); SkASSERT(dstBuffer); int dstWidth = pBitmap->GetWidth(); int dstHeight = pBitmap->GetHeight(); int dstRowBytes = dstWidth * sizeof(uint32_t); SkImageInfo dstImageInfo = SkImageInfo::Make( dstWidth, dstHeight, SkColorType::kN32_SkColorType, kPremul_SkAlphaType); SkBitmap skDstBitmap; skDstBitmap.installPixels(dstImageInfo, dstBuffer, dstRowBytes, nullptr, nullptr, nullptr); SkCanvas canvas(skDstBitmap); canvas.drawBitmap(skSrcBitmap, left, top, nullptr); return TRUE; } CFX_DIBitmap* CFX_SkiaDeviceDriver::GetBackDrop() { return m_pOriDevice; } FX_BOOL CFX_SkiaDeviceDriver::SetDIBits(const CFX_DIBSource* pBitmap, uint32_t argb, const FX_RECT* pSrcRect, int left, int top, int blend_type) { if (!m_pBitmap || !m_pBitmap->GetBuffer()) return TRUE; CFX_Matrix m(pBitmap->GetWidth(), 0, 0, -pBitmap->GetHeight(), left, top + pBitmap->GetHeight()); void* dummy; return StartDIBits(pBitmap, 0xFF, argb, &m, 0, dummy, blend_type); } FX_BOOL CFX_SkiaDeviceDriver::StretchDIBits(const CFX_DIBSource* pSource, uint32_t argb, int dest_left, int dest_top, int dest_width, int dest_height, const FX_RECT* pClipRect, uint32_t flags, int blend_type) { if (!m_pBitmap->GetBuffer()) return TRUE; CFX_Matrix m(dest_width, 0, 0, -dest_height, dest_left, dest_top + dest_height); m_pCanvas->save(); SkRect skClipRect = SkRect::MakeLTRB(pClipRect->left, pClipRect->bottom, pClipRect->right, pClipRect->top); m_pCanvas->clipRect(skClipRect, SkRegion::kIntersect_Op, true); void* dummy; FX_BOOL result = StartDIBits(pSource, 0xFF, argb, &m, 0, dummy, blend_type); m_pCanvas->restore(); return result; } FX_BOOL CFX_SkiaDeviceDriver::StartDIBits(const CFX_DIBSource* pSource, int bitmap_alpha, uint32_t argb, const CFX_Matrix* pMatrix, uint32_t render_flags, void*& handle, int blend_type) { DebugValidate(m_pBitmap, m_pOriDevice); SkColorTable* ct = nullptr; std::unique_ptr dst8Storage; std::unique_ptr dst32Storage; SkBitmap skBitmap; int width, height; if (!Upsample(pSource, dst8Storage, dst32Storage, &ct, &skBitmap, &width, &height, false)) { return FALSE; } m_pCanvas->save(); SkMatrix skMatrix; SetBitmapMatrix(pMatrix, width, height, &skMatrix); m_pCanvas->concat(skMatrix); SkPaint paint; SetBitmapPaint(pSource->IsAlphaMask(), argb, bitmap_alpha, blend_type, &paint); // TODO(caryclark) Once Skia supports 8 bit src to 8 bit dst remove this if (m_pBitmap && m_pBitmap->GetBPP() == 8 && pSource->GetBPP() == 8) { SkMatrix inv; SkAssertResult(skMatrix.invert(&inv)); for (int y = 0; y < m_pBitmap->GetHeight(); ++y) { for (int x = 0; x < m_pBitmap->GetWidth(); ++x) { SkPoint src = {x + 0.5f, y + 0.5f}; inv.mapPoints(&src, 1); // TODO(caryclark) Why does the matrix map require clamping? src.fX = SkTMax(0.5f, SkTMin(src.fX, width - 0.5f)); src.fY = SkTMax(0.5f, SkTMin(src.fY, height - 0.5f)); m_pBitmap->SetPixel(x, y, skBitmap.getColor(src.fX, src.fY)); } } } else { m_pCanvas->drawBitmap(skBitmap, 0, 0, &paint); } m_pCanvas->restore(); if (ct) ct->unref(); DebugValidate(m_pBitmap, m_pOriDevice); return TRUE; } FX_BOOL CFX_SkiaDeviceDriver::ContinueDIBits(void* handle, IFX_Pause* pPause) { return FALSE; } void CFX_SkiaDeviceDriver::PreMultiply(CFX_DIBitmap* pDIBitmap) { void* buffer = pDIBitmap->GetBuffer(); if (!buffer) return; if (pDIBitmap->GetBPP() != 32) { return; } int height = pDIBitmap->GetHeight(); int width = pDIBitmap->GetWidth(); int rowBytes = pDIBitmap->GetPitch(); SkImageInfo unpremultipliedInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kUnpremul_SkAlphaType); SkPixmap unpremultiplied(unpremultipliedInfo, buffer, rowBytes); SkImageInfo premultipliedInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType); SkPixmap premultiplied(premultipliedInfo, buffer, rowBytes); unpremultiplied.readPixels(premultiplied); pDIBitmap->DebugVerifyBitmapIsPreMultiplied(); } bool CFX_SkiaDeviceDriver::DrawBitsWithMask(const CFX_DIBSource* pSource, const CFX_DIBSource* pMask, int bitmap_alpha, const CFX_Matrix* pMatrix, int blend_type) { DebugValidate(m_pBitmap, m_pOriDevice); SkColorTable* srcCt = nullptr; SkColorTable* maskCt = nullptr; std::unique_ptr src8Storage, mask8Storage; std::unique_ptr src32Storage, mask32Storage; SkBitmap skBitmap, skMask; int srcWidth, srcHeight, maskWidth, maskHeight; if (!Upsample(pSource, src8Storage, src32Storage, &srcCt, &skBitmap, &srcWidth, &srcHeight, false)) { return false; } if (!Upsample(pMask, mask8Storage, mask32Storage, &maskCt, &skMask, &maskWidth, &maskHeight, true)) { return false; } m_pCanvas->save(); SkMatrix skMatrix; SetBitmapMatrix(pMatrix, srcWidth, srcHeight, &skMatrix); m_pCanvas->concat(skMatrix); SkPaint paint; SetBitmapPaint(pSource->IsAlphaMask(), 0xFFFFFFFF, bitmap_alpha, blend_type, &paint); sk_sp skSrc = SkImage::MakeFromBitmap(skBitmap); sk_sp skSrcShader = skSrc->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode); sk_sp skMaskImage = SkImage::MakeFromBitmap(skMask); sk_sp skMaskShader = skMaskImage->makeShader( SkShader::kClamp_TileMode, SkShader::kClamp_TileMode); sk_sp dstInMode = SkXfermode::Make(SkXfermode::kSrcIn_Mode); paint.setShader( SkShader::MakeComposeShader(skMaskShader, skSrcShader, dstInMode)); SkRect r = {0, 0, SkIntToScalar(srcWidth), SkIntToScalar(srcHeight)}; m_pCanvas->drawRect(r, paint); m_pCanvas->restore(); if (srcCt) srcCt->unref(); DebugValidate(m_pBitmap, m_pOriDevice); return true; } bool CFX_SkiaDeviceDriver::SetBitsWithMask(const CFX_DIBSource* pBitmap, const CFX_DIBSource* pMask, int dest_left, int dest_top, int bitmap_alpha, int blend_type) { if (!m_pBitmap || !m_pBitmap->GetBuffer()) return true; CFX_Matrix m(pBitmap->GetWidth(), 0, 0, -pBitmap->GetHeight(), dest_left, dest_top + pBitmap->GetHeight()); return DrawBitsWithMask(pBitmap, pMask, bitmap_alpha, &m, blend_type); } void CFX_SkiaDeviceDriver::Clear(uint32_t color) { m_pCanvas->clear(color); } void CFX_SkiaDeviceDriver::Dump() const { #ifdef SK_DEBUG if (m_pCache) m_pCache->Dump(this); #endif } void CFX_SkiaDeviceDriver::DebugVerifyBitmapIsPreMultiplied() const { if (m_pOriDevice) m_pOriDevice->DebugVerifyBitmapIsPreMultiplied(); } CFX_FxgeDevice::CFX_FxgeDevice() { m_bOwnedBitmap = FALSE; } void CFX_FxgeDevice::Clear(uint32_t color) { CFX_SkiaDeviceDriver* skDriver = static_cast(GetDeviceDriver()); skDriver->Clear(color); } SkPictureRecorder* CFX_FxgeDevice::CreateRecorder(int size_x, int size_y) { CFX_SkiaDeviceDriver* skDriver = new CFX_SkiaDeviceDriver(size_x, size_y); SetDeviceDriver(WrapUnique(skDriver)); return skDriver->GetRecorder(); } bool CFX_FxgeDevice::Attach(CFX_DIBitmap* pBitmap, bool bRgbByteOrder, CFX_DIBitmap* pOriDevice, bool bGroupKnockout) { if (!pBitmap) return false; SetBitmap(pBitmap); SetDeviceDriver(WrapUnique(new CFX_SkiaDeviceDriver( pBitmap, bRgbByteOrder, pOriDevice, bGroupKnockout))); return true; } bool CFX_FxgeDevice::AttachRecorder(SkPictureRecorder* recorder) { if (!recorder) return false; SetDeviceDriver(WrapUnique(new CFX_SkiaDeviceDriver(recorder))); return true; } bool CFX_FxgeDevice::Create(int width, int height, FXDIB_Format format, CFX_DIBitmap* pOriDevice) { m_bOwnedBitmap = TRUE; CFX_DIBitmap* pBitmap = new CFX_DIBitmap; if (!pBitmap->Create(width, height, format)) { delete pBitmap; return false; } SetBitmap(pBitmap); SetDeviceDriver( WrapUnique(new CFX_SkiaDeviceDriver(pBitmap, FALSE, pOriDevice, FALSE))); return true; } CFX_FxgeDevice::~CFX_FxgeDevice() { Flush(); // call destructor of CFX_RenderDevice / CFX_SkiaDeviceDriver immediately if (m_bOwnedBitmap && GetBitmap()) delete GetBitmap(); } void CFX_FxgeDevice::DebugVerifyBitmapIsPreMultiplied() const { #ifdef SK_DEBUG CFX_SkiaDeviceDriver* skDriver = static_cast(GetDeviceDriver()); if (skDriver) skDriver->DebugVerifyBitmapIsPreMultiplied(); #endif } bool CFX_FxgeDevice::SetBitsWithMask(const CFX_DIBSource* pBitmap, const CFX_DIBSource* pMask, int left, int top, int bitmap_alpha, int blend_type) { CFX_SkiaDeviceDriver* skDriver = static_cast(GetDeviceDriver()); if (skDriver) return skDriver->SetBitsWithMask(pBitmap, pMask, left, top, bitmap_alpha, blend_type); return false; } void CFX_DIBSource::DebugVerifyBitmapIsPreMultiplied(void* opt) const { #ifdef SK_DEBUG SkASSERT(32 == GetBPP()); const uint32_t* buffer = (const uint32_t*)(opt ? opt : GetBuffer()); int width = GetWidth(); int height = GetHeight(); // verify that input is really premultiplied for (int y = 0; y < height; ++y) { const uint32_t* srcRow = buffer + y * width; for (int x = 0; x < width; ++x) { uint8_t a = SkGetPackedA32(srcRow[x]); uint8_t r = SkGetPackedR32(srcRow[x]); uint8_t g = SkGetPackedG32(srcRow[x]); uint8_t b = SkGetPackedB32(srcRow[x]); SkA32Assert(a); SkASSERT(r <= a); SkASSERT(g <= a); SkASSERT(b <= a); } } #endif } #endif