#include "BMPParser.h" #include 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() #define U1() get() #define I2() get() #define U2() get() #define I4() get() #define U4() get() #define I1UC() get() #define U1UC() get() #define I2UC() get() #define U2UC() get() #define I4UC() get() #define U4UC() get() #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(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(entry); green = get(entry + 1); red = get(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(entry); green = get(entry + 1); red = get(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(entry); green = get(entry + 1); red = get(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(red * a + .5); green = static_cast(green * a + .5); blue = static_cast(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 inline T Parser::get(){ if(check) CHECK_OVERRUN(ptr, sizeof(T), T); T val = *(T*)ptr; ptr += sizeof(T); return val; } template 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(*ptr++)) + val; else val += static_cast(*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; }