android_frameworks_base/libs/hwui/SkiaCanvasProxy.cpp
Stan Iliev 0a3ff952a6 Improve color correctness for drawing bitmaps with Skia pipeline
Fix drawing of bitmaps with color profiles. This CL is making
ColorSpaceTests and Rgba16fTests CTS tests to pass with Skia
pipeline. Drawing bitmaps withs pixels outside SRGB range
may need more work (ColorSpaceTests#testDrawDisplayP3 test use a
a wider gamut, but the actual pixels fall into the SRGB range).

Test: Ran CtsUiRenderingTestCases with HWUI and Skia pipeline.
Bug: 62347704
Change-Id: I8d318076bb38f7d32bfde7e5492ae7a61f4731a5
2017-07-19 09:22:39 -04:00

490 lines
18 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SkiaCanvasProxy.h"
#include <memory>
#include <log/log.h>
#include "hwui/Bitmap.h"
#include <SkLatticeIter.h>
#include <SkPatchUtils.h>
#include <SkPaint.h>
#include <SkPath.h>
#include <SkPixelRef.h>
#include <SkRect.h>
#include <SkRRect.h>
#include <SkRSXform.h>
#include <SkSurface.h>
#include <SkTextBlobRunIterator.h>
#include <SkVertices.h>
namespace android {
namespace uirenderer {
SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls)
: INHERITED(canvas->width(), canvas->height())
, mCanvas(canvas)
, mFilterHwuiCalls(filterHwuiCalls) {}
void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) {
mCanvas->drawPaint(paint);
}
void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
if (!pts || count == 0) {
return;
}
// convert the SkPoints into floats
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
const size_t floatCount = count << 1;
const float* floatArray = &pts[0].fX;
switch (pointMode) {
case kPoints_PointMode: {
mCanvas->drawPoints(floatArray, floatCount, paint);
break;
}
case kLines_PointMode: {
mCanvas->drawLines(floatArray, floatCount, paint);
break;
}
case kPolygon_PointMode: {
SkPaint strokedPaint(paint);
strokedPaint.setStyle(SkPaint::kStroke_Style);
SkPath path;
for (size_t i = 0; i < count - 1; i++) {
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
this->drawPath(path, strokedPaint);
path.rewind();
}
break;
}
default:
LOG_ALWAYS_FATAL("Unknown point type");
}
}
void SkiaCanvasProxy::onDrawOval(const SkRect& rect, const SkPaint& paint) {
mCanvas->drawOval(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint);
}
void SkiaCanvasProxy::onDrawRect(const SkRect& rect, const SkPaint& paint) {
mCanvas->drawRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint);
}
void SkiaCanvasProxy::onDrawRRect(const SkRRect& roundRect, const SkPaint& paint) {
if (!roundRect.isComplex()) {
const SkRect& rect = roundRect.rect();
SkVector radii = roundRect.getSimpleRadii();
mCanvas->drawRoundRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
radii.fX, radii.fY, paint);
} else {
SkPath path;
path.addRRect(roundRect);
mCanvas->drawPath(path, paint);
}
}
void SkiaCanvasProxy::onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle,
bool useCenter, const SkPaint& paint) {
mCanvas->drawArc(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
startAngle, sweepAngle, useCenter, paint);
}
void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) {
mCanvas->drawPath(path, paint);
}
void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint* paint) {
sk_sp<Bitmap> hwuiBitmap = Bitmap::createFrom(bitmap.info(), *bitmap.pixelRef());
// HWUI doesn't support extractSubset(), so convert any subsetted bitmap into
// a drawBitmapRect(); pass through an un-subsetted bitmap.
if (hwuiBitmap && bitmap.dimensions() != hwuiBitmap->info().dimensions()) {
SkIPoint origin = bitmap.pixelRefOrigin();
mCanvas->drawBitmap(*hwuiBitmap, origin.fX, origin.fY,
origin.fX + bitmap.dimensions().width(),
origin.fY + bitmap.dimensions().height(),
left, top,
left + bitmap.dimensions().width(),
top + bitmap.dimensions().height(),
paint);
} else {
mCanvas->drawBitmap(*hwuiBitmap, left, top, paint);
}
}
void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& skBitmap, const SkRect* srcPtr,
const SkRect& dst, const SkPaint* paint, SrcRectConstraint) {
SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(skBitmap.width(), skBitmap.height());
// TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
Bitmap* bitmap = reinterpret_cast<Bitmap*>(skBitmap.pixelRef());
mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
}
void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
const SkRect& dst, const SkPaint*) {
//TODO make nine-patch drawing a method on Canvas.h
SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported");
}
void SkiaCanvasProxy::onDrawImage(const SkImage* image, SkScalar left, SkScalar top,
const SkPaint* paint) {
SkBitmap skiaBitmap;
SkPixmap pixmap;
if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) {
onDrawBitmap(skiaBitmap, left, top, paint);
}
}
void SkiaCanvasProxy::onDrawImageRect(const SkImage* image, const SkRect* srcPtr, const SkRect& dst,
const SkPaint* paint, SrcRectConstraint constraint) {
SkBitmap skiaBitmap;
SkPixmap pixmap;
if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) {
sk_sp<Bitmap> bitmap = Bitmap::createFrom(skiaBitmap.info(), *skiaBitmap.pixelRef());
SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(image->width(), image->height());
mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
}
}
void SkiaCanvasProxy::onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst,
const SkPaint*) {
SkDEBUGFAIL("SkiaCanvasProxy::onDrawImageNine is not yet supported");
}
void SkiaCanvasProxy::onDrawImageLattice(const SkImage* image, const Lattice& lattice,
const SkRect& dst, const SkPaint* paint) {
SkLatticeIter iter(lattice, dst);
SkRect srcR, dstR;
while (iter.next(&srcR, &dstR)) {
onDrawImageRect(image, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint);
}
}
void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
const SkPaint& paint) {
if (mFilterHwuiCalls) {
return;
}
mCanvas->drawVertices(vertices, bmode, paint);
}
sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) {
SkDEBUGFAIL("SkiaCanvasProxy::onNewSurface is not supported");
return NULL;
}
void SkiaCanvasProxy::willSave() {
mCanvas->save(android::SaveFlags::MatrixClip);
}
static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) {
SaveFlags::Flags saveFlags = 0;
if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) {
saveFlags |= SaveFlags::ClipToLayer;
}
if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) {
saveFlags |= SaveFlags::HasAlphaLayer;
}
return saveFlags;
}
SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy(const SaveLayerRec& saveLayerRec) {
SkRect rect;
if (saveLayerRec.fBounds) {
rect = *saveLayerRec.fBounds;
} else if (!mCanvas->getClipBounds(&rect)) {
rect = SkRect::MakeEmpty();
}
mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, saveLayerRec.fPaint,
saveFlags(saveLayerRec.fSaveLayerFlags));
return SkCanvas::kNoLayer_SaveLayerStrategy;
}
void SkiaCanvasProxy::willRestore() {
mCanvas->restore();
}
void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) {
mCanvas->concat(matrix);
}
void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) {
mCanvas->setMatrix(matrix);
}
void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
const SkPaint& paint) {
SkPath path;
path.addRRect(outer);
path.addRRect(inner);
path.setFillType(SkPath::kEvenOdd_FillType);
this->drawPath(path, paint);
}
/**
* Utility class that converts the incoming text & paint from the given encoding
* into glyphIDs.
*/
class GlyphIDConverter {
public:
GlyphIDConverter(const void* text, size_t byteLength, const SkPaint& origPaint) {
paint = origPaint;
if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) {
glyphIDs = (uint16_t*)text;
count = byteLength >> 1;
} else {
// ensure space for one glyph per ID given UTF8 encoding.
storage.reset(new uint16_t[byteLength]);
glyphIDs = storage.get();
count = paint.textToGlyphs(text, byteLength, storage.get());
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
}
}
SkPaint paint;
uint16_t* glyphIDs;
int count;
private:
std::unique_ptr<uint16_t[]> storage;
};
void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
const SkPaint& origPaint) {
// convert to glyphIDs if necessary
GlyphIDConverter glyphs(text, byteLength, origPaint);
// compute the glyph positions
std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]);
glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get());
// compute conservative bounds
// NOTE: We could call the faster paint.getFontBounds for a less accurate,
// but even more conservative bounds if this is too slow.
SkRect bounds;
glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds);
// adjust for non-left alignment
if (glyphs.paint.getTextAlign() != SkPaint::kLeft_Align) {
SkScalar stop = 0;
for (int i = 0; i < glyphs.count; i++) {
stop += glyphWidths[i];
}
if (glyphs.paint.getTextAlign() == SkPaint::kCenter_Align) {
stop = SkScalarHalf(stop);
}
if (glyphs.paint.isVerticalText()) {
y -= stop;
} else {
x -= stop;
}
}
// setup the first glyph position and adjust bounds if needed
int xBaseline = 0;
int yBaseline = 0;
if (mCanvas->drawTextAbsolutePos()) {
bounds.offset(x,y);
xBaseline = x;
yBaseline = y;
}
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
auto glyphFunc = [&] (uint16_t* text, float* positions) {
memcpy(text, glyphs.glyphIDs, glyphs.count*sizeof(uint16_t));
size_t posIndex = 0;
// setup the first glyph position
positions[posIndex++] = xBaseline;
positions[posIndex++] = yBaseline;
// setup the remaining glyph positions
if (glyphs.paint.isVerticalText()) {
float yPosition = yBaseline;
for (int i = 1; i < glyphs.count; i++) {
positions[posIndex++] = xBaseline;
yPosition += glyphWidths[i-1];
positions[posIndex++] = yPosition;
}
} else {
float xPosition = xBaseline;
for (int i = 1; i < glyphs.count; i++) {
xPosition += glyphWidths[i-1];
positions[posIndex++] = xPosition;
positions[posIndex++] = yBaseline;
}
}
};
mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop,
bounds.fRight, bounds.fBottom, 0);
}
void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
const SkPaint& origPaint) {
// convert to glyphIDs if necessary
GlyphIDConverter glyphs(text, byteLength, origPaint);
// convert to relative positions if necessary
int x, y;
if (mCanvas->drawTextAbsolutePos()) {
x = 0;
y = 0;
} else {
x = pos[0].fX;
y = pos[0].fY;
}
// Compute conservative bounds. If the content has already been processed
// by Minikin then it had already computed these bounds. Unfortunately,
// there is no way to capture those bounds as part of the Skia drawPosText
// API so we need to do that computation again here.
SkRect bounds = SkRect::MakeEmpty();
for (int i = 0; i < glyphs.count; i++) {
SkRect glyphBounds = SkRect::MakeEmpty();
glyphs.paint.measureText(&glyphs.glyphIDs[i], sizeof(uint16_t), &glyphBounds);
glyphBounds.offset(pos[i].fX, pos[i].fY);
bounds.join(glyphBounds);
}
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
auto glyphFunc = [&] (uint16_t* text, float* positions) {
memcpy(text, glyphs.glyphIDs, glyphs.count*sizeof(uint16_t));
if (mCanvas->drawTextAbsolutePos()) {
memcpy(positions, pos, 2*glyphs.count*sizeof(float));
} else {
for (int i = 0, posIndex = 0; i < glyphs.count; i++) {
positions[posIndex++] = pos[i].fX - x;
positions[posIndex++] = pos[i].fY - y;
}
}
};
mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop,
bounds.fRight, bounds.fBottom, 0);
}
void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
SkScalar constY, const SkPaint& paint) {
const size_t pointCount = byteLength >> 1;
std::unique_ptr<SkPoint[]> pts(new SkPoint[pointCount]);
for (size_t i = 0; i < pointCount; i++) {
pts[i].set(xpos[i], constY);
}
this->onDrawPosText(text, byteLength, pts.get(), paint);
}
void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
const SkMatrix* matrix, const SkPaint& origPaint) {
SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextOnPath is not supported");
}
void SkiaCanvasProxy::onDrawTextRSXform(const void* text, size_t byteLength,
const SkRSXform xform[], const SkRect* cullRect, const SkPaint& paint) {
GlyphIDConverter glyphs(text, byteLength, paint); // Just get count
SkMatrix localM, currM, origM;
mCanvas->getMatrix(&currM);
origM = currM;
for (int i = 0; i < glyphs.count; i++) {
localM.setRSXform(*xform++);
currM.setConcat(origM, localM);
mCanvas->setMatrix(currM);
this->onDrawText((char*)text + (byteLength / glyphs.count * i),
byteLength / glyphs.count, 0, 0, paint);
}
mCanvas->setMatrix(origM);
}
void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
const SkPaint& paint) {
SkPaint runPaint = paint;
SkTextBlobRunIterator it(blob);
for (;!it.done(); it.next()) {
size_t textLen = it.glyphCount() * sizeof(uint16_t);
const SkPoint& offset = it.offset();
// applyFontToPaint() always overwrites the exact same attributes,
// so it is safe to not re-seed the paint for this reason.
it.applyFontToPaint(&runPaint);
switch (it.positioning()) {
case SkTextBlob::kDefault_Positioning:
this->drawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint);
break;
case SkTextBlob::kHorizontal_Positioning: {
std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]);
for (size_t i = 0; i < it.glyphCount(); i++) {
pts[i].set(x + offset.x() + it.pos()[i], y + offset.y());
}
this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint);
break;
}
case SkTextBlob::kFull_Positioning: {
std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]);
for (size_t i = 0; i < it.glyphCount(); i++) {
const size_t xIndex = i*2;
const size_t yIndex = xIndex + 1;
pts[i].set(x + offset.x() + it.pos()[xIndex], y + offset.y() + it.pos()[yIndex]);
}
this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint);
break;
}
default:
SkFAIL("unhandled positioning mode");
}
}
}
void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
const SkPoint texCoords[4], SkBlendMode bmode, const SkPaint& paint) {
if (mFilterHwuiCalls) {
return;
}
SkMatrix matrix;
mCanvas->getMatrix(&matrix);
SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix);
mCanvas->drawVertices(SkPatchUtils::MakeVertices(cubics, colors, texCoords,
lod.width(), lod.height()).get(),
bmode, paint);
}
void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle) {
mCanvas->clipRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, op);
}
void SkiaCanvasProxy::onClipRRect(const SkRRect& roundRect, SkClipOp op, ClipEdgeStyle) {
SkPath path;
path.addRRect(roundRect);
mCanvas->clipPath(&path, op);
}
void SkiaCanvasProxy::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle) {
mCanvas->clipPath(&path, op);
}
}; // namespace uirenderer
}; // namespace android