Add getting the size of a given mipmap level.
authorcblume <cblume@chromium.org>
Thu, 2 Jun 2016 16:01:48 +0000 (09:01 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 2 Jun 2016 16:01:48 +0000 (09:01 -0700)
When allocating memory for mipmaps, it would be very handy to know the
dimensions of a given mipmap level.

R=bsalomon@google.com
BUG=578304
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2018283002

Review-Url: https://codereview.chromium.org/2018283002

src/core/SkMipMap.cpp
src/core/SkMipMap.h
tests/MipMapTest.cpp

index c851d64..4c075ea 100644 (file)
@@ -371,23 +371,12 @@ SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) {
     }
     // whip through our loop to compute the exact size needed
     size_t  size = 0;
-    int countLevels = 0;
-    {
-        int width = src.width();
-        int height = src.height();
-        for (;;) {
-            width = SkTMax(1, width >> 1);
-            height = SkTMax(1, height >> 1);
-            size += SkColorTypeMinRowBytes(ct, width) * height;
-            countLevels += 1;
-            if (1 == width && 1 == height) {
-                break;
-            }
-        }
+    int countLevels = ComputeLevelCount(src.width(), src.height());
+    for (int currentMipLevel = countLevels; currentMipLevel > 0; currentMipLevel--) {
+        SkSize mipSize = ComputeLevelSize(src.width(), src.height(), currentMipLevel);
+        size += SkColorTypeMinRowBytes(ct, mipSize.fWidth) * mipSize.fHeight;
     }
 
-    SkASSERT(countLevels == SkMipMap::ComputeLevelCount(src.width(), src.height()));
-
     size_t storageSize = SkMipMap::AllocLevelsSize(countLevels, size);
     if (0 == storageSize) {
         return nullptr;
@@ -507,6 +496,29 @@ int SkMipMap::ComputeLevelCount(int baseWidth, int baseHeight) {
     return mipLevelCount;
 }
 
+SkSize SkMipMap::ComputeLevelSize(int baseWidth, int baseHeight, int level) {
+    if (baseWidth < 1 || baseHeight < 1) {
+        return SkSize::Make(0, 0);
+    }
+
+    int maxLevelCount = ComputeLevelCount(baseWidth, baseHeight);
+    if (level > maxLevelCount || level < 0) {
+        return SkSize::Make(0, 0);
+    }
+    if (level == 0) {
+        return SkSize::Make(baseWidth, 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.
+
+    int width = SkTMax(1, baseWidth >> level);
+    int height = SkTMax(1, baseHeight >> level);
+
+    return SkSize::Make(width, height);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkMipMap::extractLevel(const SkSize& scaleSize, Level* levelPtr) const {
index 928c12d..5cb4ea8 100644 (file)
@@ -23,10 +23,12 @@ 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.
+    // Determines how many levels a SkMipMap will have without creating that mipmap.
     static int ComputeLevelCount(int baseWidth, int baseHeight);
 
+    // Determines the size of a given mipmap level.
+    static SkSize ComputeLevelSize(int baseWidth, int baseHeight, int level);
+
     struct Level {
         SkPixmap    fPixmap;
         SkSize      fScale; // < 1.0
index 467fd34..5070d04 100644 (file)
@@ -150,3 +150,50 @@ DEF_TEST(MipMap_ComputeLevelCount, reporter) {
         REPORTER_ASSERT(reporter, currentTest.fExpectedLevelCount == levelCount);
     }
 }
+
+struct LevelSizeScenario {
+    int fBaseWidth;
+    int fBaseHeight;
+    int fLevel;
+    SkSize fExpectedMipMapLevelSize;
+};
+
+DEF_TEST(MipMap_ComputeLevelSize, reporter) {
+    const LevelSizeScenario tests[] = {
+        // Test mipmaps with negative sizes
+        {-100, 100, 1, SkSize::Make(0, 0)},
+        {100, -100, 1, SkSize::Make(0, 0)},
+        {-100, -100, 1, SkSize::Make(0, 0)},
+
+        // Test mipmaps with 0, 1, 2 as dimensions
+        // (SkMipMap::Build requires a min size of 1)
+        //
+        // 0
+        {0, 100, 1, SkSize::Make(0, 0)},
+        {100, 0, 1, SkSize::Make(0, 0)},
+        {0, 0, 1, SkSize::Make(0, 0)},
+        // 1
+
+        {1, 100, 1, SkSize::Make(1, 50)},
+        {100, 1, 1, SkSize::Make(50, 1)},
+        {1, 1, 1, SkSize::Make(0, 0)},
+        // 2
+        {2, 100, 1, SkSize::Make(1, 50)},
+        {100, 2, 2, SkSize::Make(25, 1)},
+        {2, 2, 1, SkSize::Make(1, 1)},
+
+        // Test a handful of cases
+        {63, 63, 3, SkSize::Make(7, 7)},
+        {64, 64, 3, SkSize::Make(8, 8)},
+        {127, 127, 3, SkSize::Make(15, 15)},
+        {64, 129, 4, SkSize::Make(4, 8)},
+        {255, 32, 7, SkSize::Make(1, 1)},
+        {500, 1000, 2, SkSize::Make(125, 250)},
+    };
+
+    for (auto& currentTest : tests) {
+        SkSize levelSize = SkMipMap::ComputeLevelSize(currentTest.fBaseWidth,
+                                                      currentTest.fBaseHeight, currentTest.fLevel);
+        REPORTER_ASSERT(reporter, currentTest.fExpectedMipMapLevelSize == levelSize);
+    }
+}