blob: 3ccfd14d735dd0b363fafe2cdb815d4d6fc47099 [file] [log] [blame]
// 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 <algorithm>
#include <vector>
#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_gemodule.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/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPictureRecorder.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) {
SkMatrix skMatrix;
skMatrix.setAll(m.a, -m.c, m.e, m.b, -m.d, 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<SkColor>* 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<SkColor>* skColors,
SkTDArray<SkScalar>* 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<SkColor>* skColors,
SkTDArray<SkScalar>* 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));
}
} // 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);
for (int index = 0; index < nChars; ++index) {
const FXTEXT_CHARPOS& cp = pCharPos[index];
m_positions[index + count] = {cp.m_OriginX, cp.m_OriginY};
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, -delta.fY);
}
m_drawText = true;
return true;
}
void FlushText(CFX_SkiaDeviceDriver* pDriver) {
SkMatrix skMatrix = ToFlippedSkMatrix(m_drawMatrix);
SkPaint skPaint;
skPaint.setAntiAlias(true);
skPaint.setColor(m_fillColor);
if (m_pFont->GetFace() && m_pCache) { // exclude placeholder test fonts
sk_sp<SkTypeface> 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<SkPath> m_clips; // stack of clips that may be reused
SkTDArray<Clip> m_commands; // stack of clip-related commands
SkTDArray<SkPoint> m_positions; // accumulator for text positions
SkTDArray<uint16_t> 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<SkTypeface> 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();
SkMatrix skMatrix = ToFlippedSkMatrix(*pObject2Device);
m_pCanvas->concat(skMatrix);
SkTDArray<SkPoint> positions;
positions.setCount(nChars);
SkTDArray<uint16_t> glyphs;
glyphs.setCount(nChars);
for (int index = 0; index < nChars; ++index) {
const FXTEXT_CHARPOS& cp = pCharPos[index];
positions[index] = {cp.m_OriginX, cp.m_OriginY};
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<std::unique_ptr<CPDF_Function>>& 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->GetArrayBy("Coords");
if (!pCoords)
return true;
// 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++) {
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->GetArrayBy("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);
SkColorType colorType = pSource->IsAlphaMask()
? SkColorType::kAlpha_8_SkColorType
: SkColorType::kGray_8_SkColorType;
SkAlphaType alphaType =
pSource->IsAlphaMask() ? kPremul_SkAlphaType : kOpaque_SkAlphaType;
SkColorTable* ct = nullptr;
void* buffer = pSource->GetBuffer();
if (!buffer)
return FALSE;
std::unique_ptr<uint8_t, FxFreeDeleter> dst8Storage;
std::unique_ptr<uint32_t, FxFreeDeleter> dst32Storage;
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<const uint8_t*>(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()) {
ct = 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<const uint8_t*>(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 skBitmap;
skBitmap.installPixels(imageInfo, buffer, rowBytes, ct, nullptr, nullptr);
m_pCanvas->save();
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);
m_pCanvas->concat(skMatrix);
SkPaint paint;
paint.setAntiAlias(true);
if (pSource->IsAlphaMask()) {
paint.setColorFilter(
SkColorFilter::MakeModeFilter(argb, SkXfermode::kSrc_Mode));
}
// paint.setFilterQuality(kHigh_SkFilterQuality);
paint.setXfermodeMode(GetSkiaBlendMode(blend_type));
paint.setAlpha(bitmap_alpha);
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();
}
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;
}
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<CFX_SkiaDeviceDriver*>(GetDeviceDriver());
if (skDriver)
skDriver->DebugVerifyBitmapIsPreMultiplied();
#endif
}
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