123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- #include "BMPParser.h"
- #include <cassert>
- using namespace std;
- using namespace BMPParser;
- #define MAX_IMG_SIZE 10000
- #define E(cond, msg) if(cond) return setErr(msg)
- #define EU(cond, msg) if(cond) return setErrUnsupported(msg)
- #define EX(cond, msg) if(cond) return setErrUnknown(msg)
- #define I1() get<char>()
- #define U1() get<uint8_t>()
- #define I2() get<int16_t>()
- #define U2() get<uint16_t>()
- #define I4() get<int32_t>()
- #define U4() get<uint32_t>()
- #define I1UC() get<char, false>()
- #define U1UC() get<uint8_t, false>()
- #define I2UC() get<int16_t, false>()
- #define U2UC() get<uint16_t, false>()
- #define I4UC() get<int32_t, false>()
- #define U4UC() get<uint32_t, false>()
- #define CHECK_OVERRUN(ptr, size, type) \
- if((ptr) + (size) - data > len){ \
- setErr("unexpected end of file"); \
- return type(); \
- }
- Parser::~Parser(){
- data = nullptr;
- ptr = nullptr;
- if(imgd){
- delete[] imgd;
- imgd = nullptr;
- }
- }
- void Parser::parse(uint8_t *buf, int bufSize, uint8_t *format){
- assert(status == Status::EMPTY);
- data = ptr = buf;
- len = bufSize;
- // Start parsing file header
- setOp("file header");
- // File header signature
- string fhSig = getStr(2);
- string temp = "file header signature";
- EU(fhSig == "BA", temp + " \"BA\"");
- EU(fhSig == "CI", temp + " \"CI\"");
- EU(fhSig == "CP", temp + " \"CP\"");
- EU(fhSig == "IC", temp + " \"IC\"");
- EU(fhSig == "PT", temp + " \"PT\"");
- EX(fhSig != "BM", temp); // BM
- // Length of the file should not be larger than `len`
- E(U4() > static_cast<uint32_t>(len), "inconsistent file size");
- // Skip unused values
- skip(4);
- // Offset where the pixel array (bitmap data) can be found
- auto imgdOffset = U4();
- // Start parsing DIB header
- setOp("DIB header");
- // Prepare some variables in case they are needed
- uint32_t compr = 0;
- uint32_t redShift = 0, greenShift = 0, blueShift = 0, alphaShift = 0;
- uint32_t redMask = 0, greenMask = 0, blueMask = 0, alphaMask = 0;
- double redMultp = 0, greenMultp = 0, blueMultp = 0, alphaMultp = 0;
- /**
- * Type of the DIB (device-independent bitmap) header
- * is determined by its size. Most BMP files use BITMAPINFOHEADER.
- */
- auto dibSize = U4();
- temp = "DIB header";
- EU(dibSize == 64, temp + " \"OS22XBITMAPHEADER\"");
- EU(dibSize == 16, temp + " \"OS22XBITMAPHEADER\"");
- uint32_t infoHeader = dibSize == 40 ? 1 :
- dibSize == 52 ? 2 :
- dibSize == 56 ? 3 :
- dibSize == 108 ? 4 :
- dibSize == 124 ? 5 : 0;
- // BITMAPCOREHEADER, BITMAP*INFOHEADER, BITMAP*HEADER
- auto isDibValid = dibSize == 12 || infoHeader;
- EX(!isDibValid, temp);
- // Image width
- w = dibSize == 12 ? U2() : I4();
- E(!w, "image width is 0");
- E(w < 0, "negative image width");
- E(w > MAX_IMG_SIZE, "too large image width");
- // Image height (specification allows negative values)
- h = dibSize == 12 ? U2() : I4();
- E(!h, "image height is 0");
- E(h > MAX_IMG_SIZE, "too large image height");
- bool isHeightNegative = h < 0;
- if(isHeightNegative) h = -h;
- // Number of color planes (must be 1)
- E(U2() != 1, "number of color planes must be 1");
- // Bits per pixel (color depth)
- auto bpp = U2();
- auto isBppValid = bpp == 1 || bpp == 4 || bpp == 8 || bpp == 16 || bpp == 24 || bpp == 32;
- EU(!isBppValid, "color depth");
- // Calculate image data size and padding
- uint32_t expectedImgdSize = (((w * bpp + 31) >> 5) << 2) * h;
- uint32_t rowPadding = (-w * bpp & 31) >> 3;
- uint32_t imgdSize = 0;
- // Color palette data
- uint8_t* paletteStart = nullptr;
- uint32_t palColNum = 0;
- if(infoHeader){
- // Compression type
- compr = U4();
- temp = "compression type";
- EU(compr == 1, temp + " \"BI_RLE8\"");
- EU(compr == 2, temp + " \"BI_RLE4\"");
- EU(compr == 4, temp + " \"BI_JPEG\"");
- EU(compr == 5, temp + " \"BI_PNG\"");
- EU(compr == 6, temp + " \"BI_ALPHABITFIELDS\"");
- EU(compr == 11, temp + " \"BI_CMYK\"");
- EU(compr == 12, temp + " \"BI_CMYKRLE8\"");
- EU(compr == 13, temp + " \"BI_CMYKRLE4\"");
- // BI_RGB and BI_BITFIELDS
- auto isComprValid = compr == 0 || compr == 3;
- EX(!isComprValid, temp);
- // Ensure that BI_BITFIELDS appears only with 16-bit or 32-bit color
- E(compr == 3 && !(bpp == 16 || bpp == 32), "compression BI_BITFIELDS can be used only with 16-bit and 32-bit color depth");
- // Size of the image data
- imgdSize = U4();
- // Horizontal and vertical resolution (ignored)
- skip(8);
- // Number of colors in the palette or 0 if no palette is present
- palColNum = U4();
- EU(palColNum && bpp > 8, "color palette and bit depth combination");
- if(palColNum) paletteStart = data + dibSize + 14;
- // Number of important colors used or 0 if all colors are important (generally ignored)
- skip(4);
- if(infoHeader >= 2){
- // If BI_BITFIELDS are used, calculate masks, otherwise ignore them
- if(compr == 3){
- calcMaskShift(redShift, redMask, redMultp);
- calcMaskShift(greenShift, greenMask, greenMultp);
- calcMaskShift(blueShift, blueMask, blueMultp);
- if(infoHeader >= 3) calcMaskShift(alphaShift, alphaMask, alphaMultp);
- if(status == Status::ERROR) return;
- }else{
- skip(16);
- }
- // Ensure that the color space is LCS_WINDOWS_COLOR_SPACE or sRGB
- if(infoHeader >= 4 && !palColNum){
- string colSpace = getStr(4, 1);
- EU(colSpace != "Win " && colSpace != "sRGB", "color space \"" + colSpace + "\"");
- }
- }
- }
- // Skip to the image data (there may be other chunks between, but they are optional)
- E(ptr - data > imgdOffset, "image data overlaps with another structure");
- ptr = data + imgdOffset;
- // Start parsing image data
- setOp("image data");
- if(!imgdSize){
- // Value 0 is allowed only for BI_RGB compression type
- E(compr != 0, "missing image data size");
- imgdSize = expectedImgdSize;
- }else{
- E(imgdSize < expectedImgdSize, "invalid image data size");
- }
- // Ensure that all image data is present
- E(ptr - data + imgdSize > len, "not enough image data");
- // Direction of reading rows
- int yStart = h - 1;
- int yEnd = -1;
- int dy = isHeightNegative ? 1 : -1;
- // In case of negative height, read rows backward
- if(isHeightNegative){
- yStart = 0;
- yEnd = h;
- }
- // Allocate output image data array
- int buffLen = w * h << 2;
- imgd = new (nothrow) uint8_t[buffLen];
- E(!imgd, "unable to allocate memory");
- // Prepare color values
- uint8_t color[4] = {0};
- uint8_t &red = color[0];
- uint8_t &green = color[1];
- uint8_t &blue = color[2];
- uint8_t &alpha = color[3];
- // Check if pre-multiplied alpha is used
- bool premul = format ? format[4] : 0;
- // Main loop
- for(int y = yStart; y != yEnd; y += dy){
- // Use in-byte offset for bpp < 8
- uint8_t colOffset = 0;
- uint8_t cval = 0;
- uint32_t val = 0;
- for(int x = 0; x != w; x++){
- // Index in the output image data
- int i = (x + y * w) << 2;
- switch(compr){
- case 0: // BI_RGB
- switch(bpp){
- case 1:
- if(colOffset) ptr--;
- cval = (U1UC() >> (7 - colOffset)) & 1;
- if(palColNum){
- uint8_t* entry = paletteStart + (cval << 2);
- blue = get<uint8_t>(entry);
- green = get<uint8_t>(entry + 1);
- red = get<uint8_t>(entry + 2);
- if(status == Status::ERROR) return;
- }else{
- red = green = blue = cval ? 255 : 0;
- }
- alpha = 255;
- colOffset = (colOffset + 1) & 7;
- break;
- case 4:
- if(colOffset) ptr--;
- cval = (U1UC() >> (4 - colOffset)) & 15;
- if(palColNum){
- uint8_t* entry = paletteStart + (cval << 2);
- blue = get<uint8_t>(entry);
- green = get<uint8_t>(entry + 1);
- red = get<uint8_t>(entry + 2);
- if(status == Status::ERROR) return;
- }else{
- red = green = blue = cval << 4;
- }
- alpha = 255;
- colOffset = (colOffset + 4) & 7;
- break;
- case 8:
- cval = U1UC();
- if(palColNum){
- uint8_t* entry = paletteStart + (cval << 2);
- blue = get<uint8_t>(entry);
- green = get<uint8_t>(entry + 1);
- red = get<uint8_t>(entry + 2);
- if(status == Status::ERROR) return;
- }else{
- red = green = blue = cval;
- }
- alpha = 255;
- break;
- case 16:
- // RGB555
- val = U1UC();
- val |= U1UC() << 8;
- red = (val >> 10) << 3;
- green = (val >> 5) << 3;
- blue = val << 3;
- alpha = 255;
- break;
- case 24:
- blue = U1UC();
- green = U1UC();
- red = U1UC();
- alpha = 255;
- break;
- case 32:
- blue = U1UC();
- green = U1UC();
- red = U1UC();
- if(infoHeader >= 3){
- alpha = U1UC();
- }else{
- alpha = 255;
- skip(1);
- }
- break;
- }
- break;
- case 3: // BI_BITFIELDS
- uint32_t col = bpp == 16 ? U2UC() : U4UC();
- red = ((col >> redShift) & redMask) * redMultp + .5;
- green = ((col >> greenShift) & greenMask) * greenMultp + .5;
- blue = ((col >> blueShift) & blueMask) * blueMultp + .5;
- alpha = alphaMask ? ((col >> alphaShift) & alphaMask) * alphaMultp + .5 : 255;
- break;
- }
- /**
- * Pixel format:
- * red,
- * green,
- * blue,
- * alpha,
- * is alpha pre-multiplied
- * Default is [0, 1, 2, 3, 0]
- */
- if(premul && alpha != 255){
- double a = alpha / 255.;
- red = static_cast<uint8_t>(red * a + .5);
- green = static_cast<uint8_t>(green * a + .5);
- blue = static_cast<uint8_t>(blue * a + .5);
- }
- if(format){
- imgd[i] = color[format[0]];
- imgd[i + 1] = color[format[1]];
- imgd[i + 2] = color[format[2]];
- imgd[i + 3] = color[format[3]];
- }else{
- imgd[i] = red;
- imgd[i + 1] = green;
- imgd[i + 2] = blue;
- imgd[i + 3] = alpha;
- }
- }
- // Skip unused bytes in the current row
- skip(rowPadding);
- }
- if(status == Status::ERROR) return;
- status = Status::OK;
- };
- void Parser::clearImgd(){ imgd = nullptr; }
- int32_t Parser::getWidth() const{ return w; }
- int32_t Parser::getHeight() const{ return h; }
- uint8_t *Parser::getImgd() const{ return imgd; }
- Status Parser::getStatus() const{ return status; }
- string Parser::getErrMsg() const{
- return "Error while processing " + getOp() + " - " + err;
- }
- template <typename T, bool check> inline T Parser::get(){
- if(check)
- CHECK_OVERRUN(ptr, sizeof(T), T);
- T val = *(T*)ptr;
- ptr += sizeof(T);
- return val;
- }
- template <typename T, bool check> inline T Parser::get(uint8_t* pointer){
- if(check)
- CHECK_OVERRUN(pointer, sizeof(T), T);
- T val = *(T*)pointer;
- return val;
- }
- string Parser::getStr(int size, bool reverse){
- CHECK_OVERRUN(ptr, size, string);
- string val = "";
- while(size--){
- if(reverse) val = string(1, static_cast<char>(*ptr++)) + val;
- else val += static_cast<char>(*ptr++);
- }
- return val;
- }
- inline void Parser::skip(int size){
- CHECK_OVERRUN(ptr, size, void);
- ptr += size;
- }
- void Parser::calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp){
- mask = U4();
- shift = 0;
- if(mask == 0) return;
- while(~mask & 1){
- mask >>= 1;
- shift++;
- }
- E(mask & (mask + 1), "invalid color mask");
- multp = 255. / mask;
- }
- void Parser::setOp(string val){
- if(status != Status::EMPTY) return;
- op = val;
- }
- string Parser::getOp() const{
- return op;
- }
- void Parser::setErrUnsupported(string msg){
- setErr("unsupported " + msg);
- }
- void Parser::setErrUnknown(string msg){
- setErr("unknown " + msg);
- }
- void Parser::setErr(string msg){
- if(status != Status::EMPTY) return;
- err = msg;
- status = Status::ERROR;
- }
- string Parser::getErr() const{
- return err;
- }
|