Adding direct access to SkMipMap levels.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1639693002

Review URL: https://codereview.chromium.org/1639693002
This commit is contained in:
cblume 2016-02-17 14:53:23 -08:00 committed by Commit bot
parent 3bc2624a4b
commit e2412d5738
3 changed files with 173 additions and 7 deletions

View File

@ -8,7 +8,9 @@
#include "SkMipMap.h"
#include "SkBitmap.h"
#include "SkColorPriv.h"
#include "SkMath.h"
#include "SkNx.h"
#include "SkTypes.h"
//
// ColorTypeFilter is the "Type" we pass to some downsample template functions.
@ -249,6 +251,8 @@ SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) {
return nullptr;
}
SkASSERT(countLevels == SkMipMap::ComputeLevelCount(src.width(), src.height()));
size_t storageSize = SkMipMap::AllocLevelsSize(countLevels, size);
if (0 == storageSize) {
return nullptr;
@ -318,6 +322,46 @@ SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) {
return mipmap;
}
int SkMipMap::ComputeLevelCount(int baseWidth, int baseHeight) {
// OpenGL's spec requires that each mipmap level have height/width equal to
// max(1, floor(original_height / 2^i)
// (or original_width) where i is the mipmap level.
// Continue scaling down until both axes are size 1.
//
// This means it maintains isotropic space (both axes scaling down
// at the same rate) until one axis hits size 1.
// At that point, OpenGL continues to scale down into anisotropic space
// (where the scales are not the same between axes).
//
// Skia currently does not go into anisotropic space.
// Once an axis hits size 1 we stop.
// All this means is rather than use the largest axis we will use the
// smallest axis.
const int smallestAxis = SkTMin(baseWidth, baseHeight);
if (smallestAxis < 2) {
// SkMipMap::Build requires a minimum size of 2.
return 0;
}
const int leadingZeros = SkCLZ(static_cast<uint32_t>(smallestAxis));
// If the value 00011010 has 3 leading 0s then it has 5 significant bits
// (the bits which are not leading zeros)
const int significantBits = (sizeof(uint32_t) * 8) - leadingZeros;
// This is making the assumption that the size of a byte is 8 bits
// and that sizeof(uint32_t)'s implementation-defined behavior is 4.
int mipLevelCount = significantBits;
// SkMipMap does not include the base mip level.
// For example, it contains levels 1-x instead of 0-x.
// This is because the image used to create SkMipMap is the base level.
// So subtract 1 from the mip level count.
if (mipLevelCount > 0) {
--mipLevelCount;
}
return mipLevelCount;
}
///////////////////////////////////////////////////////////////////////////////
bool SkMipMap::extractLevel(const SkSize& scaleSize, Level* levelPtr) const {
@ -379,3 +423,22 @@ SkMipMap* SkMipMap::Build(const SkBitmap& src, SkDiscardableFactoryProc fact) {
return Build(srcPixmap, fact);
}
int SkMipMap::countLevels() const {
return fCount;
}
bool SkMipMap::getLevel(int index, Level* levelPtr) const {
if (NULL == fLevels) {
return false;
}
if (index < 0) {
return false;
}
if (index > fCount - 1) {
return false;
}
if (levelPtr) {
*levelPtr = fLevels[index];
}
return true;
}

View File

@ -23,12 +23,18 @@ public:
static SkMipMap* Build(const SkPixmap& src, SkDiscardableFactoryProc);
static SkMipMap* Build(const SkBitmap& src, SkDiscardableFactoryProc);
// This function lets you determine how many levels a SkMipMap will have without
// creating that mipmap.
static int ComputeLevelCount(int baseWidth, int baseHeight);
struct Level {
SkPixmap fPixmap;
SkSize fScale; // < 1.0
};
bool extractLevel(const SkSize& scale, Level*) const;
int countLevels() const;
bool getLevel(int index, Level*) const;
protected:
void onDataChange(void* oldData, void* newData) override {

View File

@ -10,12 +10,8 @@
#include "SkRandom.h"
#include "Test.h"
static void make_bitmap(SkBitmap* bm, SkRandom& rand) {
// for now, Build needs a min size of 2, otherwise it will return nullptr.
// should fix that to support 1 X N, where N > 1 to return non-null.
int w = 2 + rand.nextU() % 1000;
int h = 2 + rand.nextU() % 1000;
bm->allocN32Pixels(w, h);
static void make_bitmap(SkBitmap* bm, int width, int height) {
bm->allocN32Pixels(width, height);
bm->eraseColor(SK_ColorWHITE);
}
@ -24,9 +20,14 @@ DEF_TEST(MipMap, reporter) {
SkRandom rand;
for (int i = 0; i < 500; ++i) {
make_bitmap(&bm, rand);
// for now, Build needs a min size of 2, otherwise it will return nullptr.
// should fix that to support 1 X N, where N > 1 to return non-null.
int width = 2 + rand.nextU() % 1000;
int height = 2 + rand.nextU() % 1000;
make_bitmap(&bm, width, height);
SkAutoTUnref<SkMipMap> mm(SkMipMap::Build(bm, nullptr));
REPORTER_ASSERT(reporter, mm->countLevels() == SkMipMap::ComputeLevelCount(width, height));
REPORTER_ASSERT(reporter, !mm->extractLevel(SkSize::Make(SK_Scalar1, SK_Scalar1),
nullptr));
REPORTER_ASSERT(reporter, !mm->extractLevel(SkSize::Make(SK_Scalar1 * 2, SK_Scalar1 * 2),
@ -55,3 +56,99 @@ DEF_TEST(MipMap, reporter) {
}
}
}
static void test_mipmap_generation(int width, int height, int expectedMipLevelCount,
skiatest::Reporter* reporter) {
SkBitmap bm;
bm.allocN32Pixels(width, height);
bm.eraseColor(SK_ColorWHITE);
SkAutoTUnref<SkMipMap> mm(SkMipMap::Build(bm, nullptr));
const int mipLevelCount = mm->countLevels();
REPORTER_ASSERT(reporter, mipLevelCount == expectedMipLevelCount);
for (int i = 0; i < mipLevelCount; ++i) {
SkMipMap::Level level;
REPORTER_ASSERT(reporter, mm->getLevel(i, &level));
// Make sure the mipmaps contain valid data and that the sizes are correct
REPORTER_ASSERT(reporter, level.fPixmap.addr());
// + 1 because SkMipMap does not include the base mipmap level.
int twoToTheMipLevel = 1 << (i + 1);
int currentWidth = width / twoToTheMipLevel;
int currentHeight = height / twoToTheMipLevel;
REPORTER_ASSERT(reporter, level.fPixmap.width() == currentWidth);
REPORTER_ASSERT(reporter, level.fPixmap.height() == currentHeight);
}
}
DEF_TEST(MipMap_DirectLevelAccess, reporter) {
// create mipmap with invalid size
{
// SkMipMap current requires the dimensions be greater than 2x2
SkBitmap bm;
bm.allocN32Pixels(1, 1);
bm.eraseColor(SK_ColorWHITE);
SkAutoTUnref<SkMipMap> mm(SkMipMap::Build(bm, nullptr));
REPORTER_ASSERT(reporter, mm == nullptr);
}
// check small mipmap's count and levels
// There should be 5 mipmap levels generated:
// 16x16, 8x8, 4x4, 2x2, 1x1
test_mipmap_generation(32, 32, 5, reporter);
// check large mipmap's count and levels
// There should be 9 mipmap levels generated:
// 500x500, 250x250, 125x125, 62x62, 31x31, 15x15, 7x7, 3x3, 1x1
test_mipmap_generation(1000, 1000, 9, reporter);
}
struct LevelCountScenario {
int fWidth;
int fHeight;
int fExpectedLevelCount;
};
DEF_TEST(MipMap_ComputeLevelCount, reporter) {
const LevelCountScenario tests[] = {
// Test mipmaps with negative sizes
{-100, 100, 0},
{100, -100, 0},
{-100, -100, 0},
// Test mipmaps with 0, 1, 2 as dimensions
// (SkMipMap::Build requires a min size of 2)
//
// 0
{0, 100, 0},
{100, 0, 0},
{0, 0, 0},
// 1
{1, 100, 0},
{100, 1, 0},
{1, 1, 0},
// 2
{2, 100, 1},
{100, 2, 1},
{2, 2, 1},
// Test a handful of boundaries such as 63x63 and 64x64
{63, 63, 5},
{64, 64, 6},
{127, 127, 6},
{128, 128, 7},
{255, 255, 7},
{256, 256, 8},
// Test different dimensions, such as 256x64
{64, 129, 6},
{255, 32, 5},
{500, 1000, 8}
};
for (auto& currentTest : tests) {
int levelCount = SkMipMap::ComputeLevelCount(currentTest.fWidth, currentTest.fHeight);
REPORTER_ASSERT(reporter, currentTest.fExpectedLevelCount == levelCount);
}
}