// cf. http://stackoverflow.com/questions/4598872/creating-hbitmap-from-memory-buffer/4616394#4616394 Bitmap *BitmapFromData(const char *data, size_t len) { ImgFormat format = GfxFormatFromData(data, len); if (Img_TGA == format) return tga::ImageFromData(data, len); if (Img_WebP == format) return webp::ImageFromData(data, len); if (Img_JP2 == format) return fitz::ImageFromData(data, len); if (Img_JPEG == format && JpegUsesArithmeticCoding(data, len)) return fitz::ImageFromData(data, len); ScopedComPtr<IStream> stream(CreateStreamFromData(data, len)); if (!stream) return NULL; if (Img_JXR == format) return WICDecodeImageFromStream(stream); Bitmap *bmp = Bitmap::FromStream(stream); if (bmp && bmp->GetLastStatus() != Ok) { delete bmp; bmp = NULL; } // GDI+ under Windows XP sometimes fails to extract JPEG image dimensions if (bmp && Img_JPEG == format && (0 == bmp->GetWidth() || 0 == bmp->GetHeight())) { delete bmp; bmp = fitz::ImageFromData(data, len); } return bmp; }
bool IsGdiPlusNativeFormat(const char *data, size_t len) { ImgFormat fmt = GfxFormatFromData(data, len); return Img_BMP == fmt || Img_GIF == fmt || (Img_JPEG == fmt && !JpegUsesArithmeticCoding(data, len)) || Img_PNG == fmt || Img_TIFF == fmt; }
// Windows' JPEG codec doesn't support arithmetic coding static bool JpegUsesArithmeticCoding(const char* data, size_t len) { CrashIf(GfxFormatFromData(data, len) != Img_JPEG); ByteReader r(data, len); for (size_t ix = 2; ix + 9 < len && r.Byte(ix) == 0xFF; ix += r.WordBE(ix + 2) + 2) { if (0xC9 <= r.Byte(ix + 1) && r.Byte(ix + 1) <= 0xCB) { // found the start of a frame using arithmetic coding return true; } } return false; }
// Windows' PNG codec fails to handle an edge case, resulting in // an infinite loop (cf. http://cxsecurity.com/issue/WLB-2014080021 ) static bool PngRequiresPresetDict(const char* data, size_t len) { CrashIf(GfxFormatFromData(data, len) != Img_PNG); ByteReader r(data, len); for (size_t ix = 8; ix + 12 < len && r.DWordBE(ix) < len - ix - 12; ix += r.DWordBE(ix) + 12) { if (r.DWordBE(ix + 4) == 0x49444154 /* IDAT */) { // check the zlib header's FDICT flag // (even if this image claims not to be zlib compressed!) return (r.Byte(ix + 9) & (1 << 5)) != 0; } } return false; }
const WCHAR *GfxFileExtFromData(const char *data, size_t len) { switch (GfxFormatFromData(data, len)) { case Img_BMP: return L".bmp"; case Img_GIF: return L".gif"; case Img_JPEG: return L".jpg"; case Img_JXR: return L".jxr"; case Img_PNG: return L".png"; case Img_TGA: return L".tga"; case Img_TIFF: return L".tif"; case Img_WebP: return L".webp"; case Img_JP2: return L".jp2"; default: return NULL; } }
// adapted from http://cpansearch.perl.org/src/RJRAY/Image-Size-3.230/lib/Image/Size.pm Size BitmapSizeFromData(const char *data, size_t len) { Size result; ByteReader r(data, len); switch (GfxFormatFromData(data, len)) { case Img_BMP: if (len >= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)) { BITMAPINFOHEADER bmi; bool ok = r.UnpackLE(&bmi, sizeof(bmi), "3d2w6d", sizeof(BITMAPFILEHEADER)); CrashIf(!ok); result.Width = bmi.biWidth; result.Height = bmi.biHeight; } break; case Img_GIF: if (len >= 13) { // find the first image's actual size instead of using the // "logical screen" size which is sometimes too large size_t ix = 13; // skip the global color table if ((r.Byte(10) & 0x80)) ix += 3 * (1 << ((r.Byte(10) & 0x07) + 1)); while (ix + 8 < len) { if (r.Byte(ix) == 0x2C) { result.Width = r.WordLE(ix + 5); result.Height = r.WordLE(ix + 7); break; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xF9) ix += 8; else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xFE) { const char *commentEnd = r.Find(ix + 2, 0x00); ix = commentEnd ? commentEnd - data + 1 : len; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0x01 && ix + 15 < len) { const char *textDataEnd = r.Find(ix + 15, 0x00); ix = textDataEnd ? textDataEnd - data + 1 : len; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xFF && ix + 14 < len) { const char *applicationDataEnd = r.Find(ix + 14, 0x00); ix = applicationDataEnd ? applicationDataEnd - data + 1 : len; } else break; } } break; case Img_JPEG: // find the last start of frame marker for non-differential Huffman/arithmetic coding for (size_t ix = 2; ix + 9 < len && r.Byte(ix) == 0xFF; ) { if (0xC0 <= r.Byte(ix + 1) && r.Byte(ix + 1) <= 0xC3 || 0xC9 <= r.Byte(ix + 1) && r.Byte(ix + 1) <= 0xCB) { result.Width = r.WordBE(ix + 7); result.Height = r.WordBE(ix + 5); } ix += r.WordBE(ix + 2) + 2; } break; case Img_JXR: case Img_TIFF: if (len >= 10) { bool isBE = r.Byte(0) == 'M', isJXR = r.Byte(2) == 0xBC; CrashIf(!isBE && r.Byte(0) != 'I' || isJXR && isBE); const WORD WIDTH = isJXR ? 0xBC80 : 0x0100, HEIGHT = isJXR ? 0xBC81 : 0x0101; size_t idx = r.DWord(4, isBE); WORD count = idx <= len - 2 ? r.Word(idx, isBE) : 0; for (idx += 2; count > 0 && idx <= len - 12; count--, idx += 12) { WORD tag = r.Word(idx, isBE), type = r.Word(idx + 2, isBE); if (r.DWord(idx + 4, isBE) != 1) continue; else if (WIDTH == tag && 4 == type) result.Width = r.DWord(idx + 8, isBE); else if (WIDTH == tag && 3 == type) result.Width = r.Word(idx + 8, isBE); else if (WIDTH == tag && 1 == type) result.Width = r.Byte(idx + 8); else if (HEIGHT == tag && 4 == type) result.Height = r.DWord(idx + 8, isBE); else if (HEIGHT == tag && 3 == type) result.Height = r.Word(idx + 8, isBE); else if (HEIGHT == tag && 1 == type) result.Height = r.Byte(idx + 8); } } break; case Img_PNG: if (len >= 24 && str::StartsWith(data + 12, "IHDR")) { result.Width = r.DWordBE(16); result.Height = r.DWordBE(20); } break; case Img_TGA: if (len >= 16) { result.Width = r.WordLE(12); result.Height = r.WordLE(14); } break; case Img_WebP: if (len >= 30 && str::StartsWith(data + 12, "VP8 ")) { result.Width = r.WordLE(26) & 0x3fff; result.Height = r.WordLE(28) & 0x3fff; } else { result = webp::SizeFromData(data, len); } break; case Img_JP2: if (len >= 32) { size_t ix = 0; while (ix < len - 32) { uint32_t lbox = r.DWordBE(ix); uint32_t tbox = r.DWordBE(ix + 4); if (0x6A703268 /* jp2h */ == tbox) { ix += 8; if (r.DWordBE(ix) == 24 && r.DWordBE(ix + 4) == 0x69686472 /* ihdr */) { result.Width = r.DWordBE(ix + 16); result.Height = r.DWordBE(ix + 12); } break; } else if (lbox != 0 && ix < UINT32_MAX - lbox) { ix += lbox; } else { break; } } } break; } if (result.Empty()) { // let GDI+ extract the image size if we've failed // (currently happens for animated GIF) Bitmap *bmp = BitmapFromData(data, len); if (bmp) result = Size(bmp->GetWidth(), bmp->GetHeight()); delete bmp; } return result; }