/*
* Copyright 2007, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: cevans@google.com (Chris Evans)
#include "bmpdecoderhelper.h"
namespace image_codec {
static const int kBmpHeaderSize = 14;
static const int kBmpInfoSize = 40;
static const int kBmpOS2InfoSize = 12;
static const int kMaxDim = SHRT_MAX / 2;
bool BmpDecoderHelper::DecodeImage(const char* p,
int len,
int max_pixels,
BmpDecoderCallback* callback) {
data_ = reinterpret_cast<const uint8*>(p);
pos_ = 0;
len_ = len;
inverted_ = true;
// Parse the header structure.
if (len < kBmpHeaderSize + 4) {
return false;
}
GetShort(); // Signature.
GetInt(); // Size.
GetInt(); // Reserved.
int offset = GetInt();
// Parse the info structure.
int infoSize = GetInt();
if (infoSize != kBmpOS2InfoSize && infoSize < kBmpInfoSize) {
return false;
}
int cols = 0;
int comp = 0;
int colLen = 4;
if (infoSize >= kBmpInfoSize) {
if (len < kBmpHeaderSize + kBmpInfoSize) {
return false;
}
width_ = GetInt();
height_ = GetInt();
GetShort(); // Planes.
bpp_ = GetShort();
comp = GetInt();
GetInt(); // Size.
GetInt(); // XPPM.
GetInt(); // YPPM.
cols = GetInt();
GetInt(); // Important colours.
} else {
if (len < kBmpHeaderSize + kBmpOS2InfoSize) {
return false;
}
colLen = 3;
width_ = GetShort();
height_ = GetShort();
GetShort(); // Planes.
bpp_ = GetShort();
}
if (height_ < 0) {
height_ = -height_;
inverted_ = false;
}
if (width_ <= 0 || width_ > kMaxDim || height_ <= 0 || height_ > kMaxDim) {
return false;
}
if (width_ * height_ > max_pixels) {
return false;
}
if (cols < 0 || cols > 256) {
return false;
}
// Allocate then read in the colour map.
if (cols == 0 && bpp_ <= 8) {
cols = 1 << bpp_;
}
if (bpp_ <= 8 || cols > 0) {
uint8* colBuf = new uint8[256 * 3];
memset(colBuf, '\0', 256 * 3);
colTab_.reset(colBuf);
}
if (cols > 0) {
if (pos_ + (cols * colLen) > len_) {
return false;
}
for (int i = 0; i < cols; ++i) {
int base = i * 3;
colTab_[base + 2] = GetByte();
colTab_[base + 1] = GetByte();
colTab_[base] = GetByte();
if (colLen == 4) {
GetByte();
}
}
}
// Read in the compression data if necessary.
redBits_ = 0x7c00;
greenBits_ = 0x03e0;
blueBits_ = 0x001f;
bool rle = false;
if (comp == 1 || comp == 2) {
rle = true;
} else if (comp == 3) {
if (pos_ + 12 > len_) {
return false;
}
redBits_ = GetInt() & 0xffff;
greenBits_ = GetInt() & 0xffff;
blueBits_ = GetInt() & 0xffff;
}
redShiftRight_ = CalcShiftRight(redBits_);
greenShiftRight_ = CalcShiftRight(greenBits_);
blueShiftRight_ = CalcShiftRight(blueBits_);
redShiftLeft_ = CalcShiftLeft(redBits_);
greenShiftLeft_ = CalcShiftLeft(greenBits_);
blueShiftLeft_ = CalcShiftLeft(blueBits_);
rowPad_ = 0;
pixelPad_ = 0;
int rowLen;
if (bpp_ == 32) {
rowLen = width_ * 4;
pixelPad_ = 1;
} else if (bpp_ == 24) {
rowLen = width_ * 3;
} else if (bpp_ == 16) {
rowLen = width_ * 2;
} else if (bpp_ == 8) {
rowLen = width_;
} else if (bpp_ == 4) {
rowLen = width_ / 2;
if (width_ & 1) {
rowLen++;
}
} else if (bpp_ == 1) {
rowLen = width_ / 8;
if (width_ & 7) {
rowLen++;
}
} else {
return false;
}
// Round the rowLen up to a multiple of 4.
if (rowLen % 4 != 0) {
rowPad_ = 4 - (rowLen % 4);
rowLen += rowPad_;
}
if (offset > 0 && offset > pos_ && offset < len_) {
pos_ = offset;
}
// Deliberately off-by-one; a load of BMPs seem to have their last byte
// missing.
if (!rle && (pos_ + (rowLen * height_) > len_ + 1)) {
return false;
}
output_ = callback->SetSize(width_, height_);
if (NULL == output_) {
return true; // meaning we succeeded, but they want us to stop now
}
if (rle && (bpp_ == 4 || bpp_ == 8)) {
DoRLEDecode();
} else {
DoStandardDecode();
}
return true;
}
void BmpDecoderHelper::DoRLEDecode() {
static const uint8 RLE_ESCAPE = 0;
static const uint8 RLE_EOL = 0;
static const uint8 RLE_EOF = 1;
static const uint8 RLE_DELTA = 2;
int x = 0;
int y = height_ - 1;
while (pos_ < len_ - 1) {
uint8 cmd = GetByte();
if (cmd != RLE_ESCAPE) {
uint8 pixels = GetByte();
int num = 0;
uint8 col = pixels;
while (cmd-- && x < width_) {
if (bpp_ == 4) {
if (num & 1) {
col = pixels & 0xf;
} else {
col = pixels >> 4;
}
}
PutPixel(x++, y, col);
num++;
}
} else {
cmd = GetByte();
if (cmd == RLE_EOF) {
return;
} else if (cmd == RLE_EOL) {
x = 0;
y--;
if (y < 0) {
return;
}
} else if (cmd == RLE_DELTA) {
if (pos_ < len_ - 1) {
uint8 dx = GetByte();
uint8 dy = GetByte();
x += dx;
if (x > width_) {
x = width_;
}
y -= dy;
if (y < 0) {
return;
}
}
} else {
int num = 0;
int bytesRead = 0;
uint8 val = 0;
while (cmd-- && pos_ < len_) {
if (bpp_ == 8 || !(num & 1)) {
val = GetByte();
bytesRead++;
}
uint8 col = val;
if (bpp_ == 4) {
if (num & 1) {
col = col & 0xf;
} else {
col >>= 4;
}
}
if (x < width_) {
PutPixel(x++, y, col);
}
num++;
}
// All pixel runs must be an even number of bytes - skip a byte if we
// read an odd number.
if ((bytesRead & 1) && pos_ < len_) {
GetByte();
}
}
}
}
}
void BmpDecoderHelper::PutPixel(int x, int y, uint8 col) {
CHECK(x >= 0 && x < width_);
CHECK(y >= 0 && y < height_);
if (!inverted_) {
y = height_ - (y + 1);
}
int base = ((y * width_) + x) * 3;
int colBase = col * 3;
output_[base] = colTab_[colBase];
output_[base + 1] = colTab_[colBase + 1];
output_[base + 2] = colTab_[colBase + 2];
}
void BmpDecoderHelper::DoStandardDecode() {
int row = 0;
uint8 currVal = 0;
for (int h = height_ - 1; h >= 0; h--, row++) {
int realH = h;
if (!inverted_) {
realH = height_ - (h + 1);
}
uint8* line = output_ + (3 * width_ * realH);
for (int w = 0; w < width_; w++) {
if (bpp_ >= 24) {
line[2] = GetByte();
line[1] = GetByte();
line[0] = GetByte();
} else if (bpp_ == 16) {
uint32 val = GetShort();
line[0] = ((val & redBits_) >> redShiftRight_) << redShiftLeft_;
line[1] = ((val & greenBits_) >> greenShiftRight_) << greenShiftLeft_;
line[2] = ((val & blueBits_) >> blueShiftRight_) << blueShiftLeft_;
} else if (bpp_ <= 8) {
uint8 col;
if (bpp_ == 8) {
col = GetByte();
} else if (bpp_ == 4) {
if ((w % 2) == 0) {
currVal = GetByte();
col = currVal >> 4;
} else {
col = currVal & 0xf;
}
} else {
if ((w % 8) == 0) {
currVal = GetByte();
}
int bit = w & 7;
col = ((currVal >> (7 - bit)) & 1);
}
int base = col * 3;
line[0] = colTab_[base];
line[1] = colTab_[base + 1];
line[2] = colTab_[base + 2];
}
line += 3;
for (int i = 0; i < pixelPad_; ++i) {
GetByte();
}
}
for (int i = 0; i < rowPad_; ++i) {
GetByte();
}
}
}
int BmpDecoderHelper::GetInt() {
uint8 b1 = GetByte();
uint8 b2 = GetByte();
uint8 b3 = GetByte();
uint8 b4 = GetByte();
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
}
int BmpDecoderHelper::GetShort() {
uint8 b1 = GetByte();
uint8 b2 = GetByte();
return b1 | (b2 << 8);
}
uint8 BmpDecoderHelper::GetByte() {
CHECK(pos_ >= 0 && pos_ <= len_);
// We deliberately allow this off-by-one access to cater for BMPs with their
// last byte missing.
if (pos_ == len_) {
return 0;
}
return data_[pos_++];
}
int BmpDecoderHelper::CalcShiftRight(uint32 mask) {
int ret = 0;
while (mask != 0 && !(mask & 1)) {
mask >>= 1;
ret++;
}
return ret;
}
int BmpDecoderHelper::CalcShiftLeft(uint32 mask) {
int ret = 0;
while (mask != 0 && !(mask & 1)) {
mask >>= 1;
}
while (mask != 0 && !(mask & 0x80)) {
mask <<= 1;
ret++;
}
return ret;
}
} // namespace image_codec