6fc04f88a8
This is a reland of ce240cc6fd
Original change's description:
> SkSurface asynchronous read APIs allow client to extend pixel lifetime
>
> Previously the pixel data passed to the client was only valid during
> the client's callback. This meant if the client wanted to defer
> processing of the data a copy was necessary.
>
> Now we pass an object to the callback and the pixel lifetime is tied
> to the lifetime of that object.
>
> The object may be holding a GPU transfer buffer mapped. We don't assume
> that the object will be released on the direct GrContext thread. So
> when the object is destroyed it posts a message to a new type,
> GrClientMappedBufferManager, hanging off the direct context. The direct
> context will periodically check for messages and unmap and then unref
> buffers so that they can be reused. Currently this is done in
> GrContext::performDeferredCleanup() and GrDrawingManager::flush().
>
> The old API is kept around for backwards compatibility but it is
> reimplemented as a bridge on top of the new mechanism.
>
> Also a utility function to SkImageInfo is added to directly make a new
> info with a specified dimensions rather than passing the width and
> height separately to makeWH().
>
> Bug: chromium:973403
> Bug: skia:8962
>
> Change-Id: Id5cf04235376170142a48e90d3ecd13fd021a2a6
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/245457
> Reviewed-by: Brian Osman <brianosman@google.com>
> Commit-Queue: Brian Salomon <bsalomon@google.com>
Bug: chromium:973403, skia:8962
Change-Id: I5cecd36276c8b6dc942cf549c7095db2df88530c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/245678
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
302 lines
11 KiB
C++
302 lines
11 KiB
C++
/*
|
|
* Copyright 2018 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "include/codec/SkAndroidCodec.h"
|
|
#include "include/codec/SkCodec.h"
|
|
#include "include/core/SkBitmap.h"
|
|
#include "include/core/SkColor.h"
|
|
#include "include/core/SkColorSpace.h"
|
|
#include "include/core/SkData.h"
|
|
#include "include/core/SkEncodedImageFormat.h"
|
|
#include "include/core/SkImageGenerator.h"
|
|
#include "include/core/SkImageInfo.h"
|
|
#include "include/core/SkRefCnt.h"
|
|
#include "include/core/SkSize.h"
|
|
#include "include/core/SkString.h"
|
|
#include "include/core/SkTypes.h"
|
|
#include "include/third_party/skcms/skcms.h"
|
|
#include "src/codec/SkCodecImageGenerator.h"
|
|
#include "src/core/SkPixmapPriv.h"
|
|
#include "tests/Test.h"
|
|
#include "tools/Resources.h"
|
|
|
|
#include <string.h>
|
|
#include <initializer_list>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
static SkISize times(const SkISize& size, float factor) {
|
|
return { (int) (size.width() * factor), (int) (size.height() * factor) };
|
|
}
|
|
|
|
static SkISize plus(const SkISize& size, int term) {
|
|
return { size.width() + term, size.height() + term };
|
|
}
|
|
|
|
static bool invalid(const SkISize& size) {
|
|
return size.width() < 1 || size.height() < 1;
|
|
}
|
|
|
|
DEF_TEST(AndroidCodec_computeSampleSize, r) {
|
|
if (GetResourcePath().isEmpty()) {
|
|
return;
|
|
}
|
|
for (const char* file : { "images/color_wheel.webp",
|
|
"images/ship.png",
|
|
"images/dog.jpg",
|
|
"images/color_wheel.gif",
|
|
"images/rle.bmp",
|
|
"images/google_chrome.ico",
|
|
"images/mandrill.wbmp",
|
|
#ifdef SK_CODEC_DECODES_RAW
|
|
"images/sample_1mp.dng",
|
|
#endif
|
|
}) {
|
|
auto data = GetResourceAsData(file);
|
|
if (!data) {
|
|
ERRORF(r, "Could not get %s", file);
|
|
continue;
|
|
}
|
|
|
|
auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
|
|
if (!codec) {
|
|
ERRORF(r, "Could not create codec for %s", file);
|
|
continue;
|
|
}
|
|
|
|
const auto dims = codec->getInfo().dimensions();
|
|
const SkISize downscales[] = {
|
|
plus(dims, -1),
|
|
times(dims, .15f),
|
|
times(dims, .6f),
|
|
{ (int32_t) (dims.width() * .25f), (int32_t) (dims.height() * .75f ) },
|
|
{ 1, 1 },
|
|
{ 1, 2 },
|
|
{ 2, 1 },
|
|
{ 0, -1 },
|
|
{ dims.width(), dims.height() - 1 },
|
|
};
|
|
for (SkISize size : downscales) {
|
|
const auto requested = size;
|
|
const int computedSampleSize = codec->computeSampleSize(&size);
|
|
REPORTER_ASSERT(r, size.width() >= 1 && size.height() >= 1);
|
|
if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP) {
|
|
// WebP supports arbitrary down-scaling.
|
|
REPORTER_ASSERT(r, size == requested || invalid(requested));
|
|
} else if (computedSampleSize == 1) {
|
|
REPORTER_ASSERT(r, size == dims);
|
|
} else {
|
|
REPORTER_ASSERT(r, computedSampleSize > 1);
|
|
if (size.width() >= dims.width() || size.height() >= dims.height()) {
|
|
ERRORF(r, "File %s's computed sample size (%i) is bigger than"
|
|
" original? original: %i x %i\tsampled: %i x %i",
|
|
file, computedSampleSize, dims.width(), dims.height(),
|
|
size.width(), size.height());
|
|
}
|
|
REPORTER_ASSERT(r, size.width() >= requested.width() &&
|
|
size.height() >= requested.height());
|
|
REPORTER_ASSERT(r, size.width() < dims.width() &&
|
|
size.height() < dims.height());
|
|
}
|
|
}
|
|
|
|
const SkISize upscales[] = {
|
|
dims, plus(dims, 5), times(dims, 2),
|
|
};
|
|
for (SkISize size : upscales) {
|
|
const int computedSampleSize = codec->computeSampleSize(&size);
|
|
REPORTER_ASSERT(r, computedSampleSize == 1);
|
|
REPORTER_ASSERT(r, dims == size);
|
|
}
|
|
|
|
// This mimics how Android's ImageDecoder uses SkAndroidCodec. A client
|
|
// can choose their dimensions based on calling getSampledDimensions,
|
|
// but the ImageDecoder API takes an arbitrary size. It then uses
|
|
// computeSampleSize to determine the best dimensions and sampleSize.
|
|
// It should return the same dimensions. the sampleSize may be different
|
|
// due to integer division.
|
|
for (int sampleSize : { 1, 2, 3, 4, 8, 16, 32 }) {
|
|
const SkISize sampledDims = codec->getSampledDimensions(sampleSize);
|
|
SkISize size = sampledDims;
|
|
const int computedSampleSize = codec->computeSampleSize(&size);
|
|
if (sampledDims != size) {
|
|
ERRORF(r, "File '%s'->getSampledDimensions(%i) yields computed"
|
|
" sample size of %i\n\tsampledDimensions: %i x %i\t"
|
|
"computed dimensions: %i x %i",
|
|
file, sampleSize, computedSampleSize,
|
|
sampledDims.width(), sampledDims.height(),
|
|
size.width(), size.height());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEF_TEST(AndroidCodec_wide, r) {
|
|
if (GetResourcePath().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const char* path = "images/wide-gamut.png";
|
|
auto data = GetResourceAsData(path);
|
|
if (!data) {
|
|
ERRORF(r, "Missing file %s", path);
|
|
return;
|
|
}
|
|
|
|
auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
|
|
if (!codec) {
|
|
ERRORF(r, "Failed to create codec from %s", path);
|
|
return;
|
|
}
|
|
|
|
auto info = codec->getInfo();
|
|
auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
|
|
if (!cs) {
|
|
ERRORF(r, "%s should have a color space", path);
|
|
return;
|
|
}
|
|
|
|
auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
|
|
REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
|
|
}
|
|
|
|
DEF_TEST(AndroidCodec_P3, r) {
|
|
if (GetResourcePath().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const char* path = "images/purple-displayprofile.png";
|
|
auto data = GetResourceAsData(path);
|
|
if (!data) {
|
|
ERRORF(r, "Missing file %s", path);
|
|
return;
|
|
}
|
|
|
|
auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
|
|
if (!codec) {
|
|
ERRORF(r, "Failed to create codec from %s", path);
|
|
return;
|
|
}
|
|
|
|
auto info = codec->getInfo();
|
|
auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
|
|
if (!cs) {
|
|
ERRORF(r, "%s should have a color space", path);
|
|
return;
|
|
}
|
|
|
|
REPORTER_ASSERT(r, !cs->isSRGB());
|
|
REPORTER_ASSERT(r, cs->gammaCloseToSRGB());
|
|
|
|
skcms_Matrix3x3 matrix;
|
|
cs->toXYZD50(&matrix);
|
|
|
|
static constexpr skcms_Matrix3x3 kExpected = {{
|
|
{ 0.426254272f, 0.369018555f, 0.168914795f },
|
|
{ 0.226013184f, 0.685974121f, 0.0880126953f },
|
|
{ 0.0116729736f, 0.0950927734f, 0.71812439f },
|
|
}};
|
|
REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
|
|
}
|
|
|
|
DEF_TEST(AndroidCodec_orientation, r) {
|
|
if (GetResourcePath().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
for (const char* ext : { "jpg", "webp" })
|
|
for (char i = '1'; i <= '8'; ++i) {
|
|
SkString path = SkStringPrintf("images/orientation/%c.%s", i, ext);
|
|
auto data = GetResourceAsData(path.c_str());
|
|
auto gen = SkCodecImageGenerator::MakeFromEncodedCodec(data);
|
|
if (!gen) {
|
|
ERRORF(r, "failed to decode %s", path.c_str());
|
|
return;
|
|
}
|
|
|
|
// Dimensions after adjusting for the origin.
|
|
const SkISize expectedDims = { 100, 80 };
|
|
|
|
// SkCodecImageGenerator automatically adjusts for the origin.
|
|
REPORTER_ASSERT(r, gen->getInfo().dimensions() == expectedDims);
|
|
|
|
auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data));
|
|
if (!androidCodec) {
|
|
ERRORF(r, "failed to decode %s", path.c_str());
|
|
return;
|
|
}
|
|
|
|
// SkAndroidCodec does not adjust for the origin by default. Dimensions may be reversed.
|
|
if (SkPixmapPriv::ShouldSwapWidthHeight(androidCodec->codec()->getOrigin())) {
|
|
auto swappedDims = SkPixmapPriv::SwapWidthHeight(androidCodec->getInfo()).dimensions();
|
|
REPORTER_ASSERT(r, expectedDims == swappedDims);
|
|
} else {
|
|
REPORTER_ASSERT(r, expectedDims == androidCodec->getInfo().dimensions());
|
|
}
|
|
|
|
// Passing kRespect adjusts for the origin.
|
|
androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)),
|
|
SkAndroidCodec::ExifOrientationBehavior::kRespect);
|
|
auto info = androidCodec->getInfo();
|
|
REPORTER_ASSERT(r, info.dimensions() == expectedDims);
|
|
|
|
SkBitmap fromGenerator;
|
|
fromGenerator.allocPixels(info);
|
|
REPORTER_ASSERT(r, gen->getPixels(info, fromGenerator.getPixels(),
|
|
fromGenerator.rowBytes()));
|
|
|
|
SkBitmap fromAndroidCodec;
|
|
fromAndroidCodec.allocPixels(info);
|
|
auto result = androidCodec->getPixels(info, fromAndroidCodec.getPixels(),
|
|
fromAndroidCodec.rowBytes());
|
|
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
|
|
|
|
for (int i = 0; i < info.width(); ++i)
|
|
for (int j = 0; j < info.height(); ++j) {
|
|
SkColor c1 = *fromGenerator .getAddr32(i, j);
|
|
SkColor c2 = *fromAndroidCodec.getAddr32(i, j);
|
|
if (c1 != c2) {
|
|
ERRORF(r, "Bitmaps for %s do not match starting at position %i, %i\n"
|
|
"\tfromGenerator: %x\tfromAndroidCodec: %x", path.c_str(), i, j,
|
|
c1, c2);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEF_TEST(AndroidCodec_sampledOrientation, r) {
|
|
if (GetResourcePath().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// kRightTop_SkEncodedOrigin = 6, // Rotated 90 CW
|
|
auto path = "images/orientation/6.jpg";
|
|
auto data = GetResourceAsData(path);
|
|
if (!data) {
|
|
ERRORF(r, "Failed to get resource %s", path);
|
|
return;
|
|
}
|
|
|
|
auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)),
|
|
SkAndroidCodec::ExifOrientationBehavior::kRespect);
|
|
constexpr int sampleSize = 7;
|
|
auto sampledDims = androidCodec->getSampledDimensions(sampleSize);
|
|
|
|
SkAndroidCodec::AndroidOptions options;
|
|
options.fSampleSize = sampleSize;
|
|
|
|
SkBitmap bm;
|
|
auto info = androidCodec->getInfo().makeDimensions(sampledDims);
|
|
bm.allocPixels(info);
|
|
|
|
auto result = androidCodec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &options);
|
|
if (result != SkCodec::kSuccess) {
|
|
ERRORF(r, "got result \"%s\"\n", SkCodec::ResultToString(result));
|
|
}
|
|
}
|