skia2/gm/image.cpp
Michael Ludwig 10241484aa Fix dithering on drawImage[Rect] calls
Bug: skia:12516
Change-Id: Ibb68558e0f474143df3c6a5fb0fb3d4881529226
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/508677
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2022-02-15 18:59:02 +00:00

499 lines
17 KiB
C++

/*
* Copyright 2011 Google Inc.
*
* 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/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorPriv.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkEncodedImageFormat.h"
#include "include/core/SkFont.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageEncoder.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
#include "include/gpu/GrDirectContext.h"
#include "include/private/SkMalloc.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkWriteBuffer.h"
#include "tools/ToolUtils.h"
#include <functional>
#include <utility>
const SkSamplingOptions gSamplings[] = {
SkSamplingOptions(SkFilterMode::kNearest),
SkSamplingOptions(SkFilterMode::kLinear),
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear),
SkSamplingOptions(SkCubicResampler::Mitchell()),
};
static void drawContents(SkSurface* surface, SkColor fillC) {
SkSize size = SkSize::Make(SkIntToScalar(surface->width()),
SkIntToScalar(surface->height()));
SkCanvas* canvas = surface->getCanvas();
SkScalar stroke = size.fWidth / 10;
SkScalar radius = (size.fWidth - stroke) / 2;
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(fillC);
canvas->drawCircle(size.fWidth/2, size.fHeight/2, radius, paint);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(stroke);
paint.setColor(SK_ColorBLACK);
canvas->drawCircle(size.fWidth/2, size.fHeight/2, radius, paint);
}
static void test_surface(SkCanvas* canvas, SkSurface* surf, bool usePaint) {
drawContents(surf, SK_ColorRED);
sk_sp<SkImage> imgR = surf->makeImageSnapshot();
if (true) {
sk_sp<SkImage> imgR2 = surf->makeImageSnapshot();
SkASSERT(imgR == imgR2);
}
drawContents(surf, SK_ColorGREEN);
sk_sp<SkImage> imgG = surf->makeImageSnapshot();
// since we've drawn after we snapped imgR, imgG will be a different obj
SkASSERT(imgR != imgG);
drawContents(surf, SK_ColorBLUE);
SkSamplingOptions sampling;
SkPaint paint;
canvas->drawImage(imgR, 0, 0, sampling, usePaint ? &paint : nullptr);
canvas->drawImage(imgG, 0, 80, sampling, usePaint ? &paint : nullptr);
surf->draw(canvas, 0, 160, SkSamplingOptions(), usePaint ? &paint : nullptr);
SkRect src1, src2, src3;
src1.setIWH(surf->width(), surf->height());
src2.setLTRB(SkIntToScalar(-surf->width() / 2), SkIntToScalar(-surf->height() / 2),
SkIntToScalar(surf->width()), SkIntToScalar(surf->height()));
src3.setIWH(surf->width() / 2, surf->height() / 2);
SkRect dst1, dst2, dst3, dst4;
dst1.setLTRB(0, 240, 65, 305);
dst2.setLTRB(0, 320, 65, 385);
dst3.setLTRB(0, 400, 65, 465);
dst4.setLTRB(0, 480, 65, 545);
canvas->drawImageRect(imgR, src1, dst1, sampling, usePaint ? &paint : nullptr,
SkCanvas::kStrict_SrcRectConstraint);
canvas->drawImageRect(imgG, src2, dst2, sampling, usePaint ? &paint : nullptr,
SkCanvas::kStrict_SrcRectConstraint);
canvas->drawImageRect(imgR, src3, dst3, sampling, usePaint ? &paint : nullptr,
SkCanvas::kStrict_SrcRectConstraint);
canvas->drawImageRect(imgG, dst4, sampling, usePaint ? &paint : nullptr);
}
class ImageGM : public skiagm::GM {
void* fBuffer;
size_t fBufferSize;
SkSize fSize;
enum {
W = 64,
H = 64,
RB = W * 4 + 8,
};
public:
ImageGM() {
fBufferSize = RB * H;
fBuffer = sk_malloc_throw(fBufferSize);
fSize.set(SkIntToScalar(W), SkIntToScalar(H));
}
~ImageGM() override {
sk_free(fBuffer);
}
protected:
SkString onShortName() override {
return SkString("image-surface");
}
SkISize onISize() override {
return SkISize::Make(960, 1200);
}
void onDraw(SkCanvas* canvas) override {
canvas->scale(2, 2);
SkFont font(ToolUtils::create_portable_typeface(), 8);
canvas->drawString("Original Img", 10, 60, font, SkPaint());
canvas->drawString("Modified Img", 10, 140, font, SkPaint());
canvas->drawString("Cur Surface", 10, 220, font, SkPaint());
canvas->drawString("Full Crop", 10, 300, font, SkPaint());
canvas->drawString("Over-crop", 10, 380, font, SkPaint());
canvas->drawString("Upper-left", 10, 460, font, SkPaint());
canvas->drawString("No Crop", 10, 540, font, SkPaint());
canvas->drawString("Pre-Alloc Img", 80, 10, font, SkPaint());
canvas->drawString("New Alloc Img", 160, 10, font, SkPaint());
canvas->drawString( "GPU", 265, 10, font, SkPaint());
canvas->translate(80, 20);
// since we draw into this directly, we need to start fresh
sk_bzero(fBuffer, fBufferSize);
SkImageInfo info = SkImageInfo::MakeN32Premul(W, H);
sk_sp<SkSurface> surf0(SkSurface::MakeRasterDirect(info, fBuffer, RB));
sk_sp<SkSurface> surf1(SkSurface::MakeRaster(info));
sk_sp<SkSurface> surf2(SkSurface::MakeRenderTarget(canvas->recordingContext(),
SkBudgeted::kNo, info));
test_surface(canvas, surf0.get(), true);
canvas->translate(80, 0);
test_surface(canvas, surf1.get(), true);
if (surf2) {
canvas->translate(80, 0);
test_surface(canvas, surf2.get(), true);
}
}
private:
using INHERITED = skiagm::GM;
};
DEF_GM( return new ImageGM; )
///////////////////////////////////////////////////////////////////////////////////////////////////
static void draw_pixmap(SkCanvas* canvas, const SkPixmap& pmap) {
SkBitmap bitmap;
bitmap.installPixels(pmap);
canvas->drawImage(bitmap.asImage(), 0, 0);
}
static void show_scaled_pixels(SkCanvas* canvas, SkImage* image) {
SkAutoCanvasRestore acr(canvas, true);
canvas->drawImage(image, 0, 0);
canvas->translate(110, 10);
const SkImageInfo info = SkImageInfo::MakeN32Premul(40, 40);
SkAutoPixmapStorage storage;
storage.alloc(info);
const SkImage::CachingHint chints[] = {
SkImage::kAllow_CachingHint, SkImage::kDisallow_CachingHint,
};
for (auto ch : chints) {
canvas->save();
for (auto s : gSamplings) {
if (image->scalePixels(storage, s, ch)) {
draw_pixmap(canvas, storage);
}
canvas->translate(70, 0);
}
canvas->restore();
canvas->translate(0, 45);
}
}
static void draw_contents(SkCanvas* canvas) {
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(20);
canvas->drawCircle(50, 50, 35, paint);
}
static sk_sp<SkImage> make_raster(const SkImageInfo& info,
GrRecordingContext*,
void (*draw)(SkCanvas*)) {
auto surface(SkSurface::MakeRaster(info));
draw(surface->getCanvas());
return surface->makeImageSnapshot();
}
static sk_sp<SkImage> make_picture(const SkImageInfo& info,
GrRecordingContext*,
void (*draw)(SkCanvas*)) {
SkPictureRecorder recorder;
draw(recorder.beginRecording(SkRect::MakeIWH(info.width(), info.height())));
return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
info.dimensions(), nullptr, nullptr, SkImage::BitDepth::kU8,
SkColorSpace::MakeSRGB());
}
static sk_sp<SkImage> make_codec(const SkImageInfo& info,
GrRecordingContext*,
void (*draw)(SkCanvas*)) {
sk_sp<SkImage> image(make_raster(info, nullptr, draw));
return SkImage::MakeFromEncoded(image->encodeToData());
}
static sk_sp<SkImage> make_gpu(const SkImageInfo& info,
GrRecordingContext* ctx,
void (*draw)(SkCanvas*)) {
if (!ctx) {
return nullptr;
}
auto surface(SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info));
if (!surface) {
return nullptr;
}
draw(surface->getCanvas());
return surface->makeImageSnapshot();
}
typedef sk_sp<SkImage> (*ImageMakerProc)(const SkImageInfo&,
GrRecordingContext*,
void (*)(SkCanvas*));
class ScalePixelsGM : public skiagm::GM {
public:
ScalePixelsGM() {}
protected:
SkString onShortName() override {
return SkString("scale-pixels");
}
SkISize onISize() override {
return SkISize::Make(960, 1200);
}
void onDraw(SkCanvas* canvas) override {
const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
const ImageMakerProc procs[] = {
make_codec, make_raster, make_picture, make_codec, make_gpu,
};
for (auto& proc : procs) {
sk_sp<SkImage> image(proc(info, canvas->recordingContext(), draw_contents));
if (image) {
show_scaled_pixels(canvas, image.get());
}
canvas->translate(0, 120);
}
}
private:
using INHERITED = skiagm::GM;
};
DEF_GM( return new ScalePixelsGM; )
///////////////////////////////////////////////////////////////////////////////////////////////////
DEF_SIMPLE_GPU_GM(new_texture_image, rContext, canvas, 280, 60) {
auto dContext = rContext->asDirectContext();
if (!dContext) {
return;
}
auto render_image = [](SkCanvas* canvas) {
canvas->clear(SK_ColorBLUE);
SkPaint paint;
paint.setColor(SK_ColorRED);
canvas->drawRect(SkRect::MakeXYWH(10.f,10.f,10.f,10.f), paint);
paint.setColor(SK_ColorGREEN);
canvas->drawRect(SkRect::MakeXYWH(30.f,10.f,10.f,10.f), paint);
paint.setColor(SK_ColorYELLOW);
canvas->drawRect(SkRect::MakeXYWH(10.f,30.f,10.f,10.f), paint);
paint.setColor(SK_ColorCYAN);
canvas->drawRect(SkRect::MakeXYWH(30.f,30.f,10.f,10.f), paint);
};
static constexpr int kSize = 50;
SkBitmap bmp;
bmp.allocPixels(SkImageInfo::MakeS32(kSize, kSize, kPremul_SkAlphaType));
SkCanvas bmpCanvas(bmp);
render_image(&bmpCanvas);
std::function<sk_sp<SkImage>()> imageFactories[] = {
// Create sw raster image.
[bmp] {
return bmp.asImage();
},
// Create encoded image.
[bmp] {
auto src = SkEncodeBitmap(bmp, SkEncodedImageFormat::kPNG, 100);
return SkImage::MakeFromEncoded(std::move(src));
},
// Create YUV encoded image.
[bmp] {
auto src = SkEncodeBitmap(bmp, SkEncodedImageFormat::kJPEG, 100);
return SkImage::MakeFromEncoded(std::move(src));
},
// Create a picture image.
[render_image] {
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kSize), SkIntToScalar(kSize));
render_image(canvas);
sk_sp<SkColorSpace> srgbColorSpace = SkColorSpace::MakeSRGB();
return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
SkISize::Make(kSize, kSize), nullptr, nullptr,
SkImage::BitDepth::kU8, srgbColorSpace);
},
// Create a texture image
[rContext, render_image]() -> sk_sp<SkImage> {
auto surface(SkSurface::MakeRenderTarget(rContext, SkBudgeted::kYes,
SkImageInfo::MakeS32(kSize, kSize,
kPremul_SkAlphaType)));
if (!surface) {
return nullptr;
}
render_image(surface->getCanvas());
return surface->makeImageSnapshot();
}
};
constexpr SkScalar kPad = 5.f;
canvas->translate(kPad, kPad);
for (const auto& factory : imageFactories) {
sk_sp<SkImage> image(factory());
if (image) {
sk_sp<SkImage> texImage(image->makeTextureImage(dContext));
if (texImage) {
canvas->drawImage(texImage, 0, 0);
}
}
canvas->translate(kSize + kPad, 0);
}
}
static void draw_pixmap(SkCanvas* canvas, const SkPixmap& pm, SkScalar x, SkScalar y) {
canvas->drawImage(SkImage::MakeRasterCopy(pm), x, y);
}
static void slam_ff(const SkPixmap& pm) {
for (int y = 0; y < pm.height(); ++y) {
for (int x = 0; x < pm.width(); ++x) {
*pm.writable_addr32(x, y) = *pm.addr32(x, y) | SkPackARGB32(0xFF, 0, 0, 0);
}
}
}
DEF_SIMPLE_GM(scalepixels_unpremul, canvas, 1080, 280) {
SkImageInfo info = SkImageInfo::MakeN32(16, 16, kUnpremul_SkAlphaType);
SkAutoPixmapStorage pm;
pm.alloc(info);
for (int y = 0; y < 16; ++y) {
for (int x = 0; x < 16; ++x) {
*pm.writable_addr32(x, y) = SkPackARGB32NoCheck(0, (y << 4) | y, (x << 4) | x, 0xFF);
}
}
SkAutoPixmapStorage pm2;
pm2.alloc(SkImageInfo::MakeN32(256, 256, kUnpremul_SkAlphaType));
for (auto s : gSamplings) {
pm.scalePixels(pm2, s);
slam_ff(pm2);
draw_pixmap(canvas, pm2, 10, 10);
canvas->translate(pm2.width() + 10.0f, 0);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkImage> make_lazy_image(SkSurface* surf) {
surf->getCanvas()->drawCircle(100, 100, 100, SkPaint());
sk_sp<SkData> data = surf->makeImageSnapshot()->encodeToData();
if (!data) {
return nullptr;
}
return SkImage::MakeFromEncoded(std::move(data));
}
static sk_sp<SkImage> serial_deserial(SkImage* img) {
SkBinaryWriteBuffer writer;
writer.writeImage(img);
size_t length = writer.bytesWritten();
auto data = SkData::MakeUninitialized(length);
writer.writeToMemory(data->writable_data());
SkReadBuffer reader(data->data(), length);
return reader.readImage();
}
DEF_SIMPLE_GM_CAN_FAIL(image_subset, canvas, errorMsg, 440, 220) {
auto rContext = canvas->recordingContext();
auto dContext = GrAsDirectContext(rContext);
if (!dContext && rContext) {
*errorMsg = "Not supported in DDL mode";
return skiagm::DrawResult::kSkip;
}
SkImageInfo info = SkImageInfo::MakeN32Premul(200, 200, nullptr);
auto surf = ToolUtils::makeSurface(canvas, info, nullptr);
auto img = make_lazy_image(surf.get());
if (!img) {
*errorMsg = "Failed to make lazy image.";
return skiagm::DrawResult::kFail;
}
canvas->drawImage(img, 10, 10);
auto sub = img->makeSubset({100, 100, 200, 200});
canvas->drawImage(sub, 220, 10);
sub = serial_deserial(sub.get());
canvas->drawImage(sub, 220+110, 10);
return skiagm::DrawResult::kOk;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
DEF_SIMPLE_GM(image_dither, canvas, 800, 800) {
// Make a low-res image of a gradient that shows banding if there's no dithering. When up-scaled
// bilerp filtering will emphasize the banding unless the image is drawn with dithering.
auto gradImage = []() -> sk_sp<SkImage> {
SkImageInfo gradImageInfo = SkImageInfo::Make({8, 16}, {SkColorType::kRGBA_8888_SkColorType,
SkAlphaType::kPremul_SkAlphaType,
nullptr});
auto surface = SkSurface::MakeRaster(gradImageInfo);
SkPoint pts[2] = {{0.f, 0.f},
{(float) 2 * gradImageInfo.width(), (float) gradImageInfo.height()}};
const SkColor colors[] = { 0xFF555555, 0xFF444444 };
SkPaint p;
p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
surface->getCanvas()->drawPaint(p);
return surface->makeImageSnapshot();
}();
SkRect imageBounds = SkRect::MakeIWH(gradImage->width(), gradImage->height());
canvas->scale(50, 50);
// 1st image definitely goes through general shader paths so should have dithering
SkPaint p;
p.setShader(gradImage->makeShader(SkSamplingOptions{SkFilterMode::kLinear}));
p.setDither(true);
canvas->drawRect(imageBounds, p);
// 2nd image goes through draw-image fast paths, but dithering should be detected
p.setShader(nullptr);
canvas->translate(imageBounds.width(), 0.f);
canvas->drawImage(gradImage, 0.f, 0.f, SkSamplingOptions{SkFilterMode::kLinear}, &p);
}