Add initial pipeline for decompressors

R=robertphillips@google.com

Author: krajcevski@google.com

Review URL: https://codereview.chromium.org/432503002
diff --git a/src/utils/SkTextureCompressor.cpp b/src/utils/SkTextureCompressor.cpp
index 30fd307..e0b751f 100644
--- a/src/utils/SkTextureCompressor.cpp
+++ b/src/utils/SkTextureCompressor.cpp
@@ -20,15 +20,16 @@
 
 namespace SkTextureCompressor {
 
-void GetBlockDimensions(Format format, int* dimX, int* dimY) {
+void GetBlockDimensions(Format format, int* dimX, int* dimY, bool matchSpec) {
     if (NULL == dimX || NULL == dimY) {
         return;
     }
 
-    if (SkTextureCompressorGetPlatformDims(format, dimX, dimY)) {
+    if (!matchSpec && SkTextureCompressorGetPlatformDims(format, dimX, dimY)) {
         return;
     }
 
+    // No specialized arguments, return the dimensions as they are in the spec.
     switch(format) {
         // These formats are 64 bits per 4x4 block.
         default:
@@ -159,4 +160,30 @@
     return NULL;
 }
 
+bool DecompressBufferFromFormat(uint8_t* dst, int dstRowBytes, const uint8_t* src,
+                                int width, int height, Format format) {
+    int dimX, dimY;
+    GetBlockDimensions(format, &dimX, &dimY, true);
+
+    if (width < 0 || ((width % dimX) != 0) || height < 0 || ((height % dimY) != 0)) {
+        return false;
+    }
+
+    switch(format) {
+        case kLATC_Format:
+            DecompressLATC(dst, dstRowBytes, src, width, height);
+            return true;
+
+        case kR11_EAC_Format:
+            DecompressR11EAC(dst, dstRowBytes, src, width, height);
+            return true;
+
+        case kASTC_12x12_Format:
+            // TODO(krajcevski) .. right now just fall through and return false.
+            return false;
+    }
+
+    return false;
+}
+
 }  // namespace SkTextureCompressor
diff --git a/src/utils/SkTextureCompressor.h b/src/utils/SkTextureCompressor.h
index c6305ba..eac8c5e 100644
--- a/src/utils/SkTextureCompressor.h
+++ b/src/utils/SkTextureCompressor.h
@@ -44,6 +44,24 @@
                                 int width, int height, int rowBytes, Format format,
                                 bool opt = true /* Use optimization if available */);
 
+    // Decompresses the given src data from the format specified into the
+    // destination buffer. The width and height of the data passed corresponds
+    // to the width and height of the uncompressed image. The destination buffer (dst)
+    // is assumed to be large enough to hold the entire decompressed image. The
+    // decompressed image colors are determined based on the passed format:
+    //
+    // LATC -> Alpha 8
+    // R11_EAC -> Alpha 8
+    // ASTC -> RGBA
+    //
+    // Note, CompressBufferToFormat compresses A8 data into ASTC. However,
+    // general ASTC data encodes RGBA data, so that is what the decompressor
+    // operates on.
+    //
+    // Returns true if successfully decompresses the src data.
+    bool DecompressBufferFromFormat(uint8_t* dst, int dstRowBytes, const uint8_t* src,
+                                    int width, int height, Format format);
+
     // This typedef defines what the nominal aspects of a compression function
     // are. The typedef is not meant to be used by clients of the API, but rather
     // allows SIMD optimized compression functions to be implemented.
@@ -57,10 +75,12 @@
                                       Format format);
 
     // Returns the desired dimensions of the block size for the given format. These dimensions
-    // don't necessarily correspond to the hardware-specified dimensions, since there may
-    // be specialized algorithms that operate on multiple blocks at once. These dimensions
-    // reflect that optimization and return the appropriate operable dimensions.
-    void GetBlockDimensions(Format format, int* dimX, int* dimY);
+    // don't necessarily correspond to the specification's dimensions, since there may
+    // be specialized algorithms that operate on multiple blocks at once. If the
+    // flag 'matchSpec' is true, then the actual dimensions from the specification are
+    // returned. If the flag is false, then these dimensions reflect the appropriate operable
+    // dimensions of the compression functions.
+    void GetBlockDimensions(Format format, int* dimX, int* dimY, bool matchSpec = false);
 }
 
 #endif
