Create mipmaps when creating images
Follow-on CLs: - add serialization for mips to pictures - implement for other image types (e.g. lazy(?), gpu) - improve generated mip quality Bug: skia:10411 Change-Id: Id874f170e9cb8ae4405dc4f6249e1ea6274f20aa Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297739 Commit-Queue: Mike Reed <reed@google.com> Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
parent
12c5d29ba6
commit
2fe1569298
@ -320,29 +320,38 @@ class ShowMipLevels3 : public skiagm::GM {
|
||||
|
||||
SkString onShortName() override { return SkString("showmiplevels_explicit"); }
|
||||
|
||||
SkISize onISize() override { return {1290, 990}; }
|
||||
|
||||
SkScalar draw_downscaling(SkCanvas* canvas, SkFilterOptions options) {
|
||||
SkAutoCanvasRestore acr(canvas, true);
|
||||
|
||||
SkPaint paint;
|
||||
SkRect r = {0, 0, 150, 150};
|
||||
for (float scale = 1; scale >= 0.125f; scale *= 0.75f) {
|
||||
SkMatrix matrix = SkMatrix::Scale(scale, scale);
|
||||
paint.setShader(fImg->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
|
||||
options, &matrix));
|
||||
canvas->drawRect(r, paint);
|
||||
// r.offset(r.width() + 10, 0);
|
||||
canvas->translate(r.width() + 10, 0);
|
||||
}
|
||||
return 160;
|
||||
}
|
||||
SkISize onISize() override { return {1130, 970}; }
|
||||
|
||||
void onOnceBeforeDraw() override {
|
||||
fImg = GetResourceAsImage("images/ship.png");
|
||||
fImg = fImg->makeRasterImage(); // makeWithMips only works on raster for now
|
||||
|
||||
const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
|
||||
|
||||
SkMipmapBuilder builder(fImg->imageInfo());
|
||||
for (int i = 0; i < builder.countLevels(); ++i) {
|
||||
auto surf = SkSurface::MakeRasterDirect(builder.level(i));
|
||||
surf->getCanvas()->drawColor(colors[i % SK_ARRAY_COUNT(colors)]);
|
||||
}
|
||||
fImg = fImg->withMipmaps(builder.detach());
|
||||
}
|
||||
|
||||
void onDraw(SkCanvas* canvas) override {
|
||||
DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
|
||||
auto is_recording_canvas = [](SkCanvas* canvas) {
|
||||
SkPixmap pm;
|
||||
if (canvas->peekPixels(&pm)) {
|
||||
return false;
|
||||
}
|
||||
if (canvas->getGrContext()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (is_recording_canvas(canvas)) {
|
||||
// TODO: make explicit mipmaps serialize
|
||||
return DrawResult::kSkip;
|
||||
}
|
||||
|
||||
canvas->drawColor(0xFFDDDDDD);
|
||||
|
||||
const SkSamplingMode samplings[] = {
|
||||
@ -357,11 +366,26 @@ class ShowMipLevels3 : public skiagm::GM {
|
||||
for (auto sa : samplings) {
|
||||
canvas->translate(0, draw_downscaling(canvas, {sa, mm}));
|
||||
}
|
||||
canvas->translate(0, 10);
|
||||
}
|
||||
return DrawResult::kOk;
|
||||
}
|
||||
|
||||
private:
|
||||
SkScalar draw_downscaling(SkCanvas* canvas, SkFilterOptions options) {
|
||||
SkAutoCanvasRestore acr(canvas, true);
|
||||
|
||||
SkPaint paint;
|
||||
SkRect r = {0, 0, 150, 150};
|
||||
for (float scale = 1; scale >= 0.1f; scale *= 0.7f) {
|
||||
SkMatrix matrix = SkMatrix::Scale(scale, scale);
|
||||
paint.setShader(fImg->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
|
||||
options, &matrix));
|
||||
canvas->drawRect(r, paint);
|
||||
canvas->translate(r.width() + 10, 0);
|
||||
}
|
||||
return r.height() + 10;
|
||||
}
|
||||
|
||||
typedef skiagm::GM INHERITED;
|
||||
};
|
||||
DEF_GM( return new ShowMipLevels3; )
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "include/core/SkTileMode.h"
|
||||
|
||||
struct SkMask;
|
||||
class SkMipMap;
|
||||
struct SkIRect;
|
||||
struct SkRect;
|
||||
class SkPaint;
|
||||
@ -1165,7 +1166,9 @@ public:
|
||||
private:
|
||||
sk_sp<SkPixelRef> fPixelRef;
|
||||
SkPixmap fPixmap;
|
||||
sk_sp<SkMipMap> fMips;
|
||||
|
||||
friend class SkImage_Raster;
|
||||
friend class SkReadBuffer; // unflatten
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ class SkData;
|
||||
class SkCanvas;
|
||||
class SkImageFilter;
|
||||
class SkImageGenerator;
|
||||
class SkM44;
|
||||
class SkMipMap;
|
||||
class SkPaint;
|
||||
class SkPicture;
|
||||
class SkSurface;
|
||||
@ -55,6 +55,20 @@ struct SkFilterOptions {
|
||||
SkMipmapMode fMipmap;
|
||||
};
|
||||
|
||||
class SkMipmapBuilder {
|
||||
public:
|
||||
SkMipmapBuilder(const SkImageInfo&);
|
||||
~SkMipmapBuilder();
|
||||
|
||||
int countLevels() const;
|
||||
SkPixmap level(int index) const;
|
||||
|
||||
sk_sp<SkMipMap> detach();
|
||||
|
||||
private:
|
||||
sk_sp<SkMipMap> fMM;
|
||||
};
|
||||
|
||||
/** \class SkImage
|
||||
SkImage describes a two dimensional array of pixels to draw. The pixels may be
|
||||
decoded in a raster bitmap, encoded in a SkPicture or compressed data stream,
|
||||
@ -1184,6 +1198,20 @@ public:
|
||||
*/
|
||||
sk_sp<SkImage> makeSubset(const SkIRect& subset, GrDirectContext* direct = nullptr) const;
|
||||
|
||||
/**
|
||||
* Returns true if the image has mipmap levels.
|
||||
*/
|
||||
bool hasMipmaps() const;
|
||||
|
||||
/**
|
||||
* Returns an image with the same "base" pixels as the this image, but with mipmap levels
|
||||
* as well. If this image already has mipmap levels, they will be replaced with new ones.
|
||||
*
|
||||
* If data == nullptr, the mipmap levels are computed automatically.
|
||||
* If data != nullptr, then the caller has provided the data for each level.
|
||||
*/
|
||||
sk_sp<SkImage> withMipmaps(sk_sp<SkMipMap> data) const;
|
||||
|
||||
/** Returns SkImage backed by GPU texture associated with context. Returned SkImage is
|
||||
compatible with SkSurface created with dstColorSpace. The returned SkImage respects
|
||||
mipMapped setting; if mipMapped equals GrMipMapped::kYes, the backing texture
|
||||
|
@ -9,6 +9,7 @@
|
||||
#define SkSurface_DEFINED
|
||||
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkPixmap.h"
|
||||
#include "include/core/SkRefCnt.h"
|
||||
#include "include/core/SkSurfaceProps.h"
|
||||
|
||||
@ -70,6 +71,11 @@ public:
|
||||
size_t rowBytes,
|
||||
const SkSurfaceProps* surfaceProps = nullptr);
|
||||
|
||||
static sk_sp<SkSurface> MakeRasterDirect(const SkPixmap& pm,
|
||||
const SkSurfaceProps* props = nullptr) {
|
||||
return MakeRasterDirect(pm.info(), pm.writable_addr(), pm.rowBytes(), props);
|
||||
}
|
||||
|
||||
/** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels.
|
||||
releaseProc is called with pixels and context when SkSurface is deleted.
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "src/core/SkConvertPixels.h"
|
||||
#include "src/core/SkMask.h"
|
||||
#include "src/core/SkMaskFilterBase.h"
|
||||
#include "src/core/SkMipMap.h"
|
||||
#include "src/core/SkPixelRefPriv.h"
|
||||
#include "src/core/SkPixmapPriv.h"
|
||||
#include "src/core/SkReadBuffer.h"
|
||||
@ -42,6 +43,7 @@ SkBitmap::SkBitmap() {}
|
||||
SkBitmap::SkBitmap(const SkBitmap& src)
|
||||
: fPixelRef (src.fPixelRef)
|
||||
, fPixmap (src.fPixmap)
|
||||
, fMips (src.fMips)
|
||||
{
|
||||
SkDEBUGCODE(src.validate();)
|
||||
SkDEBUGCODE(this->validate();)
|
||||
@ -50,6 +52,7 @@ SkBitmap::SkBitmap(const SkBitmap& src)
|
||||
SkBitmap::SkBitmap(SkBitmap&& other)
|
||||
: fPixelRef (std::move(other.fPixelRef))
|
||||
, fPixmap (std::move(other.fPixmap))
|
||||
, fMips (std::move(other.fMips))
|
||||
{
|
||||
SkASSERT(!other.fPixelRef);
|
||||
other.fPixmap.reset();
|
||||
@ -61,6 +64,7 @@ SkBitmap& SkBitmap::operator=(const SkBitmap& src) {
|
||||
if (this != &src) {
|
||||
fPixelRef = src.fPixelRef;
|
||||
fPixmap = src.fPixmap;
|
||||
fMips = src.fMips;
|
||||
}
|
||||
SkDEBUGCODE(this->validate();)
|
||||
return *this;
|
||||
@ -70,6 +74,7 @@ SkBitmap& SkBitmap::operator=(SkBitmap&& other) {
|
||||
if (this != &other) {
|
||||
fPixelRef = std::move(other.fPixelRef);
|
||||
fPixmap = std::move(other.fPixmap);
|
||||
fMips = std::move(other.fMips);
|
||||
SkASSERT(!other.fPixelRef);
|
||||
other.fPixmap.reset();
|
||||
}
|
||||
@ -85,6 +90,7 @@ void SkBitmap::swap(SkBitmap& other) {
|
||||
void SkBitmap::reset() {
|
||||
fPixelRef = nullptr; // Free pixels.
|
||||
fPixmap.reset();
|
||||
fMips.reset();
|
||||
}
|
||||
|
||||
void SkBitmap::getBounds(SkRect* bounds) const {
|
||||
|
@ -15,6 +15,18 @@
|
||||
#include "src/core/SkMipMap.h"
|
||||
#include "src/image/SkImage_Base.h"
|
||||
|
||||
// Try to load from the base image, or from the cache
|
||||
static sk_sp<const SkMipMap> try_load_mips(const SkImage_Base* image) {
|
||||
sk_sp<const SkMipMap> mips = image->refMips();
|
||||
if (!mips) {
|
||||
mips.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image)));
|
||||
}
|
||||
if (!mips) {
|
||||
mips.reset(SkMipMapCache::AddAndRef(image));
|
||||
}
|
||||
return mips;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkBitmapController::State* SkBitmapController::RequestBitmap(const SkImage_Base* image,
|
||||
@ -60,12 +72,9 @@ bool SkBitmapController::State::processMediumRequest(const SkImage_Base* image)
|
||||
}
|
||||
|
||||
if (invScaleSize.width() > SK_Scalar1 || invScaleSize.height() > SK_Scalar1) {
|
||||
fCurrMip.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image)));
|
||||
if (nullptr == fCurrMip.get()) {
|
||||
fCurrMip.reset(SkMipMapCache::AddAndRef(image));
|
||||
if (nullptr == fCurrMip.get()) {
|
||||
return false;
|
||||
}
|
||||
fCurrMip = try_load_mips(image);
|
||||
if (!fCurrMip) {
|
||||
return false;
|
||||
}
|
||||
// diagnostic for a crasher...
|
||||
SkASSERT_RELEASE(fCurrMip->data());
|
||||
@ -143,16 +152,11 @@ SkMipmapAccessor::SkMipmapAccessor(const SkImage_Base* image, const SkMatrix& in
|
||||
}
|
||||
// load fCurrMip if needed
|
||||
if (levelNum > 0 || (fResolvedMode == SkMipmapMode::kLinear && lowerWeight > 0)) {
|
||||
// try to load from the cache
|
||||
fCurrMip.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image)));
|
||||
fCurrMip = try_load_mips(image);
|
||||
if (!fCurrMip) {
|
||||
fCurrMip.reset(SkMipMapCache::AddAndRef(image));
|
||||
if (!fCurrMip) {
|
||||
load_upper_from_base();
|
||||
fResolvedMode = SkMipmapMode::kNone;
|
||||
}
|
||||
}
|
||||
if (fCurrMip) {
|
||||
load_upper_from_base();
|
||||
fResolvedMode = SkMipmapMode::kNone;
|
||||
} else {
|
||||
SkMipMap::Level levelRec;
|
||||
|
||||
SkASSERT(fResolvedMode != SkMipmapMode::kNone);
|
||||
|
@ -395,7 +395,8 @@ size_t SkMipMap::AllocLevelsSize(int levelCount, size_t pixelSize) {
|
||||
return SkTo<int32_t>(size);
|
||||
}
|
||||
|
||||
SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) {
|
||||
SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact,
|
||||
bool computeContents) {
|
||||
typedef void FilterProc(void*, const void* srcPtr, size_t srcRB, int count);
|
||||
|
||||
FilterProc* proc_1_2 = nullptr;
|
||||
@ -632,14 +633,16 @@ SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) {
|
||||
SkIntToScalar(height) / src.height());
|
||||
|
||||
const SkPixmap& dstPM = levels[i].fPixmap;
|
||||
const void* srcBasePtr = srcPM.addr();
|
||||
void* dstBasePtr = dstPM.writable_addr();
|
||||
if (computeContents) {
|
||||
const void* srcBasePtr = srcPM.addr();
|
||||
void* dstBasePtr = dstPM.writable_addr();
|
||||
|
||||
const size_t srcRB = srcPM.rowBytes();
|
||||
for (int y = 0; y < height; y++) {
|
||||
proc(dstBasePtr, srcBasePtr, srcRB, width);
|
||||
srcBasePtr = (char*)srcBasePtr + srcRB * 2; // jump two rows
|
||||
dstBasePtr = (char*)dstBasePtr + dstPM.rowBytes();
|
||||
const size_t srcRB = srcPM.rowBytes();
|
||||
for (int y = 0; y < height; y++) {
|
||||
proc(dstBasePtr, srcBasePtr, srcRB, width);
|
||||
srcBasePtr = (char*)srcBasePtr + srcRB * 2; // jump two rows
|
||||
dstBasePtr = (char*)dstBasePtr + dstPM.rowBytes();
|
||||
}
|
||||
}
|
||||
srcPM = dstPM;
|
||||
addr += height * rowBytes;
|
||||
|
@ -29,7 +29,11 @@ typedef SkDiscardableMemory* (*SkDiscardableFactoryProc)(size_t bytes);
|
||||
*/
|
||||
class SkMipMap : public SkCachedData {
|
||||
public:
|
||||
static SkMipMap* Build(const SkPixmap& src, SkDiscardableFactoryProc);
|
||||
// Allocate and fill-in a mipmap. If computeContents is false, we just allocated
|
||||
// and compute the sizes/rowbytes, but leave the pixel-data uninitialized.
|
||||
static SkMipMap* Build(const SkPixmap& src, SkDiscardableFactoryProc,
|
||||
bool computeContents = true);
|
||||
|
||||
static SkMipMap* Build(const SkBitmap& src, SkDiscardableFactoryProc);
|
||||
|
||||
// Determines how many levels a SkMipMap will have without creating that mipmap.
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "src/core/SkImageFilterCache.h"
|
||||
#include "src/core/SkImageFilter_Base.h"
|
||||
#include "src/core/SkImagePriv.h"
|
||||
#include "src/core/SkMipMap.h"
|
||||
#include "src/core/SkNextID.h"
|
||||
#include "src/core/SkSpecialImage.h"
|
||||
#include "src/image/SkImage_Base.h"
|
||||
@ -649,3 +650,41 @@ SkIRect SkImage_getSubset(const SkImage* image) {
|
||||
SkASSERT(image);
|
||||
return as_IB(image)->onGetSubset();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkMipmapBuilder::SkMipmapBuilder(const SkImageInfo& info) {
|
||||
fMM = sk_sp<SkMipMap>(SkMipMap::Build({info, nullptr, 0}, nullptr, false));
|
||||
}
|
||||
|
||||
SkMipmapBuilder::~SkMipmapBuilder() {}
|
||||
|
||||
int SkMipmapBuilder::countLevels() const {
|
||||
return fMM ? fMM->countLevels() : 0;
|
||||
}
|
||||
|
||||
SkPixmap SkMipmapBuilder::level(int index) const {
|
||||
SkPixmap pm;
|
||||
|
||||
SkMipMap::Level level;
|
||||
if (fMM && fMM->getLevel(index, &level)) {
|
||||
pm = level.fPixmap;
|
||||
}
|
||||
return pm;
|
||||
}
|
||||
|
||||
sk_sp<SkMipMap> SkMipmapBuilder::detach() {
|
||||
return std::move(fMM);
|
||||
}
|
||||
|
||||
bool SkImage::hasMipmaps() const {
|
||||
return as_IB(this)->onPeekMips() != nullptr;
|
||||
}
|
||||
|
||||
sk_sp<SkImage> SkImage::withMipmaps(sk_sp<SkMipMap> data) const {
|
||||
auto result = as_IB(this)->onMakeWithMipmaps(std::move(data));
|
||||
if (!result) {
|
||||
result = sk_ref_sp((const_cast<SkImage*>(this)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkSurface.h"
|
||||
#include "src/core/SkMipMap.h"
|
||||
#include <atomic>
|
||||
|
||||
#if SK_SUPPORT_GPU
|
||||
@ -46,6 +47,12 @@ public:
|
||||
virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
|
||||
int srcX, int srcY, CachingHint) const = 0;
|
||||
|
||||
virtual SkMipMap* onPeekMips() const { return nullptr; }
|
||||
|
||||
sk_sp<SkMipMap> refMips() const {
|
||||
return sk_ref_sp(this->onPeekMips());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation does a rescale/read and then calls the callback.
|
||||
*/
|
||||
@ -128,6 +135,11 @@ public:
|
||||
|
||||
virtual sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const = 0;
|
||||
|
||||
// on failure, returns nullptr
|
||||
virtual sk_sp<SkImage> onMakeWithMipmaps(sk_sp<SkMipMap>) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
SkImage_Base(const SkImageInfo& info, uint32_t uniqueID);
|
||||
|
||||
|
@ -123,6 +123,18 @@ public:
|
||||
void onUnpinAsTexture(GrContext*) const override;
|
||||
#endif
|
||||
|
||||
SkMipMap* onPeekMips() const override { return fBitmap.fMips.get(); }
|
||||
|
||||
sk_sp<SkImage> onMakeWithMipmaps(sk_sp<SkMipMap> mips) const override {
|
||||
auto img = new SkImage_Raster(fBitmap);
|
||||
if (mips) {
|
||||
img->fBitmap.fMips = std::move(mips);
|
||||
} else {
|
||||
img->fBitmap.fMips.reset(SkMipMap::Build(fBitmap.pixmap(), nullptr));
|
||||
}
|
||||
return sk_sp<SkImage>(img);
|
||||
}
|
||||
|
||||
private:
|
||||
SkBitmap fBitmap;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "include/utils/SkRandom.h"
|
||||
#include "src/core/SkMipMap.h"
|
||||
#include "tests/Test.h"
|
||||
#include "tools/Resources.h"
|
||||
|
||||
static void make_bitmap(SkBitmap* bm, int width, int height) {
|
||||
bm->allocN32Pixels(width, height);
|
||||
@ -209,3 +210,29 @@ DEF_TEST(MipMap_F16, reporter) {
|
||||
bmp.eraseColor(0);
|
||||
sk_sp<SkMipMap> mipmap(SkMipMap::Build(bmp, nullptr));
|
||||
}
|
||||
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkSurface.h"
|
||||
|
||||
DEF_TEST(image_mip_factory, reporter) {
|
||||
// TODO: what do to about lazy images and mipmaps?
|
||||
auto img = GetResourceAsImage("images/mandrill_128.png")->makeRasterImage();
|
||||
|
||||
REPORTER_ASSERT(reporter, !img->hasMipmaps());
|
||||
auto img1 = img->withMipmaps(nullptr);
|
||||
REPORTER_ASSERT(reporter, img.get() != img1.get());
|
||||
REPORTER_ASSERT(reporter, img1->hasMipmaps());
|
||||
|
||||
SkMipmapBuilder builder(img->imageInfo());
|
||||
int count = builder.countLevels();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SkPixmap pm = builder.level(i);
|
||||
auto surf = SkSurface::MakeRasterDirect(pm);
|
||||
surf->getCanvas()->drawImageRect(img, SkRect::MakeIWH(pm.width(), pm.height()), nullptr);
|
||||
}
|
||||
auto img2 = img->withMipmaps(builder.detach());
|
||||
REPORTER_ASSERT(reporter, !builder.detach());
|
||||
REPORTER_ASSERT(reporter, img.get() != img2.get());
|
||||
REPORTER_ASSERT(reporter, img1.get() != img2.get());
|
||||
REPORTER_ASSERT(reporter, img2->hasMipmaps());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user