| /* |
| * 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/gpu/GrDirectContext.h" |
| #include "include/private/base/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 draw_contents(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) { |
| draw_contents(surf, SK_ColorRED); |
| sk_sp<SkImage> imgR = surf->makeImageSnapshot(); |
| |
| if (true) { |
| sk_sp<SkImage> imgR2 = surf->makeImageSnapshot(); |
| SkASSERT(imgR == imgR2); |
| } |
| |
| imgR = ToolUtils::MakeTextureImage(canvas, std::move(imgR)); |
| draw_contents(surf, SK_ColorGREEN); |
| sk_sp<SkImage> imgG = ToolUtils::MakeTextureImage(canvas, surf->makeImageSnapshot()); |
| |
| // since we've drawn after we snapped imgR, imgG will be a different obj unless the |
| // gpu context has been abandoned (in which case they will both be null) |
| SkASSERT(imgR != imgG || (!imgR && !imgG)); |
| |
| draw_contents(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(), skgpu::Budgeted::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, skgpu::Budgeted::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_GM_CAN_FAIL(new_texture_image, canvas, errorMsg, 280, 115) { |
| |
| GrDirectContext* dContext = GrAsDirectContext(canvas->recordingContext()); |
| bool isGPU = SkToBool(dContext); |
| |
| #ifdef SK_GRAPHITE_ENABLED |
| skgpu::graphite::Recorder* recorder = canvas->recorder(); |
| isGPU = isGPU || SkToBool(recorder); |
| #endif |
| |
| if (!isGPU) { |
| *errorMsg = skiagm::GM::kErrorMsg_DrawSkippedGpuOnly; |
| return skiagm::DrawResult::kSkip; |
| } |
| |
| 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; |
| SkImageInfo ii = SkImageInfo::Make(kSize, kSize, |
| kRGBA_8888_SkColorType, kPremul_SkAlphaType, |
| SkColorSpace::MakeSRGB()); |
| SkBitmap bmp; |
| bmp.allocPixels(ii); |
| SkCanvas bmpCanvas(bmp); |
| render_image(&bmpCanvas); |
| |
| std::function<sk_sp<SkImage>()> imageFactories[] = { |
| // Create sw raster image. |
| [&] { return bmp.asImage(); }, |
| // Create encoded image. |
| [&] { |
| auto src = SkEncodeBitmap(bmp, SkEncodedImageFormat::kPNG, 100); |
| return SkImage::MakeFromEncoded(std::move(src)); |
| }, |
| // Create YUV encoded image. |
| [&] { |
| auto src = SkEncodeBitmap(bmp, SkEncodedImageFormat::kJPEG, 100); |
| return SkImage::MakeFromEncoded(std::move(src)); |
| }, |
| // Create a picture 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 |
| [&]() -> sk_sp<SkImage> { |
| sk_sp<SkSurface> surface; |
| if (dContext) { |
| surface = SkSurface::MakeRenderTarget(dContext, skgpu::Budgeted::kYes, ii); |
| } else { |
| #ifdef SK_GRAPHITE_ENABLED |
| surface = SkSurface::MakeGraphite(recorder, ii); |
| #endif |
| } |
| |
| 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) { |
| for (auto mm : { false, true }) { |
| sk_sp<SkImage> texImage; |
| if (dContext) { |
| texImage = image->makeTextureImage(dContext, |
| mm ? GrMipmapped::kYes : GrMipmapped::kNo); |
| } else { |
| #ifdef SK_GRAPHITE_ENABLED |
| texImage = image->makeTextureImage(recorder, |
| { mm ? skgpu::graphite::Mipmapped::kYes |
| : skgpu::graphite::Mipmapped::kNo }); |
| #endif |
| } |
| if (texImage) { |
| canvas->drawImage(texImage, 0, mm ? kSize + kPad : 0); |
| } |
| } |
| } |
| canvas->translate(kSize + kPad, 0); |
| } |
| |
| return skiagm::DrawResult::kOk; |
| } |
| |
| 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; |
| } |