diff --git a/src/utils/SkTextureCompressor_LATC.cpp b/src/utils/SkTextureCompressor_LATC.cpp
index 9d42b4f..5db0fc6 100644
--- a/src/utils/SkTextureCompressor_LATC.cpp
+++ b/src/utils/SkTextureCompressor_LATC.cpp
@@ -17,6 +17,46 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
+// Generates an LATC palette. LATC constructs
+// a palette of eight colors from LUM0 and LUM1 using the algorithm:
+//
+// LUM0,              if lum0 > lum1 and code(x,y) == 0
+// LUM1,              if lum0 > lum1 and code(x,y) == 1
+// (6*LUM0+  LUM1)/7, if lum0 > lum1 and code(x,y) == 2
+// (5*LUM0+2*LUM1)/7, if lum0 > lum1 and code(x,y) == 3
+// (4*LUM0+3*LUM1)/7, if lum0 > lum1 and code(x,y) == 4
+// (3*LUM0+4*LUM1)/7, if lum0 > lum1 and code(x,y) == 5
+// (2*LUM0+5*LUM1)/7, if lum0 > lum1 and code(x,y) == 6
+// (  LUM0+6*LUM1)/7, if lum0 > lum1 and code(x,y) == 7
+//
+// LUM0,              if lum0 <= lum1 and code(x,y) == 0
+// LUM1,              if lum0 <= lum1 and code(x,y) == 1
+// (4*LUM0+  LUM1)/5, if lum0 <= lum1 and code(x,y) == 2
+// (3*LUM0+2*LUM1)/5, if lum0 <= lum1 and code(x,y) == 3
+// (2*LUM0+3*LUM1)/5, if lum0 <= lum1 and code(x,y) == 4
+// (  LUM0+4*LUM1)/5, if lum0 <= lum1 and code(x,y) == 5
+// 0,                 if lum0 <= lum1 and code(x,y) == 6
+// 255,               if lum0 <= lum1 and code(x,y) == 7
+
+static const int kLATCPaletteSize = 8;
+static void generate_latc_palette(uint8_t palette[], uint8_t lum0, uint8_t lum1) {
+    palette[0] = lum0;
+    palette[1] = lum1;
+    if (lum0 > lum1) {
+        for (int i = 1; i < 7; i++) {
+            palette[i+1] = ((7-i)*lum0 + i*lum1) / 7;
+        }
+    } else {
+        for (int i = 1; i < 5; i++) {
+            palette[i+1] = ((5-i)*lum0 + i*lum1) / 5;
+        }
+        palette[6] = 0;
+        palette[7] = 255;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 #if COMPRESS_LATC_SLOW
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -79,47 +119,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 // LATC compressed texels down into square 4x4 blocks
-static const int kLATCPaletteSize = 8;
 static const int kLATCBlockSize = 4;
 static const int kLATCPixelsPerBlock = kLATCBlockSize * kLATCBlockSize;
 
-// Generates an LATC palette. LATC constructs
-// a palette of eight colors from LUM0 and LUM1 using the algorithm:
-//
-// LUM0,              if lum0 > lum1 and code(x,y) == 0
-// LUM1,              if lum0 > lum1 and code(x,y) == 1
-// (6*LUM0+  LUM1)/7, if lum0 > lum1 and code(x,y) == 2
-// (5*LUM0+2*LUM1)/7, if lum0 > lum1 and code(x,y) == 3
-// (4*LUM0+3*LUM1)/7, if lum0 > lum1 and code(x,y) == 4
-// (3*LUM0+4*LUM1)/7, if lum0 > lum1 and code(x,y) == 5
-// (2*LUM0+5*LUM1)/7, if lum0 > lum1 and code(x,y) == 6
-// (  LUM0+6*LUM1)/7, if lum0 > lum1 and code(x,y) == 7
-//
-// LUM0,              if lum0 <= lum1 and code(x,y) == 0
-// LUM1,              if lum0 <= lum1 and code(x,y) == 1
-// (4*LUM0+  LUM1)/5, if lum0 <= lum1 and code(x,y) == 2
-// (3*LUM0+2*LUM1)/5, if lum0 <= lum1 and code(x,y) == 3
-// (2*LUM0+3*LUM1)/5, if lum0 <= lum1 and code(x,y) == 4
-// (  LUM0+4*LUM1)/5, if lum0 <= lum1 and code(x,y) == 5
-// 0,                 if lum0 <= lum1 and code(x,y) == 6
-// 255,               if lum0 <= lum1 and code(x,y) == 7
-
-static void generate_latc_palette(uint8_t palette[], uint8_t lum0, uint8_t lum1) {
-    palette[0] = lum0;
-    palette[1] = lum1;
-    if (lum0 > lum1) {
-        for (int i = 1; i < 7; i++) {
-            palette[i+1] = ((7-i)*lum0 + i*lum1) / 7;
-        }
-    } else {
-        for (int i = 1; i < 5; i++) {
-            palette[i+1] = ((5-i)*lum0 + i*lum1) / 5;
-        }
-        palette[6] = 0;
-        palette[7] = 255;
-    }
-}
-
 // Compress a block by using the bounding box of the pixels. It is assumed that
 // there are no extremal pixels in this block otherwise we would have used
 // compressBlockBBIgnoreExtremal.
@@ -393,6 +395,24 @@
 
 #endif  // COMPRESS_LATC_FAST
 
+void decompress_latc_block(uint8_t* dst, int dstRowBytes, const uint8_t* src) {
+    uint64_t block = SkEndian_SwapLE64(*(reinterpret_cast<const uint64_t *>(src)));
+    uint8_t lum0 = block & 0xFF;
+    uint8_t lum1 = (block >> 8) & 0xFF;
+
+    uint8_t palette[kLATCPaletteSize];
+    generate_latc_palette(palette, lum0, lum1);
+
+    block >>= 16;
+    for (int j = 0; j < 4; ++j) {
+        for (int i = 0; i < 4; ++i) {
+            dst[i] = palette[block & 0x7];
+            block >>= 3;
+        }
+        dst += dstRowBytes;
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 namespace SkTextureCompressor {
@@ -418,4 +438,14 @@
 #endif
 }
 
+void DecompressLATC(uint8_t* dst, int dstRowBytes, const uint8_t* src, int width, int height) {
+    for (int j = 0; j < height; j += 4) {
+        for (int i = 0; i < width; i += 4) {
+            decompress_latc_block(dst + i, dstRowBytes, src);
+            src += 8;
+        }
+        dst += 4 * dstRowBytes;
+    }
+}
+
 }  // SkTextureCompressor
diff --git a/src/utils/SkTextureCompressor_LATC.h b/src/utils/SkTextureCompressor_LATC.h
index e3446b5..6ee2ff6 100644
--- a/src/utils/SkTextureCompressor_LATC.h
+++ b/src/utils/SkTextureCompressor_LATC.h
@@ -16,6 +16,8 @@
                           int width, int height, int rowBytes);
 
     SkBlitter* CreateLATCBlitter(int width, int height, void* outputBuffer);
+
+    void DecompressLATC(uint8_t* dst, int dstRowBytes, const uint8_t* src, int width, int height);
 }
 
 #endif  // SkTextureCompressor_LATC_DEFINED
diff --git a/src/utils/SkTextureCompressor_R11EAC.cpp b/src/utils/SkTextureCompressor_R11EAC.cpp
index 982fb01..7baa219 100644
--- a/src/utils/SkTextureCompressor_R11EAC.cpp
+++ b/src/utils/SkTextureCompressor_R11EAC.cpp
@@ -29,8 +29,6 @@
 // If mul is zero, then we set mul = 1/8, so that the formula becomes
 // clamp[0, 2047](base_cw * 8 + 4 + mod_val)
 
-#if COMPRESS_R11_EAC_SLOW
-
 static const int kNumR11EACPalettes = 16;
 static const int kR11EACPaletteSize = 8;
 static const int kR11EACModifierPalettes[kNumR11EACPalettes][kR11EACPaletteSize] = {
@@ -52,6 +50,8 @@
     {-3, -5, -7, -9, 2, 4, 6, 8}
 };
 
+#if COMPRESS_R11_EAC_SLOW
+
 // Pack the base codeword, palette, and multiplier into the 64 bits necessary
 // to decode it.
 static uint64_t pack_r11eac_block(uint16_t base_cw, uint16_t palette, uint16_t multiplier,
@@ -557,6 +557,38 @@
                              static_cast<uint64_t>(packedIndexColumn3));
 }
 
