skia2/tests/AnimatedImageTest.cpp
Mike Klein 6f02af9b54 remove linear-blended sRGB dst support in software
This is the mechanism we'd want to add back if we add an sRGBA
SkColorType.  This will break the DM "srgb" config hard.

Landing this first should help flush out GMs that are incidentally using
sRGB offscreens.

Updated tests/AnimatedImageTest.cpp to avoid linear sRGB 8888 surfaces.

CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel

Change-Id: Idb5035cf4d60fcd1dc24c303d43a406fc4a603fa
Reviewed-on: https://skia-review.googlesource.com/132261
Commit-Queue: Mike Klein <mtklein@chromium.org>
Reviewed-by: Brian Osman <brianosman@google.com>
2018-06-06 20:35:24 +00:00

368 lines
13 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 "CodecPriv.h"
#include "Resources.h"
#include "SkAndroidCodec.h"
#include "SkAnimatedImage.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkCodec.h"
#include "SkColor.h"
#include "SkData.h"
#include "SkImageInfo.h"
#include "SkPicture.h"
#include "SkRefCnt.h"
#include "SkSize.h"
#include "SkString.h"
#include "SkTypes.h"
#include "SkUnPreMultiply.h"
#include "Test.h"
#include "sk_tool_utils.h"
#include <algorithm>
#include <memory>
#include <vector>
DEF_TEST(AnimatedImage_scaled, r) {
if (GetResourcePath().isEmpty()) {
return;
}
const char* file = "images/alphabetAnim.gif";
auto data = GetResourceAsData(file);
if (!data) {
ERRORF(r, "Could not get %s", file);
return;
}
auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data));
if (!codec) {
ERRORF(r, "Could not create codec for %s", file);
return;
}
// Force the drawable follow its special case that requires scaling.
auto size = codec->getInfo().dimensions();
size.set(size.width() - 5, size.height() - 5);
auto rect = SkIRect::MakeSize(size);
auto image = SkAnimatedImage::Make(std::move(codec), size, rect, nullptr);
if (!image) {
ERRORF(r, "Failed to create animated image for %s", file);
return;
}
// Clear a bitmap to non-transparent and draw to it. pixels that are transparent
// in the image should not replace the original non-transparent color.
SkBitmap bm;
bm.allocPixels(SkImageInfo::MakeN32Premul(size.width(), size.height()));
bm.eraseColor(SK_ColorBLUE);
SkCanvas canvas(bm);
image->draw(&canvas);
for (int i = 0; i < size.width(); ++i)
for (int j = 0; j < size.height(); ++j) {
if (*bm.getAddr32(i, j) == SK_ColorTRANSPARENT) {
ERRORF(r, "Erased color underneath!");
return;
}
}
}
static bool compare_bitmaps(skiatest::Reporter* r,
const char* file,
int expectedFrame,
const SkBitmap& expectedBm,
const SkBitmap& actualBm) {
REPORTER_ASSERT(r, expectedBm.colorType() == actualBm.colorType());
REPORTER_ASSERT(r, expectedBm.dimensions() == actualBm.dimensions());
for (int i = 0; i < actualBm.width(); ++i)
for (int j = 0; j < actualBm.height(); ++j) {
SkColor expected = SkUnPreMultiply::PMColorToColor(*expectedBm.getAddr32(i, j));
SkColor actual = SkUnPreMultiply::PMColorToColor(*actualBm .getAddr32(i, j));
if (expected != actual) {
ERRORF(r, "frame %i of %s does not match at pixel %i, %i!"
" expected %x\tactual: %x",
expectedFrame, file, i, j, expected, actual);
SkString expected_name = SkStringPrintf("expected_%c", '0' + expectedFrame);
SkString actual_name = SkStringPrintf("actual_%c", '0' + expectedFrame);
write_bm(expected_name.c_str(), expectedBm);
write_bm(actual_name.c_str(), actualBm);
return false;
}
}
return true;
}
// Temporary hack to avoid linear sRGB 8888 surfaces.
static SkImageInfo temporarily_sanitize(SkImageInfo info) {
if (info.colorType() == kRGBA_8888_SkColorType ||
info.colorType() == kBGRA_8888_SkColorType) {
if (info.colorSpace() && info.colorSpace()->isSRGB()) {
info = info.makeColorSpace(nullptr);
}
}
return info;
}
DEF_TEST(AnimatedImage_copyOnWrite, r) {
if (GetResourcePath().isEmpty()) {
return;
}
for (const char* file : { "images/alphabetAnim.gif",
"images/colorTables.gif",
"images/webp-animated.webp",
"images/required.webp",
}) {
auto data = GetResourceAsData(file);
if (!data) {
ERRORF(r, "Could not get %s", file);
continue;
}
auto codec = SkCodec::MakeFromData(data);
if (!codec) {
ERRORF(r, "Could not create codec for %s", file);
continue;
}
const auto imageInfo = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
const int frameCount = codec->getFrameCount();
auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
if (!androidCodec) {
ERRORF(r, "Could not create androidCodec for %s", file);
continue;
}
auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
if (!animatedImage) {
ERRORF(r, "Could not create animated image for %s", file);
continue;
}
animatedImage->setRepetitionCount(0);
std::vector<SkBitmap> expected(frameCount);
std::vector<sk_sp<SkPicture>> pictures(frameCount);
for (int i = 0; i < frameCount; i++) {
SkBitmap& bm = expected[i];
bm.allocPixels(temporarily_sanitize(imageInfo));
bm.eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(bm);
pictures[i].reset(animatedImage->newPictureSnapshot());
canvas.drawPicture(pictures[i]);
const auto duration = animatedImage->decodeNextFrame();
// We're attempting to decode i + 1, so decodeNextFrame will return
// kFinished if that is the last frame (or we attempt to decode one
// more).
if (i >= frameCount - 2) {
REPORTER_ASSERT(r, duration == SkAnimatedImage::kFinished);
} else {
REPORTER_ASSERT(r, duration != SkAnimatedImage::kFinished);
}
}
for (int i = 0; i < frameCount; i++) {
SkBitmap test;
test.allocPixels(temporarily_sanitize(imageInfo));
test.eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(test);
canvas.drawPicture(pictures[i]);
compare_bitmaps(r, file, i, expected[i], test);
}
}
}
DEF_TEST(AnimatedImage, r) {
if (GetResourcePath().isEmpty()) {
return;
}
for (const char* file : { "images/alphabetAnim.gif",
"images/colorTables.gif",
"images/webp-animated.webp",
"images/required.webp",
}) {
auto data = GetResourceAsData(file);
if (!data) {
ERRORF(r, "Could not get %s", file);
continue;
}
auto codec = SkCodec::MakeFromData(data);
if (!codec) {
ERRORF(r, "Could not create codec for %s", file);
continue;
}
const int defaultRepetitionCount = codec->getRepetitionCount();
std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
std::vector<SkBitmap> frames(frameInfos.size());
// Used down below for our test image.
const auto imageInfo = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
for (size_t i = 0; i < frameInfos.size(); ++i) {
auto info = codec->getInfo().makeAlphaType(frameInfos[i].fAlphaType);
auto& bm = frames[i];
SkCodec::Options options;
options.fFrameIndex = (int) i;
options.fPriorFrame = frameInfos[i].fRequiredFrame;
if (options.fPriorFrame == SkCodec::kNone) {
bm.allocPixels(info);
bm.eraseColor(0);
} else {
const SkBitmap& priorFrame = frames[options.fPriorFrame];
if (!sk_tool_utils::copy_to(&bm, priorFrame.colorType(), priorFrame)) {
ERRORF(r, "Failed to copy %s frame %i", file, options.fPriorFrame);
options.fPriorFrame = SkCodec::kNone;
}
REPORTER_ASSERT(r, bm.setAlphaType(frameInfos[i].fAlphaType));
}
auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), &options);
if (result != SkCodec::kSuccess) {
ERRORF(r, "error in %s frame %zu: %s", file, i, SkCodec::ResultToString(result));
}
}
auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
if (!androidCodec) {
ERRORF(r, "Could not create androidCodec for %s", file);
continue;
}
auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
if (!animatedImage) {
ERRORF(r, "Could not create animated image for %s", file);
continue;
}
REPORTER_ASSERT(r, defaultRepetitionCount == animatedImage->getRepetitionCount());
auto testDraw = [r, &frames, &imageInfo, file](const sk_sp<SkAnimatedImage>& animatedImage,
int expectedFrame) {
SkBitmap test;
test.allocPixels(temporarily_sanitize(imageInfo));
test.eraseColor(0);
SkCanvas c(test);
animatedImage->draw(&c);
const SkBitmap& frame = frames[expectedFrame];
return compare_bitmaps(r, file, expectedFrame, frame, test);
};
REPORTER_ASSERT(r, animatedImage->currentFrameDuration() == frameInfos[0].fDuration);
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "Did not start with frame 0");
continue;
}
// Start at an arbitrary time.
bool failed = false;
for (size_t i = 1; i < frameInfos.size(); ++i) {
const int frameTime = animatedImage->decodeNextFrame();
REPORTER_ASSERT(r, frameTime == animatedImage->currentFrameDuration());
if (i == frameInfos.size() - 1 && defaultRepetitionCount == 0) {
REPORTER_ASSERT(r, frameTime == SkAnimatedImage::kFinished);
REPORTER_ASSERT(r, animatedImage->isFinished());
} else {
REPORTER_ASSERT(r, frameTime == frameInfos[i].fDuration);
REPORTER_ASSERT(r, !animatedImage->isFinished());
}
if (!testDraw(animatedImage, i)) {
ERRORF(r, "Did not update to %i properly", i);
failed = true;
break;
}
}
if (failed) {
continue;
}
animatedImage->reset();
REPORTER_ASSERT(r, !animatedImage->isFinished());
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "reset failed");
continue;
}
// Test reset from all the frames.
// j is the frame to call reset on.
for (int j = 0; j < (int) frameInfos.size(); ++j) {
if (failed) {
break;
}
// i is the frame to decode.
for (int i = 0; i <= j; ++i) {
if (i == j) {
animatedImage->reset();
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "reset failed for image %s from frame %i",
file, i);
failed = true;
break;
}
} else if (i != 0) {
animatedImage->decodeNextFrame();
if (!testDraw(animatedImage, i)) {
ERRORF(r, "failed to match frame %i in %s on iteration %i",
i, file, j);
failed = true;
break;
}
}
}
}
if (failed) {
continue;
}
for (int loopCount : { 0, 1, 2, 5 }) {
animatedImage = SkAnimatedImage::Make(SkAndroidCodec::MakeFromCodec(
SkCodec::MakeFromData(data)));
animatedImage->setRepetitionCount(loopCount);
REPORTER_ASSERT(r, animatedImage->getRepetitionCount() == loopCount);
for (int loops = 0; loops <= loopCount; loops++) {
if (failed) {
break;
}
REPORTER_ASSERT(r, !animatedImage->isFinished());
for (size_t i = 1; i <= frameInfos.size(); ++i) {
const int frameTime = animatedImage->decodeNextFrame();
if (frameTime == SkAnimatedImage::kFinished) {
if (loops != loopCount) {
ERRORF(r, "%s animation stopped early: loops: %i\tloopCount: %i",
file, loops, loopCount);
failed = true;
}
if (i != frameInfos.size() - 1) {
ERRORF(r, "%s animation stopped early: i: %i\tsize: %i",
file, i, frameInfos.size());
failed = true;
}
break;
}
}
}
if (!animatedImage->isFinished()) {
ERRORF(r, "%s animation should have finished with specified loop count (%i)",
file, loopCount);
}
}
}
}