Initial implementation of SkSurface::asyncRescaleAndReadPixelsYUV420.

Rescales a pixel rectangle from a SkSurface and then converts to YUV
planes. Currently only implemented on GPU.

Supports the same rescaling options as asyncRescaleAndReadPixels. The
RGB->YUV conversion is specified by a SkYUVColorSpace.

Y, U, and V are always separate planes. The U and V planes are
subsampled in X and Y by a factor of 2.

Currently fails if either the width or height of the rectangle is
not even.

Bug: skia:8962
Change-Id: I33237849a9ccf9b66cab13dc56785496f4ce556a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/217123
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Brian Salomon 2019-06-11 11:38:16 -04:00 committed by Skia Commit-Bot
parent 2bafb64ed3
commit 024bd0058b
9 changed files with 559 additions and 94 deletions

View File

@ -11,9 +11,13 @@
#include "include/core/SkPaint.h"
#include "include/core/SkRect.h"
#include "include/core/SkSurface.h"
#include "include/core/SkYUVAIndex.h"
#include "include/gpu/GrContext.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkConvertPixels.h"
#include "src/core/SkScopeExit.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrGpu.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
@ -50,23 +54,107 @@ static sk_sp<SkImage> do_read_and_scale(SkSurface* surface, const SkIRect& srcRe
return SkImage::MakeFromBitmap(bmp);
}
static sk_sp<SkImage> do_read_and_scale_yuv(SkSurface* surface, SkYUVColorSpace yuvCS,
const SkIRect& srcRect, int dstW, int dstH,
SkSurface::RescaleGamma rescaleGamma,
SkFilterQuality quality, SkScopeExit* cleanup) {
SkASSERT(!(dstW & 0b1) && !(dstH & 0b1));
std::unique_ptr<uint8_t[]> yData(new uint8_t[dstW * dstH]);
std::unique_ptr<uint8_t[]> uData(new uint8_t[dstW / 2 * dstH / 2]);
std::unique_ptr<uint8_t[]> vData(new uint8_t[dstW / 2 * dstH / 2]);
struct Context {
int fW;
int fH;
uint8_t* fYData;
uint8_t* fUData;
uint8_t* fVData;
bool fCalled = false;
} context{dstW, dstH, yData.get(), uData.get(), vData.get()};
auto callback = [](void* c, const void* data[2], size_t rowBytes[2]) {
auto context = reinterpret_cast<Context*>(c);
context->fCalled = true;
if (!data) {
return;
}
int w = context->fW;
int h = context->fH;
SkRectMemcpy(context->fYData, w, data[0], rowBytes[0], w, h);
SkRectMemcpy(context->fUData, w / 2, data[1], rowBytes[1], w / 2, h / 2);
SkRectMemcpy(context->fVData, w / 2, data[2], rowBytes[2], w / 2, h / 2);
};
surface->asyncRescaleAndReadPixelsYUV420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, dstW, dstH,
rescaleGamma, quality, callback, &context);
while (!context.fCalled) {
// Only GPU should actually be asynchronous.
SkASSERT(surface->getCanvas()->getGrContext());
surface->getCanvas()->getGrContext()->checkAsyncWorkCompletion();
}
auto* gr = surface->getCanvas()->getGrContext();
GrBackendTexture backendTextures[3];
GrBackendFormat format = gr->priv().caps()->getBackendFormatFromColorType(kAlpha_8_SkColorType);
backendTextures[0] = gr->priv().getGpu()->createBackendTexture(
dstW, dstH, format, GrMipMapped::kNo, GrRenderable::kNo, yData.get(), 0, nullptr);
backendTextures[1] = gr->priv().getGpu()->createBackendTexture(
dstW / 2, dstH / 2, format, GrMipMapped::kNo, GrRenderable::kNo,
uData.get(), 0, nullptr);
backendTextures[2] = gr->priv().getGpu()->createBackendTexture(
dstW / 2, dstH / 2, format, GrMipMapped::kNo, GrRenderable::kNo,
vData.get(), 0, nullptr);
auto config = gr->priv().caps()->getConfigFromBackendFormat(format, kAlpha_8_SkColorType);
SkColorChannel channel;
if (config == kAlpha_8_as_Red_GrPixelConfig) {
channel = SkColorChannel::kR;
} else {
SkASSERT(config == kAlpha_8_as_Alpha_GrPixelConfig);
channel = SkColorChannel::kA;
}
SkYUVAIndex indices[4]{{0, channel}, {1, channel}, {2, channel}, {-1, SkColorChannel::kR}};
*cleanup = {[gr, backendTextures] {
GrFlushInfo flushInfo;
flushInfo.fFlags = kSyncCpu_GrFlushFlag;
gr->flush(flushInfo);
gr->deleteBackendTexture(backendTextures[0]);
gr->deleteBackendTexture(backendTextures[1]);
gr->deleteBackendTexture(backendTextures[2]);
}};
return SkImage::MakeFromYUVATextures(gr, yuvCS, backendTextures, indices, {dstW, dstH},
kTopLeft_GrSurfaceOrigin, SkColorSpace::MakeSRGB());
}
// Draws a grid of rescales. The columns are none, low, and high filter quality. The rows are
// rescale in src gamma and rescale in linear gamma.
static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas, SkSurface* surface,
const SkIRect& srcRect, int newW, int newH,
const SkIRect& srcRect, int newW, int newH, bool doYUV420,
SkString* errorMsg, int pad = 0) {
if (doYUV420) {
if (!canvas->getGrContext()) {
errorMsg->printf("YUV420 only supported on GPU for now.");
return skiagm::DrawResult::kSkip;
}
}
if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
*errorMsg = "Not supported on recording/vector backends.";
return skiagm::DrawResult::kSkip;
}
const auto ii = canvas->imageInfo().makeWH(newW, newH);
SkYUVColorSpace yuvColorSpace = kRec601_SkYUVColorSpace;
canvas->save();
for (auto linear : {SkSurface::RescaleGamma::kSrc, SkSurface::RescaleGamma::kLinear}) {
for (auto gamma : {SkSurface::RescaleGamma::kSrc, SkSurface::RescaleGamma::kLinear}) {
canvas->save();
for (auto quality : {kNone_SkFilterQuality, kLow_SkFilterQuality, kHigh_SkFilterQuality}) {
auto rescaled = do_read_and_scale(surface, srcRect, ii, linear, quality);
canvas->drawImage(rescaled, 0, 0);
SkScopeExit cleanup;
sk_sp<SkImage> result;
if (doYUV420) {
result = do_read_and_scale_yuv(surface, yuvColorSpace, srcRect, newW, newH, gamma,
quality, &cleanup);
int nextCS = static_cast<int>(yuvColorSpace + 1) % (kLastEnum_SkYUVColorSpace + 1);
yuvColorSpace = static_cast<SkYUVColorSpace>(nextCS);
} else {
result = do_read_and_scale(surface, srcRect, ii, gamma, quality);
}
canvas->drawImage(result, 0, 0);
canvas->translate(newW + pad, 0);
}
canvas->restore();
@ -78,7 +166,7 @@ static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas, SkSurface* surface,
static skiagm::DrawResult do_rescale_image_grid(SkCanvas* canvas, const char* imageFile,
const SkIRect& srcRect, int newW, int newH,
SkString* errorMsg) {
bool doYUV420, SkString* errorMsg) {
auto image = GetResourceAsImage(imageFile);
if (!image) {
errorMsg->printf("Could not load image file %s.", imageFile);
@ -106,15 +194,24 @@ static skiagm::DrawResult do_rescale_image_grid(SkCanvas* canvas, const char* im
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawImage(image, 0, 0, &paint);
return do_rescale_grid(canvas, surface.get(), srcRect, newW, newH, errorMsg);
return do_rescale_grid(canvas, surface.get(), srcRect, newW, newH, doYUV420, errorMsg);
}
#define DEF_RESCALE_AND_READ_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, W, H, errorMsg); \
#define DEF_RESCALE_AND_READ_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, W, H, false, errorMsg); \
}
#define DEF_RESCALE_AND_READ_YUV_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, W, H, true, errorMsg); \
}
DEF_RESCALE_AND_READ_YUV_GM(images/yellow_rose.webp, rose, SkIRect::MakeXYWH(50, 5, 200, 150),
410, 376)
DEF_RESCALE_AND_READ_GM(images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100),
410, 410)
@ -159,14 +256,14 @@ DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_no_bleed, canvas, errorMsg, 60, 60
skiagm::DrawResult result;
auto downW = static_cast<int>(kInner / 2);
auto downH = static_cast<int>(kInner / 2);
result = do_rescale_grid(canvas, surface.get(), srcRect, downW, downH, errorMsg, kPad);
result = do_rescale_grid(canvas, surface.get(), srcRect, downW, downH, false, errorMsg, kPad);
if (result != skiagm::DrawResult::kOk) {
return result;
}
canvas->translate(0, 2 * downH);
auto upW = static_cast<int>(kInner * 3.5);
auto upH = static_cast<int>(kInner * 4.6);
result = do_rescale_grid(canvas, surface.get(), srcRect, upW, upH, errorMsg, kPad);
result = do_rescale_grid(canvas, surface.get(), srcRect, upW, upH, false, errorMsg, kPad);
if (result != skiagm::DrawResult::kOk) {
return result;
}

View File

@ -711,6 +711,35 @@ public:
RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallback callback, ReadPixelsContext context);
/**
Similar to asyncRescaleAndReadPixels but performs an additional conversion to YUV. The
RGB->YUV conversion is controlled by 'yuvColorSpace'. The YUV data is returned as three
planes ordered y, u, v. The u and v planes are half the width and height of the resized
rectangle. Currently this fails if dstW or dstH are not even.
On failure the callback is called with a null data pointer array. Fails if srcRect is not
contained in the surface bounds.
@param yuvColorSpace The transformation from RGB to YUV. Applied to the resized image
after it is converted to dstColorSpace.
@param dstColorSpace The color space to convert the resized image to, after rescaling.
@param srcRect The portion of the surface to rescale and convert to YUV planes.
@param dstW The width to rescale srcRect to
@param dstH The height to rescale srcRect to
@param rescaleGamma controls whether rescaling is done in the surface's gamma or whether
the source data is transformed to a linear gamma before rescaling.
@param rescaleQuality controls the quality (and cost) of the rescaling
@param callback function to call with the planar read result
@param context passed to callback
*/
using ReadPixelsCallbackYUV420 = void(ReadPixelsContext, const void* data[3],
size_t rowBytes[3]);
void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
sk_sp<SkColorSpace> dstColorSpace, const SkIRect& srcRect,
int dstW, int dstH, RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback, ReadPixelsContext);
/** Copies SkRect of pixels from the src SkPixmap to the SkSurface.
Source SkRect corners are (0, 0) and (src.width(), src.height()).

View File

@ -16,6 +16,7 @@
/** SkScopeExit calls a std:::function<void()> in its destructor. */
class SkScopeExit {
public:
SkScopeExit() = default;
SkScopeExit(std::function<void()> f) : fFn(std::move(f)) {}
SkScopeExit(SkScopeExit&& that) : fFn(std::move(that.fFn)) {}

View File

@ -42,6 +42,7 @@
#include "src/gpu/effects/GrBicubicEffect.h"
#include "src/gpu/effects/GrRRectEffect.h"
#include "src/gpu/effects/GrTextureDomain.h"
#include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
#include "src/gpu/geometry/GrQuad.h"
#include "src/gpu/geometry/GrShape.h"
#include "src/gpu/ops/GrAtlasTextOp.h"
@ -1814,15 +1815,15 @@ sk_sp<GrRenderTargetContext> GrRenderTargetContext::rescale(const SkImageInfo& i
stepsY = sy != 1.f;
}
SkASSERT(stepsX || stepsY);
auto currentColorSpace = this->colorSpaceInfo().refColorSpace();
auto rescaleColorSapce = this->colorSpaceInfo().refColorSpace();
// Assume we should ignore the rescale linear request if the surface has no color space since
// it's unclear how we'd linearize from an unknown color space.
if (rescaleGamma == SkSurface::RescaleGamma::kLinear &&
currentColorSpace.get() && !currentColorSpace->gammaIsLinear()) {
auto cs = currentColorSpace->makeLinearGamma();
rescaleColorSapce.get() && !rescaleColorSapce->gammaIsLinear()) {
auto cs = rescaleColorSapce->makeLinearGamma();
auto backendFormat = this->caps()->getBackendFormatFromGrColorType(GrColorType::kRGBA_F16,
GrSRGBEncoded::kNo);
auto xform = GrColorSpaceXform::Make(currentColorSpace.get(), kPremul_SkAlphaType, cs.get(),
auto xform = GrColorSpaceXform::Make(rescaleColorSapce.get(), kPremul_SkAlphaType, cs.get(),
kPremul_SkAlphaType);
// We'll fall back to kRGBA_8888 if half float not supported.
auto linearRTC = fContext->priv().makeDeferredRenderTargetContextWithFallback(
@ -1837,7 +1838,7 @@ sk_sp<GrRenderTargetContext> GrRenderTargetContext::rescale(const SkImageInfo& i
GrAA::kNo, GrQuadAAFlags::kNone, constraint, SkMatrix::I(),
std::move(xform));
texProxy = linearRTC->asTextureProxyRef();
currentColorSpace = std::move(cs);
rescaleColorSapce = std::move(cs);
srcX = 0;
srcY = 0;
constraint = SkCanvas::kFast_SrcRectConstraint;
@ -1866,14 +1867,14 @@ sk_sp<GrRenderTargetContext> GrRenderTargetContext::rescale(const SkImageInfo& i
}
GrBackendFormat backendFormat = texProxy->backendFormat().makeTexture2D();
GrPixelConfig config = texProxy->config();
auto cs = currentColorSpace;
auto cs = rescaleColorSapce;
sk_sp<GrColorSpaceXform> xform;
if (!stepsX && !stepsY) {
// Might as well fold conversion to final info in the last step.
backendFormat = this->caps()->getBackendFormatFromColorType(info.colorType());
config = this->caps()->getConfigFromBackendFormat(backendFormat, info.colorType());
cs = info.refColorSpace();
xform = GrColorSpaceXform::Make(this->colorSpaceInfo().colorSpace(),
xform = GrColorSpaceXform::Make(rescaleColorSapce.get(),
kPremul_SkAlphaType, cs.get(), info.alphaType());
}
currRTC = fContext->priv().makeDeferredRenderTargetContextWithFallback(
@ -2015,101 +2016,371 @@ void GrRenderTargetContext::asyncRescaleAndReadPixels(
rtc = sk_ref_sp(this);
}
}
return rtc->asyncReadPixels(info, x, y, callback, context);
return rtc->asyncReadPixels(SkIRect::MakeXYWH(x, y, info.width(), info.height()),
info.colorType(), callback, context);
}
void GrRenderTargetContext::asyncReadPixels(const SkImageInfo& info, int x, int y,
ReadPixelsCallback callback,
ReadPixelsContext context) {
SkASSERT(info.width() + x <= this->width());
SkASSERT(info.height() + y <= this->height());
GrRenderTargetContext::PixelTransferResult GrRenderTargetContext::transferPixels(
GrColorType dstCT, const SkIRect& rect) {
SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width());
SkASSERT(rect.fTop >= 0 && rect.fBottom <= this->height());
auto direct = fContext->priv().asDirectContext();
if (!direct) {
callback(context, nullptr, 0);
return;
return {};
}
if (fRenderTargetProxy->wrapsVkSecondaryCB()) {
callback(context, nullptr, 0);
return;
return {};
}
// We currently don't know our own alpha type, we assume it's premul if we have an alpha channel
// and opaque otherwise.
if (!GrPixelConfigIsAlphaOnly(fRenderTargetProxy->config()) &&
info.alphaType() != kPremul_SkAlphaType) {
callback(context, nullptr, 0);
return;
}
auto dstCT = SkColorTypeToGrColorType(info.colorType());
auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT);
// Fail if we can't do a CPU conversion from readCT to dstCT.
if (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType) {
callback(context, nullptr, 0);
return;
if (readCT != dstCT && (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType ||
GrColorTypeToSkColorType(dstCT) == kUnknown_SkColorType)) {
return {};
}
// Fail if readCT does not have all of readCT's color channels.
if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readCT)) {
callback(context, nullptr, 0);
return;
return {};
}
if (!this->caps()->transferBufferSupport() ||
!this->caps()->transferFromOffsetAlignment(readCT)) {
return {};
}
size_t rowBytes = GrColorTypeBytesPerPixel(readCT) * rect.width();
size_t size = rowBytes * rect.height();
auto buffer = direct->priv().resourceProvider()->createBuffer(
size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern);
if (!buffer) {
return {};
}
this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, rect, readCT, buffer, 0),
*this->caps());
PixelTransferResult result;
result.fTransferBuffer = std::move(buffer);
if (readCT != dstCT) {
result.fPixelConverter =
[w = rect.width(), h = rect.height(), dstCT = GrColorTypeToSkColorType(dstCT),
srcCT = GrColorTypeToSkColorType(readCT)](void* dst, const void* src) {
auto dstII = SkImageInfo::Make(w, h, dstCT, kPremul_SkAlphaType, nullptr);
auto srcII = SkImageInfo::Make(w, h, srcCT, kPremul_SkAlphaType, nullptr);
SkConvertPixels(dstII, dst, w * SkColorTypeBytesPerPixel(dstCT), srcII, src,
w * SkColorTypeBytesPerPixel(srcCT));
};
}
return result;
}
void GrRenderTargetContext::asyncReadPixels(const SkIRect& rect, SkColorType colorType,
ReadPixelsCallback callback,
ReadPixelsContext context) {
SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width());
SkASSERT(rect.fTop >= 0 && rect.fBottom <= this->height());
auto transferResult = this->transferPixels(SkColorTypeToGrColorType(colorType), rect);
if (!transferResult.fTransferBuffer) {
SkAutoPixmapStorage pm;
pm.alloc(info);
if (!this->readPixels(info, pm.writable_addr(), pm.rowBytes(), x, y)) {
auto ii = SkImageInfo::Make(rect.width(), rect.height(), colorType, kPremul_SkAlphaType,
this->colorSpaceInfo().refColorSpace());
pm.alloc(ii);
if (!this->readPixels(ii, pm.writable_addr(), pm.rowBytes(), rect.fLeft, rect.fTop)) {
callback(context, nullptr, 0);
}
callback(context, pm.addr(), pm.rowBytes());
return;
}
size_t rowBytes = GrColorTypeBytesPerPixel(readCT) * info.width();
size_t size = rowBytes * info.height();
auto buffer = direct->priv().resourceProvider()->createBuffer(
size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern);
if (!buffer) {
callback(context, nullptr, 0);
return;
}
this->getRTOpList()->addOp(
GrTransferFromOp::Make(fContext, SkIRect::MakeXYWH(x, y, info.width(), info.height()),
readCT, buffer, 0),
*this->caps());
struct FinishContext {
SkImageInfo fReadInfo;
SkImageInfo fDstInfo;
ReadPixelsCallback* fClientCallback;
ReadPixelsContext fClientContext;
sk_sp<GrGpuBuffer> fBuffer;
size_t fRowBytes;
int fW, fH;
SkColorType fColorType;
PixelTransferResult fTransferResult;
};
const auto readInfo = info.makeColorType(GrColorTypeToSkColorType(readCT));
// Assumption is that the caller would like to flush. We could take a parameter or require an
// explicit flush from the caller. We'd have to have a way to defer attaching the finish
// callback to GrGpu until after the next flush that flushes our op list, though.
auto* finishContext = new FinishContext{readInfo, info, callback, context, buffer, rowBytes};
auto* finishContext = new FinishContext{callback, context, rect.width(),
rect.height(), colorType, std::move(transferResult)};
auto finishCallback = [](GrGpuFinishedContext c) {
auto context = reinterpret_cast<const FinishContext*>(c);
void* data = context->fBuffer->map();
const auto* context = reinterpret_cast<const FinishContext*>(c);
const void* data = context->fTransferResult.fTransferBuffer->map();
if (!data) {
(*context->fClientCallback)(context->fClientContext, nullptr, 0);
delete context;
return;
}
SkAutoPixmapStorage pm;
const void* callbackData = data;
size_t callbackRowBytes = context->fRowBytes;
if (context->fDstInfo != context->fReadInfo) {
pm.alloc(context->fDstInfo);
SkConvertPixels(context->fDstInfo, pm.writable_addr(), pm.rowBytes(),
context->fReadInfo, data, context->fRowBytes);
callbackData = pm.addr();
callbackRowBytes = pm.rowBytes();
if (context->fTransferResult.fPixelConverter) {
pm.alloc(SkImageInfo::Make(context->fW, context->fH, context->fColorType,
kPremul_SkAlphaType, nullptr));
context->fTransferResult.fPixelConverter(pm.writable_addr(), data);
data = pm.addr();
}
(*context->fClientCallback)(context->fClientContext, callbackData, callbackRowBytes);
if (data) {
context->fBuffer->unmap();
(*context->fClientCallback)(context->fClientContext, data,
context->fW * SkColorTypeBytesPerPixel(context->fColorType));
delete context;
};
GrFlushInfo flushInfo;
flushInfo.fFinishedContext = finishContext;
flushInfo.fFinishedProc = finishCallback;
this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
}
void GrRenderTargetContext::asyncRescaleAndReadPixelsYUV420(
SkYUVColorSpace yuvColorSpace, sk_sp<SkColorSpace> dstColorSpace, const SkIRect& srcRect,
int dstW, int dstH, RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback, ReadPixelsContext context) {
SkASSERT(srcRect.fLeft >= 0 && srcRect.fRight <= this->width());
SkASSERT(srcRect.fTop >= 0 && srcRect.fBottom <= this->height());
SkASSERT((dstW % 2 == 0) && (dstH % 2 == 0));
auto direct = fContext->priv().asDirectContext();
if (!direct) {
callback(context, nullptr, nullptr);
return;
}
if (fRenderTargetProxy->wrapsVkSecondaryCB()) {
callback(context, nullptr, nullptr);
return;
}
if (dstW & 0x1) {
return;
}
int x = srcRect.fLeft;
int y = srcRect.fTop;
auto rtc = sk_ref_sp(this);
bool needsRescale = srcRect.width() != dstW || srcRect.height() != dstH;
if (needsRescale) {
auto info = SkImageInfo::Make(dstW, dstH, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
dstColorSpace);
// TODO: Incorporate the YUV conversion into last pass of rescaling.
rtc = this->rescale(info, srcRect, rescaleGamma, rescaleQuality);
if (!rtc) {
callback(context, nullptr, nullptr);
return;
}
SkASSERT(SkColorSpace::Equals(rtc->colorSpaceInfo().colorSpace(), info.colorSpace()));
SkASSERT(rtc->origin() == kTopLeft_GrSurfaceOrigin);
x = y = 0;
} else {
sk_sp<GrColorSpaceXform> xform =
GrColorSpaceXform::Make(this->colorSpaceInfo().colorSpace(), kPremul_SkAlphaType,
dstColorSpace.get(), kPremul_SkAlphaType);
if (xform) {
sk_sp<GrTextureProxy> texProxy = this->asTextureProxyRef();
// TODO: Do something if the input is not a texture already.
if (!texProxy) {
callback(context, nullptr, nullptr);
return;
}
const auto backendFormat =
this->caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
SkRect srcRectToDraw = SkRect::Make(srcRect);
rtc = direct->priv().makeDeferredRenderTargetContext(
backendFormat, SkBackingFit::kApprox, dstW, dstH, fRenderTargetProxy->config(),
dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
if (!rtc) {
callback(context, nullptr, nullptr);
return;
}
rtc->drawTexture(GrNoClip(), std::move(texProxy), GrSamplerState::Filter::kNearest,
SkBlendMode::kSrc, SK_PMColor4fWHITE, srcRectToDraw,
SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(),
std::move(xform));
x = y = 0;
}
}
auto srcProxy = rtc->asTextureProxyRef();
// TODO: Do something if the input is not a texture already.
if (!srcProxy) {
callback(context, nullptr, nullptr);
return;
}
GrPixelConfig planeConfig = kAlpha_8_GrPixelConfig;
GrColorType planeColorType = GrColorType::kAlpha_8;
if (this->caps()->supportedReadPixelsColorType(planeConfig, planeColorType) !=
GrColorType::kAlpha_8) {
// TODO: Because there are issues with reading back/transferring A8 textures on GL, we are
// currently using RGBA textures for the planes. Fix this once the A8 read back/transfer
// issues are addressed.
planeConfig = kRGBA_8888_GrPixelConfig;
planeColorType = GrColorType::kRGBA_8888;
}
const auto backendFormat = this->caps()->getBackendFormatFromGrColorType(
planeColorType, GrSRGBEncoded::kNo);
auto yRTC = direct->priv().makeDeferredRenderTargetContext(
backendFormat, SkBackingFit::kApprox, dstW, dstH, planeConfig, dstColorSpace,
1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
auto uRTC = direct->priv().makeDeferredRenderTargetContext(
backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, planeConfig,
dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
auto vRTC = direct->priv().makeDeferredRenderTargetContext(
backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, planeConfig,
dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
if (!yRTC || !uRTC || !vRTC) {
callback(context, nullptr, nullptr);
return;
}
static constexpr float kRec601M[] {
65.481f / 255, 128.553f / 255, 24.966f / 255, 16.f / 255, // y
-37.797f / 255, -74.203f / 255, 112.0f / 255, 128.f / 255, // u
112.f / 255, -93.786f / 255, -18.214f / 255, 128.f / 255, // v
};
static constexpr float kRec709M[] {
45.5594f / 255, 156.6288f / 255, 15.8118f / 255, 16.f / 255, // y
-25.6642f / 255, -86.3358f / 255, 112.f / 255, 128.f / 255, // u
112.f / 255, -101.7303f / 255, -10.2697f / 255, 128.f / 255, // v
};
static constexpr float kJpegM[] {
0.299f , 0.587f , 0.114f , 0.f / 255, // y
-0.168736f, -0.331264f, 0.5f , 128.f / 255, // u
0.5f , -0.418688f, -0.081312f, 128.f / 255, // v
};
static constexpr float kIM[] {
1.f, 0.f, 0.f, 0.f,
0.f, 1.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
};
const float* baseM = kIM;
switch (yuvColorSpace) {
case kRec601_SkYUVColorSpace:
baseM = kRec601M;
break;
case kRec709_SkYUVColorSpace:
baseM = kRec709M;
break;
case kJPEG_SkYUVColorSpace:
baseM = kJpegM;
break;
case kIdentity_SkYUVColorSpace:
baseM = kIM;
break;
}
// TODO: Use one transfer buffer for all three planes to reduce map/unmap cost?
auto texMatrix = SkMatrix::MakeTrans(x, y);
// This matrix generates (r,g,b,a) = (0, 0, 0, y)
float yM[20];
std::fill_n(yM, 15, 0);
yM[15] = baseM[0]; yM[16] = baseM[1]; yM[17] = baseM[2]; yM[18] = 0; yM[19] = baseM[3];
GrPaint yPaint;
yPaint.addColorTextureProcessor(srcProxy, texMatrix);
auto yFP = GrColorMatrixFragmentProcessor::Make(yM, false, true, false);
yPaint.addColorFragmentProcessor(std::move(yFP));
yPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
yRTC->drawFilledRect(GrNoClip(), std::move(yPaint), GrAA::kNo, SkMatrix::I(),
SkRect::MakeWH(dstW, dstH));
auto yTransfer = yRTC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeWH(yRTC->width(), yRTC->height()));
if (!yTransfer.fTransferBuffer) {
callback(context, nullptr, nullptr);
return;
}
texMatrix.preScale(2.f, 2.f);
// This matrix generates (r,g,b,a) = (0, 0, 0, u)
float uM[20];
std::fill_n(uM, 15, 0);
uM[15] = baseM[4]; uM[16] = baseM[5]; uM[17] = baseM[6]; uM[18] = 0; uM[19] = baseM[7];
GrPaint uPaint;
uPaint.addColorTextureProcessor(srcProxy, texMatrix, GrSamplerState::ClampBilerp());
auto uFP = GrColorMatrixFragmentProcessor::Make(uM, false, true, false);
uPaint.addColorFragmentProcessor(std::move(uFP));
uPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
uRTC->drawFilledRect(GrNoClip(), std::move(uPaint), GrAA::kNo, SkMatrix::I(),
SkRect::MakeWH(dstW / 2, dstH / 2));
auto uTransfer = uRTC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeWH(uRTC->width(), uRTC->height()));
if (!uTransfer.fTransferBuffer) {
callback(context, nullptr, nullptr);
return;
}
// This matrix generates (r,g,b,a) = (0, 0, 0, v)
float vM[20];
std::fill_n(vM, 15, 0);
vM[15] = baseM[8]; vM[16] = baseM[9]; vM[17] = baseM[10]; vM[18] = 0; vM[19] = baseM[11];
GrPaint vPaint;
vPaint.addColorTextureProcessor(srcProxy, texMatrix, GrSamplerState::ClampBilerp());
auto vFP = GrColorMatrixFragmentProcessor::Make(vM, false, true, false);
vPaint.addColorFragmentProcessor(std::move(vFP));
vPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
vRTC->drawFilledRect(GrNoClip(), std::move(vPaint), GrAA::kNo, SkMatrix::I(),
SkRect::MakeWH(dstW / 2, dstH / 2));
auto vTransfer = vRTC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeWH(vRTC->width(), vRTC->height()));
if (!vTransfer.fTransferBuffer) {
callback(context, nullptr, nullptr);
return;
}
struct FinishContext {
ReadPixelsCallbackYUV420* fClientCallback;
ReadPixelsContext fClientContext;
int fW, fH;
PixelTransferResult fYTransfer;
PixelTransferResult fUTransfer;
PixelTransferResult fVTransfer;
};
// Assumption is that the caller would like to flush. We could take a parameter or require an
// explicit flush from the caller. We'd have to have a way to defer attaching the finish
// callback to GrGpu until after the next flush that flushes our op list, though.
auto* finishContext = new FinishContext{callback,
context,
dstW,
dstH,
std::move(yTransfer),
std::move(uTransfer),
std::move(vTransfer)};
auto finishCallback = [](GrGpuFinishedContext c) {
const auto* context = reinterpret_cast<const FinishContext*>(c);
const void* y = context->fYTransfer.fTransferBuffer->map();
const void* u = context->fUTransfer.fTransferBuffer->map();
const void* v = context->fVTransfer.fTransferBuffer->map();
if (!y || !u || !v) {
if (y) {
context->fYTransfer.fTransferBuffer->unmap();
}
if (u) {
context->fUTransfer.fTransferBuffer->unmap();
}
if (v) {
context->fVTransfer.fTransferBuffer->unmap();
}
(*context->fClientCallback)(context->fClientContext, nullptr, 0);
delete context;
return;
}
size_t w = SkToSizeT(context->fW);
size_t h = SkToSizeT(context->fH);
std::unique_ptr<uint8_t[]> yTemp;
if (context->fYTransfer.fPixelConverter) {
yTemp.reset(new uint8_t[w * h]);
context->fYTransfer.fPixelConverter(yTemp.get(), y);
y = yTemp.get();
}
std::unique_ptr<uint8_t[]> uTemp;
if (context->fUTransfer.fPixelConverter) {
uTemp.reset(new uint8_t[w / 2 * h / 2]);
context->fUTransfer.fPixelConverter(uTemp.get(), u);
u = uTemp.get();
}
std::unique_ptr<uint8_t[]> vTemp;
if (context->fVTransfer.fPixelConverter) {
vTemp.reset(new uint8_t[w / 2 * h / 2]);
context->fVTransfer.fPixelConverter(vTemp.get(), v);
v = vTemp.get();
}
const void* data[] = {y, u, v};
size_t rowBytes[] = {w, w / 2, w / 2};
(*context->fClientCallback)(context->fClientContext, data, rowBytes);
context->fYTransfer.fTransferBuffer->unmap();
context->fUTransfer.fTransferBuffer->unmap();
context->fVTransfer.fTransferBuffer->unmap();
delete context;
};
GrFlushInfo flushInfo;

View File

@ -410,22 +410,21 @@ public:
void drawDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>, const SkRect& bounds);
using ReadPixelsCallback = SkSurface::ReadPixelsCallback;
using ReadPixelsCallbackYUV420 = SkSurface::ReadPixelsCallbackYUV420;
using ReadPixelsContext = SkSurface::ReadPixelsContext;
/**
* Performs an asynchronous read (if possible) into a transfer buffer and then calls callback
* with context. If asynchronous reads are not supported then this is done as a synchronous
* read via readPixels(). The callback is called with the data pointer equal to nullptr on
* failure.
*/
void asyncReadPixels(const SkImageInfo& info, int x, int y, ReadPixelsCallback callback,
ReadPixelsContext context);
/**
* Like asyncReadPixels but first rescales the contents before read back.
*/
using RescaleGamma = SkSurface::RescaleGamma;
// GPU implementation for SkSurface::asyncRescaleAndReadPixels.
void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
SkSurface::RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality, ReadPixelsCallback callback,
ReadPixelsContext context);
RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallback callback, ReadPixelsContext context);
// GPU implementation for SkSurface::asyncRescaleAndReadPixelsYUV420.
void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
sk_sp<SkColorSpace> dstColorSpace, const SkIRect& srcRect,
int dstW, int dstH, RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback,
ReadPixelsContext context);
/**
* After this returns any pending surface IO will be issued to the backend 3D API and
@ -560,10 +559,28 @@ private:
const GrOp& op,
GrXferProcessor::DstProxy* result);
// The rescaling step of asyncRescaleAndReadPixels().
// The rescaling step of asyncRescaleAndReadPixels[YUV420]().
sk_sp<GrRenderTargetContext> rescale(const SkImageInfo& info, const SkIRect& srcRect,
SkSurface::RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality);
// The async read step of asyncRescaleAndReadPixels()
void asyncReadPixels(const SkIRect& rect, SkColorType colorType, ReadPixelsCallback callback,
ReadPixelsContext context);
// Inserts a transfer, part of the implementation of asyncReadPixels and
// asyncRescaleAndReadPixelsYUV420().
struct PixelTransferResult {
using ConversionSignature = void(void* dst, const void* mappedBuffer);
// If null then the transfer could not be performed. Otherwise this buffer will contain
// the pixel data when the transfer is complete.
sk_sp<GrGpuBuffer> fTransferBuffer;
// If this is null then the transfer buffer will contain the data in the requested
// color type. Otherwise, when the transfer is done this must be called to convert
// from the transfer buffer's color type to the requested color type.
std::function<ConversionSignature> fPixelConverter;
};
// Inserts a transfer of rect to a buffer that this call will create.
PixelTransferResult transferPixels(GrColorType colorType, const SkIRect& rect);
GrRenderTargetOpList* getRTOpList();
GrOpList* getOpList() override;

View File

@ -204,6 +204,15 @@ void SkSurface_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info, const
}
}
void SkSurface_Base::onAsyncRescaleAndReadPixelsYUV420(
SkYUVColorSpace yuvColorSpace, sk_sp<SkColorSpace> dstColorSpace, const SkIRect& srcRect,
int dstW, int dstH, RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback, ReadPixelsContext context) {
// TODO: Call non-YUV asyncRescaleAndReadPixels and then make our callback convert to YUV and
// call client's callback.
callback(context, nullptr, nullptr);
}
bool SkSurface_Base::outstandingImageSnapshot() const {
return fCachedImage && !fCachedImage->unique();
}
@ -337,6 +346,20 @@ void SkSurface::asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect
context);
}
void SkSurface::asyncRescaleAndReadPixelsYUV420(
SkYUVColorSpace yuvColorSpace, sk_sp<SkColorSpace> dstColorSpace, const SkIRect& srcRect,
int dstW, int dstH, RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback, ReadPixelsContext context) {
if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || (dstW & 0b1) ||
(dstH & 0b1)) {
callback(context, nullptr, nullptr);
return;
}
asSB(this)->onAsyncRescaleAndReadPixelsYUV420(yuvColorSpace, std::move(dstColorSpace), srcRect,
dstW, dstH, rescaleGamma, rescaleQuality,
callback, context);
}
void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) {
if (pmap.addr() == nullptr || pmap.width() <= 0 || pmap.height() <= 0) {
return;

View File

@ -56,6 +56,16 @@ public:
SkFilterQuality rescaleQuality,
ReadPixelsCallback callback,
ReadPixelsContext context);
/**
* Default implementation does a rescale/read/yuv conversion and then calls the callback.
*/
virtual void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
sk_sp<SkColorSpace> dstColorSpace,
const SkIRect& srcRect, int dstW, int dstH,
RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback,
ReadPixelsContext context);
/**
* Default implementation:

View File

@ -133,14 +133,23 @@ void SkSurface_Gpu::onWritePixels(const SkPixmap& src, int x, int y) {
}
void SkSurface_Gpu::onAsyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
SkSurface::RescaleGamma rescaleGamma,
RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality,
SkSurface::ReadPixelsCallback callback,
SkSurface::ReadPixelsContext context) {
auto* rtc = static_cast<SkSurface_Gpu*>(this)->fDevice->accessRenderTargetContext();
ReadPixelsCallback callback,
ReadPixelsContext context) {
auto* rtc = this->fDevice->accessRenderTargetContext();
rtc->asyncRescaleAndReadPixels(info, srcRect, rescaleGamma, rescaleQuality, callback, context);
}
void SkSurface_Gpu::onAsyncRescaleAndReadPixelsYUV420(
SkYUVColorSpace yuvColorSpace, sk_sp<SkColorSpace> dstColorSpace, const SkIRect& srcRect,
int dstW, int dstH, RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback, ReadPixelsContext context) {
auto* rtc = this->fDevice->accessRenderTargetContext();
rtc->asyncRescaleAndReadPixelsYUV420(yuvColorSpace, std::move(dstColorSpace), srcRect, dstW,
dstH, rescaleGamma, rescaleQuality, callback, context);
}
// Create a new render target and, if necessary, copy the contents of the old
// render target into it. Note that this flushes the SkGpuDevice but
// doesn't force an OpenGL flush.

View File

@ -36,6 +36,14 @@ public:
RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
ReadPixelsCallback callback,
ReadPixelsContext context) override;
void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
sk_sp<SkColorSpace> dstColorSpace,
const SkIRect& srcRect, int dstW, int dstH,
RescaleGamma rescaleGamma,
SkFilterQuality rescaleQuality,
ReadPixelsCallbackYUV420 callback,
ReadPixelsContext context) override;
void onCopyOnWrite(ContentChangeMode) override;
void onDiscard() override;
GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, const GrFlushInfo& info) override;