+static inline int get_r11_eac_index(uint64_t block, int x, int y) {
+    SkASSERT(x >= 0 && x < 4);
+    SkASSERT(y >= 0 && y < 4);
+    const int idx = x*4 + y;
+    return (block >> ((15-idx)*3)) & 0x7;
+}
+
+static void decompress_r11_eac_block(uint8_t* dst, int dstRowBytes, const uint8_t* src) {
+    const uint64_t block = SkEndian_SwapBE64(*(reinterpret_cast<const uint64_t *>(src)));
+
+    const int base_cw = (block >> 56) & 0xFF;
+    const int mod = (block >> 52) & 0xF;
+    const int palette_idx = (block >> 48) & 0xF;
+
+    const int* palette = kR11EACModifierPalettes[palette_idx];
+
+    for (int j = 0; j < 4; ++j) {
+        for (int i = 0; i < 4; ++i) {
+            const int idx = get_r11_eac_index(block, i, j);
+            const int val = base_cw*8 + 4 + palette[idx]*mod*8;
+            if (val < 0) {
+                dst[i] = 0;
+            } else if (val > 2047) {
+                dst[i] = 0xFF;
+            } else {
+                dst[i] = (val >> 3) & 0xFF;
+            }
+        }
+        dst += dstRowBytes;
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 namespace SkTextureCompressor {
@@ -582,4 +614,14 @@
         (width, height, outputBuffer);
 }
 
+void DecompressR11EAC(uint8_t* dst, int dstRowBytes, const uint8_t* src, int width, int height) {
+    for (int j = 0; j < height; j += 4) {
+        for (int i = 0; i < width; i += 4) {
+            decompress_r11_eac_block(dst + i, dstRowBytes, src);
+            src += 8;
+        }
+        dst += 4 * dstRowBytes;
+    }    
+}
+
 }  // namespace SkTextureCompressor
diff --git a/src/utils/SkTextureCompressor_R11EAC.h b/src/utils/SkTextureCompressor_R11EAC.h
index 5f981ef..1dd8e39 100644
--- a/src/utils/SkTextureCompressor_R11EAC.h
+++ b/src/utils/SkTextureCompressor_R11EAC.h
@@ -16,6 +16,8 @@
                             int width, int height, int rowBytes);
 
     SkBlitter* CreateR11EACBlitter(int width, int height, void* outputBuffer);
