raster/geotiff/geotiff_read.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/raster/geotiff/geotiff.h" | ||
| 2 | |||
| 3 | #include "terraingen/raster/geotiff/tiff_codecs.h" | ||
| 4 | #include "terraingen/raster/geotiff/tiff_format.h" | ||
| 5 | |||
| 6 | #include "terraingen/util/log.h" | ||
| 7 | |||
| 8 | #include <algorithm> | ||
| 9 | #include <array> | ||
| 10 | #include <cstdlib> | ||
| 11 | #include <cstring> | ||
| 12 | #include <fstream> | ||
| 13 | #include <map> | ||
| 14 | |||
| 15 | namespace terraingen { | ||
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | using namespace detail; | ||
| 20 | |||
| 21 | /// `value` with its bytes reversed. | ||
| 22 | 1555 | template <typename T> T ByteSwap(T value) { | |
| 23 | 1555 | std::array<std::uint8_t, sizeof(T)> bytes{}; | |
| 24 | 1555 | std::memcpy(bytes.data(), &value, sizeof(T)); | |
| 25 | 1555 | std::reverse(bytes.begin(), bytes.end()); | |
| 26 | 1555 | std::memcpy(&value, bytes.data(), sizeof(T)); | |
| 27 | 1555 | return value; | |
| 28 | } | ||
| 29 | |||
| 30 | /// The name of an unsupported compression code, for error messages. | ||
| 31 | 7 | std::string CompressionName(std::uint64_t code) { | |
| 32 | 7 | switch (code) { | |
| 33 | 1 | case 7: | |
| 34 | 2 | return "JPEG"; | |
| 35 | 1 | case 34887: | |
| 36 | 2 | return "LERC"; | |
| 37 | 1 | case 34925: | |
| 38 | 2 | return "LZMA"; | |
| 39 | 2 | case 50000: | |
| 40 | 4 | return "ZSTD"; | |
| 41 | 1 | case 50001: | |
| 42 | 2 | return "WebP"; | |
| 43 | 1 | default: | |
| 44 | 1 | return "code " + std::to_string(code); | |
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Random access to a TIFF file in its byte order. | ||
| 49 | class TiffReader { | ||
| 50 | public: | ||
| 51 | 261 | explicit TiffReader(const std::filesystem::path& path) | |
| 52 | 261 | : path_(path), file_(path, std::ios::binary) { | |
| 53 | 261 | if (!file_) { | |
| 54 | 18 | Fail("cannot open the file"); | |
| 55 | } | ||
| 56 | 252 | file_.seekg(0, std::ios::end); | |
| 57 | 252 | size_ = static_cast<std::uint64_t>(file_.tellg()); | |
| 58 | 252 | ReadHeader(); | |
| 59 | 273 | } | |
| 60 | |||
| 61 | 33 | [[noreturn]] void Fail(const std::string& reason) const { | |
| 62 | 33 | throw GeoTiffError(path_.string() + ": " + reason); | |
| 63 | } | ||
| 64 | |||
| 65 | 2042 | void Read(std::uint64_t offset, void* destination, std::uint64_t count) { | |
| 66 | 2042 | RequireInFile(offset, count); | |
| 67 | 2041 | file_.seekg(static_cast<std::streamoff>(offset)); | |
| 68 | 2041 | file_.read(static_cast<char*>(destination), static_cast<std::streamsize>(count)); | |
| 69 | 2041 | if (!file_) { | |
| 70 | ✗ | Fail("read error"); | |
| 71 | } | ||
| 72 | 2041 | } | |
| 73 | |||
| 74 | 1533 | std::vector<std::uint8_t> ReadBytes(std::uint64_t offset, std::uint64_t count) { | |
| 75 | 1533 | RequireInFile(offset, count); | |
| 76 | 1532 | std::vector<std::uint8_t> bytes(static_cast<std::size_t>(count)); | |
| 77 | 1532 | Read(offset, bytes.data(), count); | |
| 78 | 1532 | return bytes; | |
| 79 | } | ||
| 80 | |||
| 81 | 19258 | std::uint16_t U16(const std::uint8_t* p) const { | |
| 82 | 19258 | const auto value = static_cast<std::uint16_t>(p[0] | (p[1] << 8)); | |
| 83 | 19258 | return bigEndian_ ? ByteSwap(value) : value; | |
| 84 | } | ||
| 85 | |||
| 86 | 6621 | std::uint32_t U32(const std::uint8_t* p) const { | |
| 87 | 6621 | std::uint32_t value = 0; | |
| 88 | 6621 | std::memcpy(&value, p, 4); | |
| 89 | 13242 | return bigEndian_ ? ByteSwap(value) : value; | |
| 90 | } | ||
| 91 | |||
| 92 | 3200 | std::uint64_t U64(const std::uint8_t* p) const { | |
| 93 | 3200 | std::uint64_t value = 0; | |
| 94 | 3200 | std::memcpy(&value, p, 8); | |
| 95 | 6400 | return bigEndian_ ? ByteSwap(value) : value; | |
| 96 | } | ||
| 97 | |||
| 98 | /// An unsigned value of `size` bytes, 2, 4 or 8. | ||
| 99 | 17505 | std::uint64_t Word(const std::uint8_t* p, std::size_t size) const { | |
| 100 | 17505 | switch (size) { | |
| 101 | 10745 | case 2: | |
| 102 | 10745 | return U16(p); | |
| 103 | 6381 | case 4: | |
| 104 | 6381 | return U32(p); | |
| 105 | 379 | default: | |
| 106 | 379 | return U64(p); | |
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | /// The entries of the first IFD. | ||
| 111 | 249 | std::map<std::uint16_t, Entry> ReadFirstIfd() { | |
| 112 | 249 | const IfdFormat format = IfdFormat::For(bigTiff_); | |
| 113 | 249 | std::array<std::uint8_t, 8> countBytes{}; | |
| 114 | 498 | Read(firstIfd_, countBytes.data(), format.countSize); | |
| 115 | 498 | const std::uint64_t entries = Word(countBytes.data(), format.countSize); | |
| 116 | 249 | if (entries > 4096) { | |
| 117 | ✗ | Fail("implausible number of IFD entries"); | |
| 118 | } | ||
| 119 | const std::vector<std::uint8_t> table = | ||
| 120 | 249 | ReadBytes(firstIfd_ + format.countSize, entries * format.EntrySize()); | |
| 121 | 249 | std::map<std::uint16_t, Entry> result; | |
| 122 | 4376 | for (std::uint64_t i = 0; i < entries; ++i) { | |
| 123 | 4127 | const std::uint8_t* p = table.data() + i * format.EntrySize(); | |
| 124 | 4127 | const std::uint16_t tag = U16(p); | |
| 125 | 4127 | Entry entry; | |
| 126 | 4127 | entry.type = U16(p + 2); | |
| 127 | 4127 | entry.count = Word(p + 4, format.wordSize); | |
| 128 | 4127 | const std::size_t fieldSize = FieldSize(entry.type); | |
| 129 | 4127 | if (fieldSize == 0) { | |
| 130 | ✗ | continue; | |
| 131 | } | ||
| 132 | 4127 | if (entry.count > size_ / fieldSize + 1) { | |
| 133 | ✗ | Fail("implausible count for tag " + std::to_string(tag)); | |
| 134 | } | ||
| 135 | 4127 | const std::uint8_t* value = p + format.ValueAt(); | |
| 136 | 4127 | const std::uint64_t bytes = entry.count * fieldSize; | |
| 137 | 4127 | if (bytes <= format.wordSize) { | |
| 138 | 3037 | entry.bytes.assign(value, value + bytes); | |
| 139 | } else { | ||
| 140 | 1090 | entry.bytes = ReadBytes(Word(value, format.wordSize), bytes); | |
| 141 | } | ||
| 142 | 4127 | result.emplace(tag, std::move(entry)); | |
| 143 | 4127 | } | |
| 144 | 498 | return result; | |
| 145 | 249 | } | |
| 146 | |||
| 147 | /// Unsigned integer values of an entry of any integer type. | ||
| 148 | 3003 | std::vector<std::uint64_t> Integers(const Entry& entry) const { | |
| 149 | 3003 | std::vector<std::uint64_t> values(static_cast<std::size_t>(entry.count)); | |
| 150 | 3003 | const std::size_t size = FieldSize(entry.type); | |
| 151 | 15042 | for (std::size_t i = 0; i < values.size(); ++i) { | |
| 152 | 12039 | const std::uint8_t* p = entry.bytes.data() + i * size; | |
| 153 | 12039 | switch (entry.type) { | |
| 154 | ✗ | case kByte: | |
| 155 | case kUndefined: | ||
| 156 | ✗ | values[i] = p[0]; | |
| 157 | ✗ | break; | |
| 158 | 12039 | case kShort: | |
| 159 | case kLong: | ||
| 160 | case kLong8: | ||
| 161 | case kIfd8: | ||
| 162 | 12039 | values[i] = Word(p, size); | |
| 163 | 12039 | break; | |
| 164 | ✗ | default: | |
| 165 | ✗ | Fail("unexpected field type " + std::to_string(entry.type)); | |
| 166 | } | ||
| 167 | } | ||
| 168 | 3003 | return values; | |
| 169 | } | ||
| 170 | |||
| 171 | 539 | std::vector<double> Doubles(const Entry& entry) const { | |
| 172 | 539 | if (entry.type != kDouble && entry.type != kFloat) { | |
| 173 | ✗ | Fail("expected floating-point values"); | |
| 174 | } | ||
| 175 | 539 | std::vector<double> values(static_cast<std::size_t>(entry.count)); | |
| 176 | 3351 | for (std::size_t i = 0; i < values.size(); ++i) { | |
| 177 | 2812 | if (entry.type == kDouble) { | |
| 178 | 2812 | values[i] = std::bit_cast<double>(U64(entry.bytes.data() + i * 8)); | |
| 179 | } else { | ||
| 180 | ✗ | values[i] = std::bit_cast<float>(U32(entry.bytes.data() + i * 4)); | |
| 181 | } | ||
| 182 | } | ||
| 183 | 539 | return values; | |
| 184 | } | ||
| 185 | |||
| 186 | 152 | static std::string Ascii(const Entry& entry) { | |
| 187 | 152 | std::string text(entry.bytes.begin(), entry.bytes.end()); | |
| 188 | 304 | while (!text.empty() && text.back() == '\0') { | |
| 189 | 152 | text.pop_back(); | |
| 190 | } | ||
| 191 | 152 | return text; | |
| 192 | } | ||
| 193 | |||
| 194 | 249 | bool BigEndian() const { | |
| 195 | 249 | return bigEndian_; | |
| 196 | } | ||
| 197 | |||
| 198 | 249 | bool BigTiff() const { | |
| 199 | 249 | return bigTiff_; | |
| 200 | } | ||
| 201 | |||
| 202 | private: | ||
| 203 | 3575 | void RequireInFile(std::uint64_t offset, std::uint64_t count) const { | |
| 204 | 3575 | if (offset > size_ || count > size_ - offset) { | |
| 205 | 4 | Fail("data lies past the end of the file"); | |
| 206 | } | ||
| 207 | 3573 | } | |
| 208 | |||
| 209 | 252 | void ReadHeader() { | |
| 210 | 252 | std::array<std::uint8_t, 16> header{}; | |
| 211 | 252 | Read(0, header.data(), 8); | |
| 212 | 251 | if (header[0] == 'I' && header[1] == 'I') { | |
| 213 | 241 | bigEndian_ = false; | |
| 214 | 10 | } else if (header[0] == 'M' && header[1] == 'M') { | |
| 215 | 9 | bigEndian_ = true; | |
| 216 | } else { | ||
| 217 | 2 | Fail("not a TIFF file"); | |
| 218 | } | ||
| 219 | 250 | const std::uint16_t version = U16(header.data() + 2); | |
| 220 | 250 | if (version == 42) { | |
| 221 | 240 | firstIfd_ = U32(header.data() + 4); | |
| 222 | 10 | } else if (version == 43) { | |
| 223 | 9 | bigTiff_ = true; | |
| 224 | 9 | Read(0, header.data(), 16); | |
| 225 | 9 | if (U16(header.data() + 4) != 8) { | |
| 226 | ✗ | Fail("unsupported BigTIFF offset size"); | |
| 227 | } | ||
| 228 | 9 | firstIfd_ = U64(header.data() + 8); | |
| 229 | } else { | ||
| 230 | 3 | Fail("unknown TIFF version " + std::to_string(version)); | |
| 231 | } | ||
| 232 | 249 | } | |
| 233 | |||
| 234 | std::filesystem::path path_; | ||
| 235 | std::ifstream file_; | ||
| 236 | std::uint64_t size_ = 0; | ||
| 237 | std::uint64_t firstIfd_ = 0; | ||
| 238 | bool bigEndian_ = false; | ||
| 239 | bool bigTiff_ = false; | ||
| 240 | }; | ||
| 241 | |||
| 242 | /// The first IFD of a file and the reader to interpret it. | ||
| 243 | struct Ifd { | ||
| 244 | TiffReader& reader; | ||
| 245 | std::map<std::uint16_t, Entry> entries; | ||
| 246 | |||
| 247 | 5737 | const Entry* Find(Tag tag) const { | |
| 248 | 5737 | const auto found = entries.find(tag); | |
| 249 | 5737 | return found == entries.end() ? nullptr : &found->second; | |
| 250 | } | ||
| 251 | |||
| 252 | /// The one value of `tag`, the same for every band, or `fallback` without the tag. | ||
| 253 | 2359 | std::uint64_t Single(Tag tag, std::uint64_t fallback) const { | |
| 254 | 2359 | const Entry* entry = Find(tag); | |
| 255 | 2359 | if (entry == nullptr) { | |
| 256 | 20 | return fallback; | |
| 257 | } | ||
| 258 | 2339 | const std::vector<std::uint64_t> values = reader.Integers(*entry); | |
| 259 | 2339 | if (values.empty()) { | |
| 260 | ✗ | reader.Fail("tag " + std::to_string(tag) + " has no value"); | |
| 261 | } | ||
| 262 | 4758 | for (const std::uint64_t value : values) { | |
| 263 | 2419 | if (value != values.front()) { | |
| 264 | ✗ | reader.Fail("bands with different values of tag " + std::to_string(tag) + | |
| 265 | " are not supported"); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | 2339 | return values.front(); | |
| 269 | 2339 | } | |
| 270 | |||
| 271 | 841 | std::uint64_t Required(Tag tag) const { | |
| 272 | 841 | if (Find(tag) == nullptr) { | |
| 273 | 3 | reader.Fail("missing required tag " + std::to_string(tag)); | |
| 274 | } | ||
| 275 | 840 | return Single(tag, 0); | |
| 276 | } | ||
| 277 | }; | ||
| 278 | |||
| 279 | /// Everything needed to decode the image of the first IFD. | ||
| 280 | struct Layout { | ||
| 281 | GeoTiffInfo info; | ||
| 282 | bool planar = false; | ||
| 283 | std::size_t chunkWidth = 0; ///< Tile width, or the image width for strips. | ||
| 284 | std::size_t chunkHeight = 0; ///< Tile length, or rows per strip. | ||
| 285 | std::size_t across = 0; ///< Chunks per row of chunks. | ||
| 286 | std::size_t down = 0; ///< Rows of chunks. | ||
| 287 | std::vector<std::uint64_t> offsets; | ||
| 288 | std::vector<std::uint64_t> byteCounts; | ||
| 289 | |||
| 290 | /// Samples per pixel within one chunk: 1 for planar bands, otherwise every band. | ||
| 291 | 580 | std::size_t SamplesInChunk() const { | |
| 292 | 580 | return planar ? 1 : info.bands; | |
| 293 | } | ||
| 294 | }; | ||
| 295 | |||
| 296 | 249 | void ImageSize(const Ifd& ifd, GeoTiffInfo& info) { | |
| 297 | 249 | info.width = static_cast<std::size_t>(ifd.Required(kImageWidth)); | |
| 298 | 248 | info.height = static_cast<std::size_t>(ifd.Required(kImageLength)); | |
| 299 | 248 | if (info.width == 0 || info.height == 0 || info.width > (1u << 30) || | |
| 300 | 247 | info.height > (1u << 30)) { | |
| 301 | 3 | ifd.reader.Fail("implausible image size"); | |
| 302 | } | ||
| 303 | 247 | } | |
| 304 | |||
| 305 | 247 | void ParseSampleFormat(const Ifd& ifd, GeoTiffInfo& info) { | |
| 306 | 247 | info.bitsPerSample = static_cast<int>(ifd.Single(kBitsPerSample, 1)); | |
| 307 | 247 | if (info.bitsPerSample != 8 && info.bitsPerSample != 16 && info.bitsPerSample != 32 && | |
| 308 | 9 | info.bitsPerSample != 64) { | |
| 309 | 3 | ifd.reader.Fail(std::to_string(info.bitsPerSample) + "-bit samples are not supported"); | |
| 310 | } | ||
| 311 | 246 | switch (ifd.Single(kSampleFormat, 1)) { | |
| 312 | 16 | case 1: | |
| 313 | 16 | info.sampleFormat = TiffSampleFormat::UnsignedInteger; | |
| 314 | 16 | break; | |
| 315 | 28 | case 2: | |
| 316 | 28 | info.sampleFormat = TiffSampleFormat::SignedInteger; | |
| 317 | 28 | break; | |
| 318 | 201 | case 3: | |
| 319 | 201 | info.sampleFormat = TiffSampleFormat::Float; | |
| 320 | 201 | if (info.bitsPerSample != 32 && info.bitsPerSample != 64) { | |
| 321 | 3 | ifd.reader.Fail(std::to_string(info.bitsPerSample) + "-bit floats are not supported"); | |
| 322 | } | ||
| 323 | 200 | break; | |
| 324 | 1 | default: | |
| 325 | 3 | ifd.reader.Fail("unsupported sample format (complex or undefined)"); | |
| 326 | } | ||
| 327 | 244 | } | |
| 328 | |||
| 329 | 244 | void ParseCompression(const Ifd& ifd, GeoTiffInfo& info) { | |
| 330 | 244 | const std::uint64_t code = ifd.Single(kCompression, kCompressionNone); | |
| 331 | 244 | const std::optional<TiffCompression> compression = CompressionFromCode(code); | |
| 332 | 244 | if (!compression) { | |
| 333 | 21 | ifd.reader.Fail(CompressionName(code) + " compression is not supported; convert with " | |
| 334 | "gdal_translate -co COMPRESS=DEFLATE"); | ||
| 335 | } | ||
| 336 | 237 | info.compression = *compression; | |
| 337 | 237 | } | |
| 338 | |||
| 339 | 237 | void ParsePredictor(const Ifd& ifd, GeoTiffInfo& info) { | |
| 340 | 237 | info.predictor = static_cast<int>(ifd.Single(kPredictor, 1)); | |
| 341 | 237 | if (info.predictor < 1 || info.predictor > 3) { | |
| 342 | 3 | ifd.reader.Fail("unknown predictor " + std::to_string(info.predictor)); | |
| 343 | } | ||
| 344 | 236 | if (info.predictor == 3 && info.sampleFormat != TiffSampleFormat::Float) { | |
| 345 | 3 | ifd.reader.Fail("the floating-point predictor needs float samples"); | |
| 346 | } | ||
| 347 | 235 | } | |
| 348 | |||
| 349 | /// Reads the tile or strip sizes, counts, offsets and byte counts into `layout`. | ||
| 350 | 235 | void ReadChunks(const Ifd& ifd, Layout& layout) { | |
| 351 | 235 | const GeoTiffInfo& info = layout.info; | |
| 352 | 235 | Tag offsetsTag = kStripOffsets; | |
| 353 | 235 | Tag countsTag = kStripByteCounts; | |
| 354 | 235 | if (info.tiled) { | |
| 355 | 172 | layout.chunkWidth = static_cast<std::size_t>(ifd.Required(kTileWidth)); | |
| 356 | 172 | layout.chunkHeight = static_cast<std::size_t>(ifd.Required(kTileLength)); | |
| 357 | 172 | offsetsTag = kTileOffsets; | |
| 358 | 172 | countsTag = kTileByteCounts; | |
| 359 | } else { | ||
| 360 | 63 | layout.chunkWidth = info.width; | |
| 361 | 63 | layout.chunkHeight = static_cast<std::size_t>( | |
| 362 | 63 | std::min<std::uint64_t>(ifd.Single(kRowsPerStrip, info.height), info.height)); | |
| 363 | } | ||
| 364 | 235 | const Entry* offsets = ifd.Find(offsetsTag); | |
| 365 | 235 | const Entry* counts = ifd.Find(countsTag); | |
| 366 | 235 | if (offsets == nullptr || counts == nullptr) { | |
| 367 | 10 | ifd.reader.Fail(std::string("missing ") + (info.tiled ? "tile" : "strip") + | |
| 368 | " offsets or byte counts"); | ||
| 369 | } | ||
| 370 | 233 | layout.offsets = ifd.reader.Integers(*offsets); | |
| 371 | 233 | layout.byteCounts = ifd.reader.Integers(*counts); | |
| 372 | 233 | if (layout.chunkWidth == 0 || layout.chunkHeight == 0) { | |
| 373 | 3 | ifd.reader.Fail("zero tile or strip size"); | |
| 374 | } | ||
| 375 | 232 | layout.across = (info.width + layout.chunkWidth - 1) / layout.chunkWidth; | |
| 376 | 232 | layout.down = (info.height + layout.chunkHeight - 1) / layout.chunkHeight; | |
| 377 | 232 | const std::size_t chunks = layout.across * layout.down * (layout.planar ? info.bands : 1); | |
| 378 | 232 | if (layout.offsets.size() < chunks || layout.byteCounts.size() < chunks) { | |
| 379 | 3 | ifd.reader.Fail("fewer tile or strip offsets than the image needs"); | |
| 380 | } | ||
| 381 | 231 | } | |
| 382 | |||
| 383 | 231 | GeoKeys ReadGeoKeys(const Ifd& ifd) { | |
| 384 | 231 | GeoKeys keys; | |
| 385 | 231 | if (const Entry* directory = ifd.Find(kGeoKeyDirectory)) { | |
| 386 | 8842 | for (const std::uint64_t value : ifd.reader.Integers(*directory)) { | |
| 387 | 8644 | keys.directory.push_back(static_cast<std::uint16_t>(value)); | |
| 388 | 198 | } | |
| 389 | } | ||
| 390 | 231 | if (const Entry* doubles = ifd.Find(kGeoDoubleParams)) { | |
| 391 | 91 | keys.doubleParams = ifd.reader.Doubles(*doubles); | |
| 392 | } | ||
| 393 | 231 | if (const Entry* ascii = ifd.Find(kGeoAsciiParams)) { | |
| 394 | 87 | keys.asciiParams = TiffReader::Ascii(*ascii); | |
| 395 | } | ||
| 396 | 231 | return keys; | |
| 397 | } | ||
| 398 | |||
| 399 | /// The pixel-to-world transform of the model tags, and whether there were any. | ||
| 400 | 231 | GeoTransform TransformFromTags(const Ifd& ifd, bool& georeferenced) { | |
| 401 | 231 | georeferenced = false; | |
| 402 | 231 | if (const Entry* matrix = ifd.Find(kModelTransformation)) { | |
| 403 | 8 | const std::vector<double> m = ifd.reader.Doubles(*matrix); | |
| 404 | 8 | if (m.size() < 16) { | |
| 405 | ✗ | ifd.reader.Fail("ModelTransformation needs 16 values"); | |
| 406 | } | ||
| 407 | 8 | georeferenced = true; | |
| 408 | 8 | return GeoTransform::FromGdal({m[3], m[0], m[1], m[7], m[4], m[5]}); | |
| 409 | 8 | } | |
| 410 | 223 | const Entry* scale = ifd.Find(kModelPixelScale); | |
| 411 | 223 | const Entry* tiepoint = ifd.Find(kModelTiepoint); | |
| 412 | 223 | if (scale == nullptr || tiepoint == nullptr) { | |
| 413 | 3 | return {}; | |
| 414 | } | ||
| 415 | 220 | const std::vector<double> s = ifd.reader.Doubles(*scale); | |
| 416 | 220 | const std::vector<double> t = ifd.reader.Doubles(*tiepoint); | |
| 417 | 220 | if (s.size() < 2 || t.size() < 6) { | |
| 418 | ✗ | ifd.reader.Fail("ModelPixelScale needs 2 and ModelTiepoint 6 values"); | |
| 419 | } | ||
| 420 | 220 | if (t.size() > 6) { | |
| 421 | ✗ | ifd.reader.Fail("multiple tiepoints (ground control points) are not supported"); | |
| 422 | } | ||
| 423 | 220 | georeferenced = true; | |
| 424 | 220 | return GeoTransform::FromGdal({t[3] - t[0] * s[0], s[0], 0.0, t[4] + t[1] * s[1], 0.0, -s[1]}); | |
| 425 | 220 | } | |
| 426 | |||
| 427 | 231 | std::optional<double> ParseNoData(const Ifd& ifd) { | |
| 428 | 231 | const Entry* noData = ifd.Find(kGdalNoData); | |
| 429 | 231 | if (noData == nullptr) { | |
| 430 | 186 | return std::nullopt; | |
| 431 | } | ||
| 432 | 45 | const std::string text = TiffReader::Ascii(*noData); | |
| 433 | 45 | char* end = nullptr; | |
| 434 | 45 | const double value = std::strtod(text.c_str(), &end); | |
| 435 | 45 | return end != text.c_str() ? std::optional<double>(value) : std::nullopt; | |
| 436 | 45 | } | |
| 437 | |||
| 438 | 249 | Layout ReadLayout(TiffReader& reader) { | |
| 439 | 249 | const Ifd ifd{reader, reader.ReadFirstIfd()}; | |
| 440 | 249 | Layout layout; | |
| 441 | 249 | GeoTiffInfo& info = layout.info; | |
| 442 | 249 | info.bigEndian = reader.BigEndian(); | |
| 443 | 249 | info.bigTiff = reader.BigTiff(); | |
| 444 | 249 | ImageSize(ifd, info); | |
| 445 | 247 | info.bands = static_cast<std::size_t>(ifd.Single(kSamplesPerPixel, 1)); | |
| 446 | 247 | ParseSampleFormat(ifd, info); | |
| 447 | 244 | ParseCompression(ifd, info); | |
| 448 | 237 | ParsePredictor(ifd, info); | |
| 449 | 235 | layout.planar = ifd.Single(kPlanarConfiguration, 1) == 2; | |
| 450 | 235 | info.tiled = ifd.Find(kTileWidth) != nullptr; | |
| 451 | 235 | ReadChunks(ifd, layout); | |
| 452 | 231 | info.geoKeys = ReadGeoKeys(ifd); | |
| 453 | 231 | info.transform = TransformFromTags(ifd, info.georeferenced); | |
| 454 | 231 | if (info.georeferenced && info.geoKeys.PixelIsPoint()) { | |
| 455 | 5 | info.transform = ShiftHalfPixel(info.transform, -1.0); | |
| 456 | } | ||
| 457 | 231 | const Entry* metadata = ifd.Find(kGdalMetadata); | |
| 458 | 462 | info.bandScaling = ParseGdalBandScaling( | |
| 459 | 693 | metadata != nullptr ? TiffReader::Ascii(*metadata) : std::string(), info.bands); | |
| 460 | 231 | info.noData = ParseNoData(ifd); | |
| 461 | 462 | return layout; | |
| 462 | 267 | } | |
| 463 | |||
| 464 | /// Adds each sample of a row to the one `stride` samples before it (horizontal predictor). | ||
| 465 | template <typename T> | ||
| 466 | 114 | void AccumulateRow(std::uint8_t* p, std::size_t rowSamples, std::size_t stride) { | |
| 467 | 3154 | for (std::size_t i = stride; i < rowSamples; ++i) { | |
| 468 | 3040 | T current = 0; | |
| 469 | 3040 | T previous = 0; | |
| 470 | 3040 | std::memcpy(¤t, p + i * sizeof(T), sizeof(T)); | |
| 471 | 3040 | std::memcpy(&previous, p + (i - stride) * sizeof(T), sizeof(T)); | |
| 472 | 3040 | current = static_cast<T>(current + previous); | |
| 473 | 3040 | std::memcpy(p + i * sizeof(T), ¤t, sizeof(T)); | |
| 474 | } | ||
| 475 | 114 | } | |
| 476 | |||
| 477 | 114 | void UndoHorizontalPredictor(std::uint8_t* p, std::size_t sampleBytes, std::size_t rowSamples, | |
| 478 | std::size_t stride) { | ||
| 479 | 114 | switch (sampleBytes) { | |
| 480 | ✗ | case 1: | |
| 481 | ✗ | AccumulateRow<std::uint8_t>(p, rowSamples, stride); | |
| 482 | ✗ | break; | |
| 483 | 76 | case 2: | |
| 484 | 76 | AccumulateRow<std::uint16_t>(p, rowSamples, stride); | |
| 485 | 76 | break; | |
| 486 | 38 | case 4: | |
| 487 | 38 | AccumulateRow<std::uint32_t>(p, rowSamples, stride); | |
| 488 | 38 | break; | |
| 489 | ✗ | default: | |
| 490 | ✗ | AccumulateRow<std::uint64_t>(p, rowSamples, stride); | |
| 491 | ✗ | break; | |
| 492 | } | ||
| 493 | 114 | } | |
| 494 | |||
| 495 | /// Reverses a chunk's predictor in place; `bytes` holds `rows` rows of `rowSamples` samples. | ||
| 496 | 193 | void UndoPredictor(std::vector<std::uint8_t>& bytes, const GeoTiffInfo& info, std::size_t rows, | |
| 497 | std::size_t rowSamples, std::size_t stride) { | ||
| 498 | 193 | if (info.predictor != 2 && info.predictor != 3) { | |
| 499 | 51 | return; | |
| 500 | } | ||
| 501 | 142 | const std::size_t sampleBytes = static_cast<std::size_t>(info.bitsPerSample) / 8; | |
| 502 | 142 | const std::size_t rowBytes = rowSamples * sampleBytes; | |
| 503 | 13958 | for (std::size_t row = 0; row < rows; ++row) { | |
| 504 | 13816 | std::uint8_t* p = bytes.data() + row * rowBytes; | |
| 505 | 13816 | if (info.predictor == 2) { | |
| 506 | 114 | UndoHorizontalPredictor(p, sampleBytes, rowSamples, stride); | |
| 507 | } else { | ||
| 508 | 13702 | DecodeFloatPredictorRow(p, sampleBytes, rowSamples, stride); | |
| 509 | } | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | /// Swaps each sample of `bytes` from big- to little-endian. | ||
| 514 | 6 | void SwapSamples(std::vector<std::uint8_t>& bytes, std::size_t sampleBytes) { | |
| 515 | 6 | if (sampleBytes == 1) { | |
| 516 | ✗ | return; | |
| 517 | } | ||
| 518 | 1828 | for (std::size_t i = 0; i + sampleBytes <= bytes.size(); i += sampleBytes) { | |
| 519 | 1822 | std::reverse(bytes.begin() + static_cast<std::ptrdiff_t>(i), | |
| 520 | 3644 | bytes.begin() + static_cast<std::ptrdiff_t>(i + sampleBytes)); | |
| 521 | } | ||
| 522 | } | ||
| 523 | |||
| 524 | 178813 | template <typename T> float ReadSample(const std::uint8_t* p) { | |
| 525 | 178813 | T value{}; | |
| 526 | 178813 | std::memcpy(&value, p, sizeof(T)); | |
| 527 | 178813 | return static_cast<float>(value); | |
| 528 | } | ||
| 529 | |||
| 530 | 178813 | float SampleAt(const std::uint8_t* p, const GeoTiffInfo& info) { | |
| 531 | 178813 | switch (info.sampleFormat) { | |
| 532 | 171631 | case TiffSampleFormat::Float: | |
| 533 | 171631 | return info.bitsPerSample == 32 ? ReadSample<float>(p) : ReadSample<double>(p); | |
| 534 | 4389 | case TiffSampleFormat::SignedInteger: | |
| 535 | 4389 | switch (info.bitsPerSample) { | |
| 536 | 399 | case 8: | |
| 537 | 399 | return ReadSample<std::int8_t>(p); | |
| 538 | 3192 | case 16: | |
| 539 | 3192 | return ReadSample<std::int16_t>(p); | |
| 540 | 399 | case 32: | |
| 541 | 399 | return ReadSample<std::int32_t>(p); | |
| 542 | 399 | default: | |
| 543 | 399 | return ReadSample<std::int64_t>(p); | |
| 544 | } | ||
| 545 | 2793 | default: | |
| 546 | 2793 | switch (info.bitsPerSample) { | |
| 547 | 399 | case 8: | |
| 548 | 399 | return ReadSample<std::uint8_t>(p); | |
| 549 | 1995 | case 16: | |
| 550 | 1995 | return ReadSample<std::uint16_t>(p); | |
| 551 | 399 | case 32: | |
| 552 | 399 | return ReadSample<std::uint32_t>(p); | |
| 553 | ✗ | default: | |
| 554 | ✗ | return ReadSample<std::uint64_t>(p); | |
| 555 | } | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | /// The stored bytes of chunk `chunk` decompressed, or zeros for a chunk with none. | ||
| 560 | 194 | std::vector<std::uint8_t> Decompress(TiffReader& reader, const Layout& layout, std::size_t chunk, | |
| 561 | std::size_t expected) { | ||
| 562 | 194 | const std::uint64_t stored = layout.byteCounts[chunk]; | |
| 563 | 194 | if (stored == 0) { | |
| 564 | ✗ | return std::vector<std::uint8_t>(expected, 0); | |
| 565 | } | ||
| 566 | 194 | std::vector<std::uint8_t> raw = reader.ReadBytes(layout.offsets[chunk], stored); | |
| 567 | 193 | switch (layout.info.compression) { | |
| 568 | 117 | case TiffCompression::Lzw: | |
| 569 | 117 | return DecodeLzw(raw, expected); | |
| 570 | 1 | case TiffCompression::PackBits: | |
| 571 | 1 | return DecodePackBits(raw, expected); | |
| 572 | 40 | case TiffCompression::Deflate: | |
| 573 | 40 | return DecodeDeflate(raw, expected); | |
| 574 | 35 | default: | |
| 575 | 35 | if (raw.size() < expected) { | |
| 576 | ✗ | reader.Fail("truncated uncompressed chunk"); | |
| 577 | } | ||
| 578 | 35 | raw.resize(expected); | |
| 579 | 35 | return raw; | |
| 580 | } | ||
| 581 | 193 | } | |
| 582 | |||
| 583 | /// Chunk `chunk`, holding `rows` rows, as little-endian samples without a predictor. | ||
| 584 | 194 | std::vector<std::uint8_t> DecodeChunk(TiffReader& reader, const Layout& layout, std::size_t chunk, | |
| 585 | std::size_t rows) { | ||
| 586 | 194 | const GeoTiffInfo& info = layout.info; | |
| 587 | 194 | const std::size_t sampleBytes = static_cast<std::size_t>(info.bitsPerSample) / 8; | |
| 588 | 194 | const std::size_t rowSamples = layout.chunkWidth * layout.SamplesInChunk(); | |
| 589 | std::vector<std::uint8_t> bytes = | ||
| 590 | 194 | Decompress(reader, layout, chunk, rows * rowSamples * sampleBytes); | |
| 591 | 193 | if (info.bigEndian && info.predictor != 3) { | |
| 592 | 6 | SwapSamples(bytes, sampleBytes); | |
| 593 | } | ||
| 594 | 193 | UndoPredictor(bytes, info, rows, rowSamples, layout.SamplesInChunk()); | |
| 595 | 193 | return bytes; | |
| 596 | } | ||
| 597 | |||
| 598 | /// Copies sample `sampleIndex` of the decoded chunk at column `cx` and row `cy` of chunks into | ||
| 599 | /// `raster`. | ||
| 600 | 193 | void CopyChunk(const std::vector<std::uint8_t>& bytes, const Layout& layout, std::size_t cx, | |
| 601 | std::size_t cy, std::size_t rows, std::size_t sampleIndex, Raster<float>& raster) { | ||
| 602 | 193 | const GeoTiffInfo& info = layout.info; | |
| 603 | 193 | const std::size_t sampleBytes = static_cast<std::size_t>(info.bitsPerSample) / 8; | |
| 604 | 193 | const std::size_t samplesInChunk = layout.SamplesInChunk(); | |
| 605 | 193 | const std::size_t rowBytes = layout.chunkWidth * samplesInChunk * sampleBytes; | |
| 606 | 193 | const std::size_t x0 = cx * layout.chunkWidth; | |
| 607 | 193 | const std::size_t y0 = cy * layout.chunkHeight; | |
| 608 | 193 | const std::size_t width = std::min(layout.chunkWidth, info.width - x0); | |
| 609 | 193 | const std::size_t height = std::min(rows, info.height - y0); | |
| 610 | 3967 | for (std::size_t y = 0; y < height; ++y) { | |
| 611 | 3774 | const std::uint8_t* row = bytes.data() + y * rowBytes; | |
| 612 | 182587 | for (std::size_t x = 0; x < width; ++x) { | |
| 613 | 178813 | raster(x0 + x, y0 + y) = | |
| 614 | 178813 | SampleAt(row + (x * samplesInChunk + sampleIndex) * sampleBytes, info); | |
| 615 | } | ||
| 616 | } | ||
| 617 | 193 | } | |
| 618 | |||
| 619 | } // namespace | ||
| 620 | |||
| 621 | 146 | GeoTiffInfo ReadGeoTiffInfo(const std::filesystem::path& path) { | |
| 622 | 146 | TiffReader reader(path); | |
| 623 | 278 | return ReadLayout(reader).info; | |
| 624 | 139 | } | |
| 625 | |||
| 626 | 115 | GeoTiff ReadGeoTiff(const std::filesystem::path& path, std::size_t band) { | |
| 627 | 115 | const Stopwatch stopwatch; | |
| 628 | 115 | TiffReader reader(path); | |
| 629 | 110 | const Layout layout = ReadLayout(reader); | |
| 630 | 92 | const GeoTiffInfo& info = layout.info; | |
| 631 | 92 | if (band >= info.bands) { | |
| 632 | 12 | reader.Fail("band " + std::to_string(band) + " requested of " + std::to_string(info.bands)); | |
| 633 | } | ||
| 634 | 90 | const std::size_t planeOffset = layout.planar ? band * layout.across * layout.down : 0; | |
| 635 | 90 | const std::size_t sampleIndex = layout.planar ? 0 : band; | |
| 636 | 90 | GeoTiff result{Raster<float>(info.width, info.height), info.geoKeys}; | |
| 637 | 90 | result.raster.SetTransform(info.transform); | |
| 638 | 90 | if (info.noData) { | |
| 639 | 22 | result.raster.SetNoData(static_cast<float>(*info.noData)); | |
| 640 | } | ||
| 641 | 203 | for (std::size_t cy = 0; cy < layout.down; ++cy) { | |
| 642 | const std::size_t rows = | ||
| 643 | 142 | info.tiled ? layout.chunkHeight | |
| 644 | 28 | : std::min(layout.chunkHeight, info.height - cy * layout.chunkHeight); | |
| 645 | 307 | for (std::size_t cx = 0; cx < layout.across; ++cx) { | |
| 646 | 194 | const std::size_t chunk = planeOffset + cy * layout.across + cx; | |
| 647 | 194 | CopyChunk(DecodeChunk(reader, layout, chunk, rows), layout, cx, cy, rows, sampleIndex, | |
| 648 | 194 | result.raster); | |
| 649 | } | ||
| 650 | } | ||
| 651 | 89 | LogInfo("raster", "Read ", info.width, "x", info.height, " GeoTIFF ", path.string(), " in ", | |
| 652 | 178 | FormatDuration(stopwatch.Milliseconds())); | |
| 653 | 178 | return result; | |
| 654 | 114 | } | |
| 655 | |||
| 656 | } // namespace terraingen | ||
| 657 |