michael@0: michael@0: /* michael@0: * Copyright 2007 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: // Author: cevans@google.com (Chris Evans) michael@0: michael@0: #include "bmpdecoderhelper.h" michael@0: michael@0: namespace image_codec { michael@0: michael@0: static const int kBmpHeaderSize = 14; michael@0: static const int kBmpInfoSize = 40; michael@0: static const int kBmpOS2InfoSize = 12; michael@0: static const int kMaxDim = SHRT_MAX / 2; michael@0: michael@0: bool BmpDecoderHelper::DecodeImage(const char* p, michael@0: size_t len, michael@0: int max_pixels, michael@0: BmpDecoderCallback* callback) { michael@0: data_ = reinterpret_cast(p); michael@0: pos_ = 0; michael@0: len_ = len; michael@0: inverted_ = true; michael@0: // Parse the header structure. michael@0: if (len < kBmpHeaderSize + 4) { michael@0: return false; michael@0: } michael@0: GetShort(); // Signature. michael@0: GetInt(); // Size. michael@0: GetInt(); // Reserved. michael@0: int offset = GetInt(); michael@0: // Parse the info structure. michael@0: int infoSize = GetInt(); michael@0: if (infoSize != kBmpOS2InfoSize && infoSize < kBmpInfoSize) { michael@0: return false; michael@0: } michael@0: int cols = 0; michael@0: int comp = 0; michael@0: int colLen = 4; michael@0: if (infoSize >= kBmpInfoSize) { michael@0: if (len < kBmpHeaderSize + kBmpInfoSize) { michael@0: return false; michael@0: } michael@0: width_ = GetInt(); michael@0: height_ = GetInt(); michael@0: GetShort(); // Planes. michael@0: bpp_ = GetShort(); michael@0: comp = GetInt(); michael@0: GetInt(); // Size. michael@0: GetInt(); // XPPM. michael@0: GetInt(); // YPPM. michael@0: cols = GetInt(); michael@0: GetInt(); // Important colours. michael@0: } else { michael@0: if (len < kBmpHeaderSize + kBmpOS2InfoSize) { michael@0: return false; michael@0: } michael@0: colLen = 3; michael@0: width_ = GetShort(); michael@0: height_ = GetShort(); michael@0: GetShort(); // Planes. michael@0: bpp_ = GetShort(); michael@0: } michael@0: if (height_ < 0) { michael@0: height_ = -height_; michael@0: inverted_ = false; michael@0: } michael@0: if (width_ <= 0 || width_ > kMaxDim || height_ <= 0 || height_ > kMaxDim) { michael@0: return false; michael@0: } michael@0: if (width_ * height_ > max_pixels) { michael@0: return false; michael@0: } michael@0: if (cols < 0 || cols > 256) { michael@0: return false; michael@0: } michael@0: // Allocate then read in the colour map. michael@0: if (cols == 0 && bpp_ <= 8) { michael@0: cols = 1 << bpp_; michael@0: } michael@0: if (bpp_ <= 8 || cols > 0) { michael@0: uint8* colBuf = new uint8[256 * 3]; michael@0: memset(colBuf, '\0', 256 * 3); michael@0: colTab_.reset(colBuf); michael@0: } michael@0: if (cols > 0) { michael@0: if (pos_ + (cols * colLen) > len_) { michael@0: return false; michael@0: } michael@0: for (int i = 0; i < cols; ++i) { michael@0: int base = i * 3; michael@0: colTab_[base + 2] = GetByte(); michael@0: colTab_[base + 1] = GetByte(); michael@0: colTab_[base] = GetByte(); michael@0: if (colLen == 4) { michael@0: GetByte(); michael@0: } michael@0: } michael@0: } michael@0: // Read in the compression data if necessary. michael@0: redBits_ = 0x7c00; michael@0: greenBits_ = 0x03e0; michael@0: blueBits_ = 0x001f; michael@0: bool rle = false; michael@0: if (comp == 1 || comp == 2) { michael@0: rle = true; michael@0: } else if (comp == 3) { michael@0: if (pos_ + 12 > len_) { michael@0: return false; michael@0: } michael@0: redBits_ = GetInt() & 0xffff; michael@0: greenBits_ = GetInt() & 0xffff; michael@0: blueBits_ = GetInt() & 0xffff; michael@0: } michael@0: redShiftRight_ = CalcShiftRight(redBits_); michael@0: greenShiftRight_ = CalcShiftRight(greenBits_); michael@0: blueShiftRight_ = CalcShiftRight(blueBits_); michael@0: redShiftLeft_ = CalcShiftLeft(redBits_); michael@0: greenShiftLeft_ = CalcShiftLeft(greenBits_); michael@0: blueShiftLeft_ = CalcShiftLeft(blueBits_); michael@0: rowPad_ = 0; michael@0: pixelPad_ = 0; michael@0: int rowLen; michael@0: if (bpp_ == 32) { michael@0: rowLen = width_ * 4; michael@0: pixelPad_ = 1; michael@0: } else if (bpp_ == 24) { michael@0: rowLen = width_ * 3; michael@0: } else if (bpp_ == 16) { michael@0: rowLen = width_ * 2; michael@0: } else if (bpp_ == 8) { michael@0: rowLen = width_; michael@0: } else if (bpp_ == 4) { michael@0: rowLen = width_ / 2; michael@0: if (width_ & 1) { michael@0: rowLen++; michael@0: } michael@0: } else if (bpp_ == 1) { michael@0: rowLen = width_ / 8; michael@0: if (width_ & 7) { michael@0: rowLen++; michael@0: } michael@0: } else { michael@0: return false; michael@0: } michael@0: // Round the rowLen up to a multiple of 4. michael@0: if (rowLen % 4 != 0) { michael@0: rowPad_ = 4 - (rowLen % 4); michael@0: rowLen += rowPad_; michael@0: } michael@0: michael@0: if (offset > 0 && (size_t)offset > pos_ && (size_t)offset < len_) { michael@0: pos_ = offset; michael@0: } michael@0: // Deliberately off-by-one; a load of BMPs seem to have their last byte michael@0: // missing. michael@0: if (!rle && (pos_ + (rowLen * height_) > len_ + 1)) { michael@0: return false; michael@0: } michael@0: michael@0: output_ = callback->SetSize(width_, height_); michael@0: if (NULL == output_) { michael@0: return true; // meaning we succeeded, but they want us to stop now michael@0: } michael@0: michael@0: if (rle && (bpp_ == 4 || bpp_ == 8)) { michael@0: DoRLEDecode(); michael@0: } else { michael@0: DoStandardDecode(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void BmpDecoderHelper::DoRLEDecode() { michael@0: static const uint8 RLE_ESCAPE = 0; michael@0: static const uint8 RLE_EOL = 0; michael@0: static const uint8 RLE_EOF = 1; michael@0: static const uint8 RLE_DELTA = 2; michael@0: int x = 0; michael@0: int y = height_ - 1; michael@0: while (pos_ + 1 < len_) { michael@0: uint8 cmd = GetByte(); michael@0: if (cmd != RLE_ESCAPE) { michael@0: uint8 pixels = GetByte(); michael@0: int num = 0; michael@0: uint8 col = pixels; michael@0: while (cmd-- && x < width_) { michael@0: if (bpp_ == 4) { michael@0: if (num & 1) { michael@0: col = pixels & 0xf; michael@0: } else { michael@0: col = pixels >> 4; michael@0: } michael@0: } michael@0: PutPixel(x++, y, col); michael@0: num++; michael@0: } michael@0: } else { michael@0: cmd = GetByte(); michael@0: if (cmd == RLE_EOF) { michael@0: return; michael@0: } else if (cmd == RLE_EOL) { michael@0: x = 0; michael@0: y--; michael@0: if (y < 0) { michael@0: return; michael@0: } michael@0: } else if (cmd == RLE_DELTA) { michael@0: if (pos_ + 1 < len_) { michael@0: uint8 dx = GetByte(); michael@0: uint8 dy = GetByte(); michael@0: x += dx; michael@0: if (x > width_) { michael@0: x = width_; michael@0: } michael@0: y -= dy; michael@0: if (y < 0) { michael@0: return; michael@0: } michael@0: } michael@0: } else { michael@0: int num = 0; michael@0: int bytesRead = 0; michael@0: uint8 val = 0; michael@0: while (cmd-- && pos_ < len_) { michael@0: if (bpp_ == 8 || !(num & 1)) { michael@0: val = GetByte(); michael@0: bytesRead++; michael@0: } michael@0: uint8 col = val; michael@0: if (bpp_ == 4) { michael@0: if (num & 1) { michael@0: col = col & 0xf; michael@0: } else { michael@0: col >>= 4; michael@0: } michael@0: } michael@0: if (x < width_) { michael@0: PutPixel(x++, y, col); michael@0: } michael@0: num++; michael@0: } michael@0: // All pixel runs must be an even number of bytes - skip a byte if we michael@0: // read an odd number. michael@0: if ((bytesRead & 1) && pos_ < len_) { michael@0: GetByte(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void BmpDecoderHelper::PutPixel(int x, int y, uint8 col) { michael@0: CHECK(x >= 0 && x < width_); michael@0: CHECK(y >= 0 && y < height_); michael@0: if (!inverted_) { michael@0: y = height_ - (y + 1); michael@0: } michael@0: michael@0: int base = ((y * width_) + x) * 3; michael@0: int colBase = col * 3; michael@0: output_[base] = colTab_[colBase]; michael@0: output_[base + 1] = colTab_[colBase + 1]; michael@0: output_[base + 2] = colTab_[colBase + 2]; michael@0: } michael@0: michael@0: void BmpDecoderHelper::DoStandardDecode() { michael@0: int row = 0; michael@0: uint8 currVal = 0; michael@0: for (int h = height_ - 1; h >= 0; h--, row++) { michael@0: int realH = h; michael@0: if (!inverted_) { michael@0: realH = height_ - (h + 1); michael@0: } michael@0: uint8* line = output_ + (3 * width_ * realH); michael@0: for (int w = 0; w < width_; w++) { michael@0: if (bpp_ >= 24) { michael@0: line[2] = GetByte(); michael@0: line[1] = GetByte(); michael@0: line[0] = GetByte(); michael@0: } else if (bpp_ == 16) { michael@0: uint32 val = GetShort(); michael@0: line[0] = ((val & redBits_) >> redShiftRight_) << redShiftLeft_; michael@0: line[1] = ((val & greenBits_) >> greenShiftRight_) << greenShiftLeft_; michael@0: line[2] = ((val & blueBits_) >> blueShiftRight_) << blueShiftLeft_; michael@0: } else if (bpp_ <= 8) { michael@0: uint8 col; michael@0: if (bpp_ == 8) { michael@0: col = GetByte(); michael@0: } else if (bpp_ == 4) { michael@0: if ((w % 2) == 0) { michael@0: currVal = GetByte(); michael@0: col = currVal >> 4; michael@0: } else { michael@0: col = currVal & 0xf; michael@0: } michael@0: } else { michael@0: if ((w % 8) == 0) { michael@0: currVal = GetByte(); michael@0: } michael@0: int bit = w & 7; michael@0: col = ((currVal >> (7 - bit)) & 1); michael@0: } michael@0: int base = col * 3; michael@0: line[0] = colTab_[base]; michael@0: line[1] = colTab_[base + 1]; michael@0: line[2] = colTab_[base + 2]; michael@0: } michael@0: line += 3; michael@0: for (int i = 0; i < pixelPad_; ++i) { michael@0: GetByte(); michael@0: } michael@0: } michael@0: for (int i = 0; i < rowPad_; ++i) { michael@0: GetByte(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: int BmpDecoderHelper::GetInt() { michael@0: uint8 b1 = GetByte(); michael@0: uint8 b2 = GetByte(); michael@0: uint8 b3 = GetByte(); michael@0: uint8 b4 = GetByte(); michael@0: return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); michael@0: } michael@0: michael@0: int BmpDecoderHelper::GetShort() { michael@0: uint8 b1 = GetByte(); michael@0: uint8 b2 = GetByte(); michael@0: return b1 | (b2 << 8); michael@0: } michael@0: michael@0: uint8 BmpDecoderHelper::GetByte() { michael@0: CHECK(pos_ <= len_); michael@0: // We deliberately allow this off-by-one access to cater for BMPs with their michael@0: // last byte missing. michael@0: if (pos_ == len_) { michael@0: return 0; michael@0: } michael@0: return data_[pos_++]; michael@0: } michael@0: michael@0: int BmpDecoderHelper::CalcShiftRight(uint32 mask) { michael@0: int ret = 0; michael@0: while (mask != 0 && !(mask & 1)) { michael@0: mask >>= 1; michael@0: ret++; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: int BmpDecoderHelper::CalcShiftLeft(uint32 mask) { michael@0: int ret = 0; michael@0: while (mask != 0 && !(mask & 1)) { michael@0: mask >>= 1; michael@0: } michael@0: while (mask != 0 && !(mask & 0x80)) { michael@0: mask <<= 1; michael@0: ret++; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: } // namespace image_codec