| 102 | } |
| 103 | |
| 104 | Image ReadBmp24(const std::string& bmpPath) |
| 105 | { |
| 106 | std::ifstream bmpStream(bmpPath, std::ios::binary | std::ios::ate); |
| 107 | if (!bmpStream.is_open()) { |
| 108 | throw std::runtime_error("failed to open BMP file: " + bmpPath); |
| 109 | } |
| 110 | |
| 111 | const auto fileSize = bmpStream.tellg(); |
| 112 | if (fileSize < 0) { |
| 113 | throw std::runtime_error("failed to determine BMP file size: " + bmpPath); |
| 114 | } |
| 115 | |
| 116 | std::vector<uint8_t> bytes(static_cast<size_t>(fileSize)); |
| 117 | bmpStream.seekg(0, std::ios_base::beg); |
| 118 | bmpStream.read(reinterpret_cast<char*>(bytes.data()), |
| 119 | static_cast<std::streamsize>(bytes.size())); |
| 120 | |
| 121 | if (static_cast<size_t>(bmpStream.gcount()) != bytes.size()) { |
| 122 | throw std::runtime_error("BMP payload size mismatch in: " + bmpPath); |
| 123 | } |
| 124 | |
| 125 | constexpr size_t kBmpFileHeaderBytes = 14; |
| 126 | constexpr size_t kMinDibHeaderBytes = 40; |
| 127 | constexpr uint16_t kBmpMagic = 0x4D42; |
| 128 | constexpr uint16_t kRgbBitsPerPixel = 24; |
| 129 | constexpr uint32_t kNoCompression = 0; |
| 130 | if (bytes.size() < kBmpFileHeaderBytes + kMinDibHeaderBytes || |
| 131 | ReadLe16(bytes, 0) != kBmpMagic) { |
| 132 | throw std::runtime_error("invalid BMP header: " + bmpPath); |
| 133 | } |
| 134 | |
| 135 | const uint32_t pixelOffset = ReadLe32(bytes, 10); |
| 136 | const uint32_t dibBytes = ReadLe32(bytes, 14); |
| 137 | const int32_t width = ReadLeS32(bytes, 18); |
| 138 | const int32_t height = ReadLeS32(bytes, 22); |
| 139 | const uint16_t planes = ReadLe16(bytes, 26); |
| 140 | const uint16_t bpp = ReadLe16(bytes, 28); |
| 141 | const uint32_t compression = ReadLe32(bytes, 30); |
| 142 | |
| 143 | if (dibBytes < kMinDibHeaderBytes || width <= 0 || height == 0 || planes != 1U || |
| 144 | bpp != kRgbBitsPerPixel || compression != kNoCompression) { |
| 145 | throw std::runtime_error("only uncompressed 24-bit BMP input is supported: " + bmpPath); |
| 146 | } |
| 147 | |
| 148 | const int absHeight = height < 0 ? -height : height; |
| 149 | const size_t rowBytes = |
| 150 | ((static_cast<size_t>(width) * static_cast<size_t>(bpp) + 31U) / 32U) * 4U; |
| 151 | const size_t requiredBytes = static_cast<size_t>(pixelOffset) + |
| 152 | rowBytes * static_cast<size_t>(absHeight); |
| 153 | if (requiredBytes > bytes.size()) { |
| 154 | throw std::runtime_error("BMP pixel data is truncated: " + bmpPath); |
| 155 | } |
| 156 | |
| 157 | std::vector<uint8_t> rgb(static_cast<size_t>(width) * static_cast<size_t>(absHeight) * 3U); |
| 158 | for (int row = 0; row < absHeight; ++row) { |
| 159 | const int srcRow = height > 0 ? absHeight - 1 - row : row; |
| 160 | const size_t src = static_cast<size_t>(pixelOffset) + rowBytes * static_cast<size_t>(srcRow); |
| 161 | const size_t dst = static_cast<size_t>(row) * static_cast<size_t>(width) * 3U; |