SkCodec: add wbmp class

Review URL: https://codereview.chromium.org/1006583005
diff --git a/src/codec/SkCodec_wbmp.cpp b/src/codec/SkCodec_wbmp.cpp
new file mode 100644
index 0000000..465c76d
--- /dev/null
+++ b/src/codec/SkCodec_wbmp.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodec.h"
+#include "SkColorPriv.h"
+#include "SkStream.h"
+#include "SkCodec_wbmp.h"
+
+// http://en.wikipedia.org/wiki/Variable-length_quantity
+static bool read_mbf(SkStream* stream, uint64_t* value) {
+    uint64_t n = 0;
+    uint8_t data;
+    const uint64_t kLimit = 0xFE00000000000000;
+    SkASSERT(kLimit == ~((~static_cast<uint64_t>(0)) >> 7));
+    do {
+        if (n & kLimit) { // Will overflow on shift by 7.
+            return false;
+        }
+        if (stream->read(&data, 1) != 1) {
+            return false;
+        }
+        n = (n << 7) | (data & 0x7F);
+    } while (data & 0x80);
+    *value = n;
+    return true;
+}
+
+static bool read_header(SkStream* stream, SkISize* size) {
+    uint64_t width, height;
+    uint16_t data;
+    if (stream->read(&data, 2) != 2 || data != 0) {
+        return false;
+    }
+    if (!read_mbf(stream, &width) || width > 0xFFFF || !width) {
+        return false;
+    }
+    if (!read_mbf(stream, &height) || width > 0xFFFF || !height) {
+        return false;
+    }
+    if (size) {
+        *size = SkISize::Make(SkToS32(width), SkToS32(height));
+    }
+    return true;
+}
+
+#define BLACK SkPackARGB32NoCheck(0xFF, 0, 0, 0)
+#define WHITE SkPackARGB32NoCheck(0xFF, 0xFF, 0xFF, 0xFF)
+
+#define GRAYSCALE_BLACK 0
+#define GRAYSCALE_WHITE 0xFF
+
+#define RGB565_BLACK 0
+#define RGB565_WHITE 0xFFFF
+
+static SkPMColor bit_to_pmcolor(U8CPU bit) { return bit ? WHITE : BLACK; }
+
+static uint8_t bit_to_bit(U8CPU bit) { return bit; }
+
+static uint8_t bit_to_grayscale(U8CPU bit) {
+    return bit ? GRAYSCALE_WHITE : GRAYSCALE_BLACK;
+}
+
+static uint16_t bit_to_rgb565(U8CPU bit) {
+    return bit ? RGB565_WHITE : RGB565_BLACK;
+}
+
+typedef void (*ExpandProc)(uint8_t*, const uint8_t*, int);
+
+// TODO(halcanary): Add this functionality (grayscale and indexed output) to
+//                  SkSwizzler and use it here.
+template <typename T, T (*TRANSFORM)(U8CPU)>
+static void expand_bits_to_T(uint8_t* dstptr, const uint8_t* src, int bits) {
+    T* dst = reinterpret_cast<T*>(dstptr);
+    int bytes = bits >> 3;
+    for (int i = 0; i < bytes; i++) {
+        U8CPU mask = *src++;
+        for (int j = 0; j < 8; j++) {
+            dst[j] = TRANSFORM((mask >> (7 - j)) & 1);
+        }
+        dst += 8;
+    }
+    bits &= 7;
+    if (bits > 0) {
+        U8CPU mask = *src;
+        do {
+            *dst++ = TRANSFORM((mask >> 7) & 1);
+            mask <<= 1;
+        } while (--bits != 0);
+    }
+}
+
+SkWbmpCodec::SkWbmpCodec(const SkImageInfo& info, SkStream* stream)
+    : INHERITED(info, stream) {}
+
+SkEncodedFormat SkWbmpCodec::onGetEncodedFormat() const {
+    return kWBMP_SkEncodedFormat;
+}
+
+SkImageGenerator::Result SkWbmpCodec::onGetPixels(const SkImageInfo& info,
+                                                  void* pixels,
+                                                  size_t rowBytes,
+                                                  const Options&,
+                                                  SkPMColor ctable[],
+                                                  int* ctableCount) {
+    SkCodec::RewindState rewindState = this->rewindIfNeeded();
+    if (rewindState == kCouldNotRewind_RewindState) {
+        return SkImageGenerator::kCouldNotRewind;
+    } else if (rewindState == kRewound_RewindState) {
+        (void)read_header(this->stream(), NULL);
+    }
+    if (info.dimensions() != this->getInfo().dimensions()) {
+        return SkImageGenerator::kInvalidScale;
+    }
+    ExpandProc proc = NULL;
+    switch (info.colorType()) {
+        case kGray_8_SkColorType:
+            proc = expand_bits_to_T<uint8_t, bit_to_grayscale>;
+            break;
+        case kN32_SkColorType:
+            proc = expand_bits_to_T<SkPMColor, bit_to_pmcolor>;
+            break;
+        case kIndex_8_SkColorType:
+            ctable[0] = BLACK;
+            ctable[1] = WHITE;
+            *ctableCount = 2;
+            proc = expand_bits_to_T<uint8_t, bit_to_bit>;
+            break;
+        case kRGB_565_SkColorType:
+            proc = expand_bits_to_T<uint16_t, bit_to_rgb565>;
+            break;
+        default:
+            return SkImageGenerator::kInvalidConversion;
+    }
+    SkISize size = info.dimensions();
+    uint8_t* dst = static_cast<uint8_t*>(pixels);
+    size_t srcRowBytes = SkAlign8(size.width()) >> 3;
+    SkAutoTMalloc<uint8_t> src(srcRowBytes);
+    for (int y = 0; y < size.height(); ++y) {
+        if (this->stream()->read(src.get(), srcRowBytes) != srcRowBytes) {
+            return SkImageGenerator::kIncompleteInput;
+        }
+        proc(dst, src.get(), size.width());
+        dst += rowBytes;
+    }
+    return SkImageGenerator::kSuccess;
+}
+
+bool SkWbmpCodec::IsWbmp(SkStream* stream) {
+    return read_header(stream, NULL);
+}
+
+SkCodec* SkWbmpCodec::NewFromStream(SkStream* stream) {
+    SkISize size;
+    if (!read_header(stream, &size)) {
+        return NULL;
+    }
+    SkImageInfo info =
+            SkImageInfo::Make(size.width(), size.height(), kGray_8_SkColorType,
+                              kOpaque_SkAlphaType);
+    return SkNEW_ARGS(SkWbmpCodec, (info, stream));
+}