+
+    void DecompressR11EAC(uint8_t* dst, int dstRB, const uint8_t* src, int width, int height);
 }
 
 #endif  // SkTextureCompressor_R11EAC_DEFINED
diff --git a/tests/TextureCompressionTest.cpp b/tests/TextureCompressionTest.cpp
index 503605b..7313487 100644
--- a/tests/TextureCompressionTest.cpp
+++ b/tests/TextureCompressionTest.cpp
@@ -75,6 +75,86 @@
 }
 
 /**
+ * Make sure that if you compress a texture with alternating black/white pixels, and
+ * then decompress it, you get what you started with.
+ */
+DEF_TEST(CompressCheckerboard, reporter) {
+    SkBitmap bitmap;
+    static const int kWidth = 12;
+    static const int kHeight = 12;
+    SkImageInfo info = SkImageInfo::MakeA8(kWidth, kHeight);
+
+    // ASTC is at most 12x12, and any dimension divisible by 12 is also divisible
+    // by 4, which is the dimensions of R11_EAC and LATC. In the future, we might
+    // support additional variants of ASTC, such as 5x6 and 8x8, in which case this would
+    // need to be updated.
+    REPORTER_ASSERT(reporter, kWidth % 12 == 0);
+    REPORTER_ASSERT(reporter, kHeight % 12 == 0);
+
+    bool setInfoSuccess = bitmap.setInfo(info);
+    REPORTER_ASSERT(reporter, setInfoSuccess);
+
+    bool allocPixelsSuccess = bitmap.allocPixels(info);
+    REPORTER_ASSERT(reporter, allocPixelsSuccess);
+
+    bitmap.lockPixels();
+    uint8_t* pixels = reinterpret_cast<uint8_t*>(bitmap.getPixels());
+    REPORTER_ASSERT(reporter, NULL != pixels);
+
+    for (int y = 0; y < kHeight; ++y) {
+        for (int x = 0; x < kWidth; ++x) {
+            if ((x ^ y) & 1) {
+                pixels[x] = 0xFF;
+            } else {
+                pixels[x] = 0;
+            }
+        }
+        pixels += bitmap.rowBytes();
+    }
+    bitmap.unlockPixels();
+
+    SkAutoMalloc decompMemory(kWidth*kHeight);
+    uint8_t* decompBuffer = reinterpret_cast<uint8_t*>(decompMemory.get());
+    REPORTER_ASSERT(reporter, NULL != decompBuffer);
+    if (NULL == decompBuffer) {
+        return;
+    }
+
+    for (int i = 0; i < SkTextureCompressor::kFormatCnt; ++i) {
+        const SkTextureCompressor::Format fmt = static_cast<SkTextureCompressor::Format>(i);
+
+        // ASTC is for RGBA data, and the decompressed buffer
+        // won't match the size and contents of the original.
+        // TODO: Create separate tests for RGB and RGBA data once
+        // ASTC decompression is implemented.
+        if (SkTextureCompressor::kASTC_12x12_Format == fmt) {
+            continue;
+        }
+
+        SkAutoDataUnref data(SkTextureCompressor::CompressBitmapToFormat(bitmap, fmt));
+        REPORTER_ASSERT(reporter, NULL != data);
+
+        bool decompResult =
+            SkTextureCompressor::DecompressBufferFromFormat(
+                decompBuffer, kWidth,
+                data->bytes(),
+                kWidth, kHeight, fmt);
+        REPORTER_ASSERT(reporter, decompResult);
+
+        bitmap.lockPixels();
+        pixels = reinterpret_cast<uint8_t*>(bitmap.getPixels());
+        REPORTER_ASSERT(reporter, NULL != pixels);
+
+        for (int y = 0; y < kHeight; ++y) {
+            for (int x = 0; x < kWidth; ++x) {
+                bool ok = pixels[y*bitmap.rowBytes() + x] == decompBuffer[y*kWidth + x];
+                REPORTER_ASSERT(reporter, ok);
+            }
+        }
+    }
+}
+
+/**
  * Make sure that if we pass in a solid color bitmap that we get the appropriate results
  */
 DEF_TEST(CompressLATC, reporter) {