e42716032b
No known clients (CanvasKit, Android) want to ignore the exif orientation in an animation. A follow-on CL will deprecate SkAndroidCodec::ExifOrientation, leaving it up to the client (e.g. SkAnimatedImage, hwui/ImageDecoder) to handle the orientation. Add SkEncodedOriginSwapsWidthHeight to assist clients to do so. Update stoplight_animated_image GM. It previously showed using SkAnimatedImage without respecting the orientation, which is no longer supported. The new version replaces the left half of the image with the right. Remove assert that is no longer true. Originally, an SkAnimatedImage was "simple" if it did not have a crop or postProcessor. This is no longer true if has an exif orientation. Add a test that calls the simple constructor and verifies it does not crash. Change-Id: I421fd02700f220fb90458cd03c4431dee7daf399 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/344762 Reviewed-by: Derek Sollenberger <djsollen@google.com> Commit-Queue: Leon Scroggins <scroggo@google.com>
166 lines
6.8 KiB
C++
166 lines
6.8 KiB
C++
/*
|
|
* Copyright 2020 Google LLC
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "gm/gm.h"
|
|
#include "include/android/SkAnimatedImage.h"
|
|
#include "include/codec/SkAndroidCodec.h"
|
|
#include "include/codec/SkCodec.h"
|
|
#include "include/core/SkBlendMode.h"
|
|
#include "include/core/SkCanvas.h"
|
|
#include "include/core/SkData.h"
|
|
#include "include/core/SkPathBuilder.h"
|
|
#include "include/core/SkPathTypes.h"
|
|
#include "include/core/SkPicture.h"
|
|
#include "include/core/SkPictureRecorder.h"
|
|
#include "include/core/SkRRect.h"
|
|
#include "tools/Resources.h"
|
|
|
|
static sk_sp<SkPicture> post_processor(const SkRect& bounds) {
|
|
int radius = (bounds.width() + bounds.height()) / 6;
|
|
SkPathBuilder pathBuilder;
|
|
pathBuilder.setFillType(SkPathFillType::kInverseEvenOdd)
|
|
.addRRect(SkRRect::MakeRectXY(bounds, radius, radius));
|
|
|
|
SkPaint paint;
|
|
paint.setAntiAlias(true);
|
|
paint.setColor(SK_ColorTRANSPARENT);
|
|
paint.setBlendMode(SkBlendMode::kSrc);
|
|
|
|
SkPictureRecorder recorder;
|
|
auto* canvas = recorder.beginRecording(bounds);
|
|
canvas->drawPath(pathBuilder.detach(), paint);
|
|
return recorder.finishRecordingAsPicture();
|
|
}
|
|
|
|
class AnimatedImageGM : public skiagm::GM {
|
|
const char* fPath;
|
|
const char* fName;
|
|
const int fStep;
|
|
const SkIRect fCropRect;
|
|
SkISize fSize;
|
|
int fTranslate;
|
|
sk_sp<SkData> fData;
|
|
|
|
static const int kMaxFrames = 2;
|
|
|
|
void init() {
|
|
if (!fData) {
|
|
fData = GetResourceAsData(fPath);
|
|
auto codec = SkCodec::MakeFromData(fData);
|
|
auto dimensions = codec->dimensions();
|
|
|
|
fTranslate = std::max(dimensions.width(), dimensions.height()) // may be rotated
|
|
* 1.25f // will be scaled up
|
|
+ 2; // padding
|
|
|
|
fSize = { fTranslate * kMaxFrames
|
|
* 2 // crop and no-crop
|
|
* 2, // post-process and no post-process
|
|
fTranslate * 4 // 4 scales
|
|
* 2 }; // newPictureSnapshot and getCurrentFrame
|
|
}
|
|
}
|
|
public:
|
|
AnimatedImageGM(const char* path, const char* name, int step, SkIRect cropRect)
|
|
: fPath(path)
|
|
, fName(name)
|
|
, fStep(step)
|
|
, fCropRect(cropRect)
|
|
, fSize{0, 0}
|
|
, fTranslate(0)
|
|
{}
|
|
~AnimatedImageGM() override = default;
|
|
|
|
SkString onShortName() override {
|
|
return SkStringPrintf("%s_animated_image", fName);
|
|
}
|
|
|
|
SkISize onISize() override {
|
|
this->init();
|
|
return fSize;
|
|
}
|
|
|
|
void onDraw(SkCanvas* canvas) override {
|
|
this->init();
|
|
for (bool usePic : { true, false }) {
|
|
auto drawProc = [canvas, usePic](const sk_sp<SkAnimatedImage>& animatedImage) {
|
|
if (usePic) {
|
|
sk_sp<SkPicture> pic(animatedImage->newPictureSnapshot());
|
|
canvas->drawPicture(pic);
|
|
} else {
|
|
auto image = animatedImage->getCurrentFrame();
|
|
canvas->drawImage(image, 0, 0, nullptr);
|
|
}
|
|
};
|
|
for (float scale : { 1.25f, 1.0f, .75f, .5f }) {
|
|
canvas->save();
|
|
for (bool doCrop : { false, true }) {
|
|
for (bool doPostProcess : { false, true }) {
|
|
auto codec = SkCodec::MakeFromData(fData);
|
|
const auto origin = codec->getOrigin();
|
|
auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
|
|
auto info = androidCodec->getInfo();
|
|
const auto unscaledSize = SkEncodedOriginSwapsWidthHeight(origin)
|
|
? SkISize{ info.height(), info.width() } : info.dimensions();
|
|
|
|
SkISize scaledSize = { SkScalarFloorToInt(unscaledSize.width() * scale) ,
|
|
SkScalarFloorToInt(unscaledSize.height() * scale) };
|
|
info = info.makeDimensions(scaledSize);
|
|
|
|
auto cropRect = SkIRect::MakeSize(scaledSize);
|
|
if (doCrop) {
|
|
auto matrix = SkMatrix::MakeRectToRect(SkRect::Make(unscaledSize),
|
|
SkRect::Make(scaledSize), SkMatrix::kFill_ScaleToFit);
|
|
matrix.preConcat(SkEncodedOriginToMatrix(origin,
|
|
unscaledSize.width(), unscaledSize.height()));
|
|
SkRect cropRectFloat = SkRect::Make(fCropRect);
|
|
matrix.mapRect(&cropRectFloat);
|
|
cropRectFloat.roundOut(&cropRect);
|
|
}
|
|
|
|
sk_sp<SkPicture> postProcessor = doPostProcess
|
|
? post_processor(SkRect::Make(cropRect.size())) : nullptr;
|
|
auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec),
|
|
info, cropRect, std::move(postProcessor));
|
|
animatedImage->setRepetitionCount(0);
|
|
|
|
for (int frame = 0; frame < kMaxFrames; frame++) {
|
|
{
|
|
SkAutoCanvasRestore acr(canvas, doCrop);
|
|
if (doCrop) {
|
|
canvas->translate(cropRect.left(), cropRect.top());
|
|
}
|
|
drawProc(animatedImage);
|
|
}
|
|
|
|
canvas->translate(fTranslate, 0);
|
|
const auto duration = animatedImage->currentFrameDuration();
|
|
if (duration == SkAnimatedImage::kFinished) {
|
|
break;
|
|
}
|
|
for (int i = 0; i < fStep; i++) {
|
|
animatedImage->decodeNextFrame();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
canvas->restore();
|
|
canvas->translate(0, fTranslate);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
DEF_GM( return new AnimatedImageGM("images/stoplight_h.webp", "stoplight", 2,
|
|
// Deliberately not centered in X or Y, and shows all three
|
|
// lights, but otherwise arbitrary.
|
|
SkIRect::MakeLTRB(5, 6, 11, 29)); )
|
|
DEF_GM( return new AnimatedImageGM("images/flightAnim.gif", "flight", 20,
|
|
// Deliberately starts in the upper left corner to exercise
|
|
// a special case, but otherwise arbitrary.
|
|
SkIRect::MakeLTRB(0, 0, 300, 200)); )
|