//-------------------------------------------------------------------------------------- // File: bmpdump.cpp // // BMP file content examination utility // // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. //-------------------------------------------------------------------------------------- #include #include #include #ifdef __clang__ #pragma clang diagnostic ignored "-Wfour-char-constants" #endif namespace { //--------------------------------------------------------------------------------- struct handle_closer { void operator()(HANDLE h) noexcept { if (h) CloseHandle(h); } }; using ScopedHandle = std::unique_ptr; inline HANDLE safe_handle(HANDLE h) noexcept { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; } //-------------------------------------------------------------------------------------- static_assert(sizeof(BITMAPFILEHEADER) == 14, "Mis-match size"); static_assert(sizeof(BITMAPCOREHEADER) == 12, "Mis-match size"); static_assert(sizeof(BITMAPINFOHEADER) == 40, "Mis-match size"); static_assert(sizeof(BITMAPV4HEADER) == 108, "Mis-match size"); static_assert(sizeof(BITMAPV5HEADER) == 124, "Mis-match size"); //-------------------------------------------------------------------------------------- HRESULT LoadTextureDataFromFile(_In_z_ const wchar_t* fileName, std::unique_ptr& bmpData ) { // open the file #if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/) ScopedHandle hFile(safe_handle(CreateFile2(fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr))); #else ScopedHandle hFile(safe_handle(CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr))); #endif if (!hFile) return HRESULT_FROM_WIN32(GetLastError()); // Get the file size FILE_STANDARD_INFO fileInfo; if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) { return HRESULT_FROM_WIN32(GetLastError()); } // File is too big for 32-bit allocation, so reject read if (fileInfo.EndOfFile.HighPart > 0) { return E_FAIL; } // Need at least enough data to fill the basic header if (fileInfo.EndOfFile.LowPart < (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPCOREHEADER))) { return E_FAIL; } // create enough space for the file data bmpData.reset(new uint8_t[fileInfo.EndOfFile.LowPart]); if (!bmpData) { return E_OUTOFMEMORY; } // read the data in DWORD BytesRead = 0; if (!ReadFile(hFile.get(), bmpData.get(), fileInfo.EndOfFile.LowPart, &BytesRead, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (BytesRead < fileInfo.EndOfFile.LowPart) { return E_FAIL; } auto filehdr = reinterpret_cast(bmpData.get()); // Valid BMP files always start with 'BM' at the top if (filehdr->bfType != 0x4D42) { return E_FAIL; } // Check we have enough data for the discovered header auto header = reinterpret_cast(bmpData.get() + sizeof(BITMAPFILEHEADER)); if (BytesRead < (sizeof(BITMAPFILEHEADER) + header->bcSize)) { return E_FAIL; } return S_OK; } void OutputHeader(const uint8_t* bmpData, const wchar_t* fname) { auto coreHeader = reinterpret_cast(bmpData + sizeof(BITMAPFILEHEADER)); if (coreHeader->bcSize >= sizeof(BITMAPV5HEADER)) { wprintf(L"BMP %ls\n\tV5 header (%lu bytes)\n", fname, coreHeader->bcSize); auto header = reinterpret_cast(bmpData + sizeof(BITMAPFILEHEADER)); wprintf(L"%ld x %ld\n", header->bV5Width, header->bV5Height); wprintf(L"%u planes\n", header->bV5Planes); wprintf(L"%u bits\n", header->bV5BitCount); switch (header->bV5Compression) { case BI_RGB: wprintf(L"BI_RGB\n"); break; case BI_RLE8: wprintf(L"BI_RLE8\n"); break; case BI_RLE4: wprintf(L"BI_RLE4\n"); break; case BI_BITFIELDS: wprintf(L"BI_BITFIELDS\n"); break; case BI_JPEG: wprintf(L"BI_JPEG\n"); break; case BI_PNG: wprintf(L"BI_PNG\n"); break; default: wprintf(L"%lu compression type\n", header->bV5Compression); break; } wprintf(L"%lu image size\n", header->bV5SizeImage); wprintf(L"%ld x %ld pels per meter\n", header->bV5XPelsPerMeter, header->bV5YPelsPerMeter); wprintf(L"%lu colors used\n", header->bV5ClrUsed); wprintf(L"%lu colors important\n", header->bV5ClrImportant); if (header->bV5Compression == BI_BITFIELDS) { wprintf(L"R: %08lX, G: %08lX, B: %08lX, A: %08lX\n", header->bV5RedMask, header->bV5GreenMask, header->bV5BlueMask, header->bV5AlphaMask); } switch (header->bV5CSType) { case LCS_CALIBRATED_RGB: wprintf(L"LCS_CALIBRATED_RGB\n"); break; case 0x73524742 /* 'sRGB' */: wprintf(L"LCS_sRGB\n"); break; case 0x57696E20 /* 'Win ' */: wprintf(L"LCS_WINDOWS_COLOR_SPACE\n"); break; case 0x4C494E4B /* 'LINK' */: wprintf(L"PROFILE_LINKED\n"); break; case 0x4D424544 /* 'MBED' */: wprintf(L"PROFILE_EMBEDDED\n"); break; default: wprintf(L"%lu color space type\n", header->bV5CSType); break; } switch (header->bV5Intent) { case LCS_GM_ABS_COLORIMETRIC: wprintf(L"LCS_GM_ABS_COLORIMETRIC\n"); break; case LCS_GM_BUSINESS: wprintf(L"LCS_GM_BUSINESS\n"); break; case LCS_GM_GRAPHICS: wprintf(L"LCS_GM_GRAPHICS\n"); break; case LCS_GM_IMAGES: wprintf(L"LCS_GM_IMAGES\n"); break; default: wprintf(L"%lu color space type\n", header->bV5Intent); break; } } else if (coreHeader->bcSize >= sizeof(BITMAPV4HEADER)) { wprintf(L"BMP %ls\n\tV4 header (%lu bytes)\n", fname, coreHeader->bcSize); auto header = reinterpret_cast(bmpData + sizeof(BITMAPFILEHEADER)); wprintf(L"%ld x %ld\n", header->bV4Width, header->bV4Height); wprintf(L"%u planes\n", header->bV4Planes); wprintf(L"%u bits\n", header->bV4BitCount); switch (header->bV4V4Compression) { case BI_RGB: wprintf(L"BI_RGB\n"); break; case BI_RLE8: wprintf(L"BI_RLE8\n"); break; case BI_RLE4: wprintf(L"BI_RLE4\n"); break; case BI_BITFIELDS: wprintf(L"BI_BITFIELDS\n"); break; case BI_JPEG: wprintf(L"BI_JPEG\n"); break; case BI_PNG: wprintf(L"BI_PNG\n"); break; default: wprintf(L"%lu compression type\n", header->bV4V4Compression); break; } wprintf(L"%lu image size\n", header->bV4SizeImage); wprintf(L"%ld x %ld pels per meter\n", header->bV4XPelsPerMeter, header->bV4YPelsPerMeter); wprintf(L"%lu colors used\n", header->bV4ClrUsed); wprintf(L"%lu colors important\n", header->bV4ClrImportant); if (header->bV4V4Compression == BI_BITFIELDS) { wprintf(L"R: %08lX, G: %08lX, B: %08lX, A: %08lX\n", header->bV4RedMask, header->bV4GreenMask, header->bV4BlueMask, header->bV4AlphaMask); } switch (header->bV4CSType) { case LCS_CALIBRATED_RGB: wprintf(L"LCS_CALIBRATED_RGB\n"); break; default: wprintf(L"%lu color space type\n", header->bV4CSType); break; } } else if (coreHeader->bcSize >= sizeof(BITMAPINFOHEADER)) { wprintf(L"BMP %ls\n\tINFO header (%lu bytes)\n", fname, coreHeader->bcSize); auto header = reinterpret_cast(bmpData + sizeof(BITMAPFILEHEADER)); wprintf(L"%ld x %ld\n", header->biWidth, header->biHeight); wprintf(L"%u planes\n", header->biPlanes); wprintf(L"%u bits\n", header->biBitCount); switch (header->biCompression) { case BI_RGB: wprintf(L"BI_RGB\n"); break; case BI_RLE8: wprintf(L"BI_RLE8\n"); break; case BI_RLE4: wprintf(L"BI_RLE4\n"); break; case BI_BITFIELDS: wprintf(L"BI_BITFIELDS\n"); break; case BI_JPEG: wprintf(L"BI_JPEG\n"); break; case BI_PNG: wprintf(L"BI_PNG\n"); break; case 0x31545844: wprintf(L"DXT1 (Extended BMP)\n"); break; case 0x33545844: wprintf(L"DXT3 (Extended BMP)\n"); break; case 0x35545844: wprintf(L"DXT5 (Extended BMP)\n"); break; default: wprintf(L"%lu compression type\n", header->biCompression); break; } wprintf(L"%lu image size\n", header->biSizeImage); wprintf(L"%ld x %ld pels per meter\n", header->biXPelsPerMeter, header->biYPelsPerMeter); wprintf(L"%lu colors used\n", header->biClrUsed); wprintf(L"%lu colors important\n", header->biClrImportant); } else { wprintf(L"BMP %ls\n\tCORE header (%lu bytes)\n", fname, coreHeader->bcSize); wprintf(L"%u x %u\n", coreHeader->bcWidth, coreHeader->bcHeight); wprintf(L"%u planes\n", coreHeader->bcPlanes); wprintf(L"%u bits\n", coreHeader->bcBitCount); } } } int wmain(int argc, wchar_t* argv[]) { if (argc < 2 || argc > 2) { printf("Usage: bmpdump \n"); return 0; } std::unique_ptr bmpData; HRESULT hr = LoadTextureDataFromFile(argv[1], bmpData); if (FAILED(hr)) { wprintf(L"ERROR: failed to load %ls\n", argv[1]); return -1; } OutputHeader(bmpData.get(), argv[1]); return 0; }