TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
463 / 511
90.6%
Branches:
332 / 391
84.9%
Functions:
53 / 56
94.6%

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
3/3
unsigned int terraingen::(anonymous namespace)::ByteSwap<unsigned int>(unsigned int):
✓ Branch 1 taken 233 times.
unsigned long terraingen::(anonymous namespace)::ByteSwap<unsigned long>(unsigned long):
✓ Branch 1 taken 153 times.
unsigned short terraingen::(anonymous namespace)::ByteSwap<unsigned short>(unsigned short):
✓ Branch 1 taken 1169 times.
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
6/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
7 switch (code) {
33 1 case 7:
34
1/1
✓ Branch 1 taken 1 time.
2 return "JPEG";
35 1 case 34887:
36
1/1
✓ Branch 1 taken 1 time.
2 return "LERC";
37 1 case 34925:
38
1/1
✓ Branch 1 taken 1 time.
2 return "LZMA";
39 2 case 50000:
40
1/1
✓ Branch 1 taken 2 times.
4 return "ZSTD";
41 1 case 50001:
42
1/1
✓ Branch 1 taken 1 time.
2 return "WebP";
43 1 default:
44
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
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
1/1
✓ Branch 2 taken 261 times.
261 : path_(path), file_(path, std::ios::binary) {
53
3/3
✓ Branch 1 taken 261 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 252 times.
261 if (!file_) {
54
1/1
✓ Branch 1 taken 9 times.
18 Fail("cannot open the file");
55 }
56
1/1
✓ Branch 1 taken 252 times.
252 file_.seekg(0, std::ios::end);
57
1/1
✓ Branch 1 taken 252 times.
252 size_ = static_cast<std::uint64_t>(file_.tellg());
58
1/1
✓ Branch 1 taken 249 times.
252 ReadHeader();
59 273 }
60
61 33 [[noreturn]] void Fail(const std::string& reason) const {
62
4/4
✓ Branch 2 taken 33 times.
✓ Branch 5 taken 33 times.
✓ Branch 8 taken 33 times.
✓ Branch 11 taken 33 times.
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
1/1
✓ Branch 2 taken 2041 times.
2041 file_.seekg(static_cast<std::streamoff>(offset));
68 2041 file_.read(static_cast<char*>(destination), static_cast<std::streamsize>(count));
69
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2041 times.
2041 if (!file_) {
70
0/1
✗ Branch 1 not taken.
✗ 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
1/1
✓ Branch 1 taken 1532 times.
1532 std::vector<std::uint8_t> bytes(static_cast<std::size_t>(count));
77
1/1
✓ Branch 2 taken 1532 times.
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
2/2
✓ Branch 0 taken 1169 times.
✓ Branch 1 taken 18089 times.
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
3/3
✓ Branch 0 taken 233 times.
✓ Branch 1 taken 6388 times.
✓ Branch 3 taken 233 times.
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
3/3
✓ Branch 0 taken 153 times.
✓ Branch 1 taken 3047 times.
✓ Branch 3 taken 153 times.
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
3/3
✓ Branch 0 taken 10745 times.
✓ Branch 1 taken 6381 times.
✓ Branch 2 taken 379 times.
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
1/1
✓ Branch 1 taken 249 times.
498 Read(firstIfd_, countBytes.data(), format.countSize);
115
1/1
✓ Branch 1 taken 249 times.
498 const std::uint64_t entries = Word(countBytes.data(), format.countSize);
116
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249 times.
249 if (entries > 4096) {
117
0/1
✗ Branch 1 not taken.
✗ Fail("implausible number of IFD entries");
118 }
119 const std::vector<std::uint8_t> table =
120
1/1
✓ Branch 2 taken 249 times.
249 ReadBytes(firstIfd_ + format.countSize, entries * format.EntrySize());
121 249 std::map<std::uint16_t, Entry> result;
122
2/2
✓ Branch 0 taken 4127 times.
✓ Branch 1 taken 249 times.
4376 for (std::uint64_t i = 0; i < entries; ++i) {
123 4127 const std::uint8_t* p = table.data() + i * format.EntrySize();
124
1/1
✓ Branch 1 taken 4127 times.
4127 const std::uint16_t tag = U16(p);
125 4127 Entry entry;
126
1/1
✓ Branch 1 taken 4127 times.
4127 entry.type = U16(p + 2);
127
1/1
✓ Branch 1 taken 4127 times.
4127 entry.count = Word(p + 4, format.wordSize);
128
1/1
✓ Branch 1 taken 4127 times.
4127 const std::size_t fieldSize = FieldSize(entry.type);
129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4127 times.
4127 if (fieldSize == 0) {
130 ✗ continue;
131 }
132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4127 times.
4127 if (entry.count > size_ / fieldSize + 1) {
133
0/1
✗ Branch 2 not taken.
✗ 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
2/2
✓ Branch 0 taken 3037 times.
✓ Branch 1 taken 1090 times.
4127 if (bytes <= format.wordSize) {
138
1/1
✓ Branch 1 taken 3037 times.
3037 entry.bytes.assign(value, value + bytes);
139 } else {
140
2/2
✓ Branch 1 taken 1090 times.
✓ Branch 4 taken 1090 times.
1090 entry.bytes = ReadBytes(Word(value, format.wordSize), bytes);
141 }
142
1/1
✓ Branch 2 taken 4127 times.
4127 result.emplace(tag, std::move(entry));
143
1/2
✓ Branch 1 taken 4127 times.
✗ Branch 2 not taken.
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
1/1
✓ Branch 1 taken 3003 times.
3003 std::vector<std::uint64_t> values(static_cast<std::size_t>(entry.count));
150
1/1
✓ Branch 1 taken 3003 times.
3003 const std::size_t size = FieldSize(entry.type);
151
2/2
✓ Branch 1 taken 12039 times.
✓ Branch 2 taken 3003 times.
15042 for (std::size_t i = 0; i < values.size(); ++i) {
152 12039 const std::uint8_t* p = entry.bytes.data() + i * size;
153
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 12039 times.
✗ Branch 2 not taken.
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
1/1
✓ Branch 1 taken 12039 times.
12039 values[i] = Word(p, size);
163 12039 break;
164 ✗ default:
165
0/1
✗ Branch 2 not taken.
✗ 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
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
539 if (entry.type != kDouble && entry.type != kFloat) {
173
0/1
✗ Branch 1 not taken.
✗ Fail("expected floating-point values");
174 }
175
1/1
✓ Branch 1 taken 539 times.
539 std::vector<double> values(static_cast<std::size_t>(entry.count));
176
2/2
✓ Branch 1 taken 2812 times.
✓ Branch 2 taken 539 times.
3351 for (std::size_t i = 0; i < values.size(); ++i) {
177
1/2
✓ Branch 0 taken 2812 times.
✗ Branch 1 not taken.
2812 if (entry.type == kDouble) {
178
1/1
✓ Branch 2 taken 2812 times.
2812 values[i] = std::bit_cast<double>(U64(entry.bytes.data() + i * 8));
179 } else {
180
0/1
✗ Branch 2 not taken.
✗ 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
1/1
✓ Branch 3 taken 152 times.
152 std::string text(entry.bytes.begin(), entry.bytes.end());
188
5/6
✓ Branch 1 taken 304 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 152 times.
✓ Branch 5 taken 152 times.
✓ Branch 6 taken 152 times.
✓ Branch 7 taken 152 times.
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
3/4
✓ Branch 0 taken 3575 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3573 times.
3575 if (offset > size_ || count > size_ - offset) {
205
1/1
✓ Branch 1 taken 2 times.
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
1/1
✓ Branch 1 taken 251 times.
252 Read(0, header.data(), 8);
212
5/6
✓ Branch 1 taken 241 times.
✓ Branch 2 taken 10 times.
✓ Branch 4 taken 241 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 241 times.
✓ Branch 7 taken 10 times.
251 if (header[0] == 'I' && header[1] == 'I') {
213 241 bigEndian_ = false;
214
5/6
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✓ Branch 7 taken 1 time.
10 } else if (header[0] == 'M' && header[1] == 'M') {
215 9 bigEndian_ = true;
216 } else {
217
1/1
✓ Branch 1 taken 1 time.
2 Fail("not a TIFF file");
218 }
219
1/1
✓ Branch 1 taken 250 times.
250 const std::uint16_t version = U16(header.data() + 2);
220
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 10 times.
250 if (version == 42) {
221
1/1
✓ Branch 1 taken 240 times.
240 firstIfd_ = U32(header.data() + 4);
222
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
10 } else if (version == 43) {
223 9 bigTiff_ = true;
224
1/1
✓ Branch 1 taken 9 times.
9 Read(0, header.data(), 16);
225
2/3
✓ Branch 1 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
9 if (U16(header.data() + 4) != 8) {
226
0/1
✗ Branch 1 not taken.
✗ Fail("unsupported BigTIFF offset size");
227 }
228
1/1
✓ Branch 1 taken 9 times.
9 firstIfd_ = U64(header.data() + 8);
229 } else {
230
1/1
✓ Branch 2 taken 1 time.
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
1/1
✓ Branch 1 taken 5737 times.
5737 const auto found = entries.find(tag);
249
2/2
✓ Branch 2 taken 1029 times.
✓ Branch 3 taken 4708 times.
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
1/1
✓ Branch 1 taken 2359 times.
2359 const Entry* entry = Find(tag);
255
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2339 times.
2359 if (entry == nullptr) {
256 20 return fallback;
257 }
258
1/1
✓ Branch 1 taken 2339 times.
2339 const std::vector<std::uint64_t> values = reader.Integers(*entry);
259
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2339 times.
2339 if (values.empty()) {
260
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ reader.Fail("tag " + std::to_string(tag) + " has no value");
261 }
262
2/2
✓ Branch 5 taken 2419 times.
✓ Branch 6 taken 2339 times.
4758 for (const std::uint64_t value : values) {
263
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2419 times.
2419 if (value != values.front()) {
264
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ 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
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 840 times.
841 if (Find(tag) == nullptr) {
273
1/1
✓ Branch 2 taken 1 time.
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
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 535 times.
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
4/6
✓ Branch 0 taken 247 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 247 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 247 times.
✗ Branch 5 not taken.
248 if (info.width == 0 || info.height == 0 || info.width > (1u << 30) ||
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 247 times.
247 info.height > (1u << 30)) {
301
1/1
✓ Branch 1 taken 1 time.
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
6/6
✓ Branch 0 taken 242 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 209 times.
✓ Branch 3 taken 33 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 200 times.
247 if (info.bitsPerSample != 8 && info.bitsPerSample != 16 && info.bitsPerSample != 32 &&
308
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
9 info.bitsPerSample != 64) {
309
1/1
✓ Branch 2 taken 1 time.
3 ifd.reader.Fail(std::to_string(info.bitsPerSample) + "-bit samples are not supported");
310 }
311
4/4
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 201 times.
✓ Branch 4 taken 1 time.
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
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 194 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6 times.
201 if (info.bitsPerSample != 32 && info.bitsPerSample != 64) {
321
1/1
✓ Branch 2 taken 1 time.
3 ifd.reader.Fail(std::to_string(info.bitsPerSample) + "-bit floats are not supported");
322 }
323 200 break;
324 1 default:
325
1/1
✓ Branch 1 taken 1 time.
3 ifd.reader.Fail("unsupported sample format (complex or undefined)");
326 }
327 244 }
328
329 244 void ParseCompression(const Ifd& ifd, GeoTiffInfo& info) {
330
1/1
✓ Branch 1 taken 244 times.
244 const std::uint64_t code = ifd.Single(kCompression, kCompressionNone);
331
1/1
✓ Branch 1 taken 244 times.
244 const std::optional<TiffCompression> compression = CompressionFromCode(code);
332
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 237 times.
244 if (!compression) {
333
2/2
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
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
3/4
✓ Branch 0 taken 237 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 236 times.
237 if (info.predictor < 1 || info.predictor > 3) {
342
1/1
✓ Branch 2 taken 1 time.
3 ifd.reader.Fail("unknown predictor " + std::to_string(info.predictor));
343 }
344
4/4
✓ Branch 0 taken 170 times.
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 169 times.
236 if (info.predictor == 3 && info.sampleFormat != TiffSampleFormat::Float) {
345
1/1
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 172 times.
✓ Branch 1 taken 63 times.
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
1/1
✓ Branch 1 taken 63 times.
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
4/4
✓ Branch 0 taken 234 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 233 times.
235 if (offsets == nullptr || counts == nullptr) {
367
5/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 2 times.
✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
10 ifd.reader.Fail(std::string("missing ") + (info.tiled ? "tile" : "strip") +
368 " offsets or byte counts");
369 }
370
1/1
✓ Branch 1 taken 233 times.
233 layout.offsets = ifd.reader.Integers(*offsets);
371
1/1
✓ Branch 1 taken 233 times.
233 layout.byteCounts = ifd.reader.Integers(*counts);
372
3/4
✓ Branch 0 taken 232 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 232 times.
233 if (layout.chunkWidth == 0 || layout.chunkHeight == 0) {
373
1/1
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 221 times.
232 const std::size_t chunks = layout.across * layout.down * (layout.planar ? info.bands : 1);
378
5/6
✓ Branch 1 taken 231 times.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 231 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 231 times.
232 if (layout.offsets.size() < chunks || layout.byteCounts.size() < chunks) {
379
1/1
✓ Branch 1 taken 1 time.
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
3/3
✓ Branch 1 taken 231 times.
✓ Branch 3 taken 198 times.
✓ Branch 4 taken 33 times.
231 if (const Entry* directory = ifd.Find(kGeoKeyDirectory)) {
386
3/3
✓ Branch 1 taken 198 times.
✓ Branch 7 taken 8644 times.
✓ Branch 8 taken 198 times.
8842 for (const std::uint64_t value : ifd.reader.Integers(*directory)) {
387
1/1
✓ Branch 1 taken 8644 times.
8644 keys.directory.push_back(static_cast<std::uint16_t>(value));
388 198 }
389 }
390
3/3
✓ Branch 1 taken 231 times.
✓ Branch 3 taken 91 times.
✓ Branch 4 taken 140 times.
231 if (const Entry* doubles = ifd.Find(kGeoDoubleParams)) {
391
1/1
✓ Branch 1 taken 91 times.
91 keys.doubleParams = ifd.reader.Doubles(*doubles);
392 }
393
3/3
✓ Branch 1 taken 231 times.
✓ Branch 3 taken 87 times.
✓ Branch 4 taken 144 times.
231 if (const Entry* ascii = ifd.Find(kGeoAsciiParams)) {
394
1/1
✓ Branch 1 taken 87 times.
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
3/3
✓ Branch 1 taken 231 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 223 times.
231 if (const Entry* matrix = ifd.Find(kModelTransformation)) {
403
1/1
✓ Branch 1 taken 8 times.
8 const std::vector<double> m = ifd.reader.Doubles(*matrix);
404
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (m.size() < 16) {
405
0/1
✗ Branch 1 not taken.
✗ 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
1/1
✓ Branch 1 taken 223 times.
223 const Entry* scale = ifd.Find(kModelPixelScale);
411
1/1
✓ Branch 1 taken 223 times.
223 const Entry* tiepoint = ifd.Find(kModelTiepoint);
412
3/4
✓ Branch 0 taken 220 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 220 times.
223 if (scale == nullptr || tiepoint == nullptr) {
413 3 return {};
414 }
415
1/1
✓ Branch 1 taken 220 times.
220 const std::vector<double> s = ifd.reader.Doubles(*scale);
416
1/1
✓ Branch 1 taken 220 times.
220 const std::vector<double> t = ifd.reader.Doubles(*tiepoint);
417
3/6
✓ Branch 1 taken 220 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 220 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 220 times.
220 if (s.size() < 2 || t.size() < 6) {
418
0/1
✗ Branch 1 not taken.
✗ ifd.reader.Fail("ModelPixelScale needs 2 and ModelTiepoint 6 values");
419 }
420
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 220 times.
220 if (t.size() > 6) {
421
0/1
✗ Branch 1 not taken.
✗ 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
1/1
✓ Branch 1 taken 231 times.
231 const Entry* noData = ifd.Find(kGdalNoData);
429
2/2
✓ Branch 0 taken 186 times.
✓ Branch 1 taken 45 times.
231 if (noData == nullptr) {
430 186 return std::nullopt;
431 }
432
1/1
✓ Branch 1 taken 45 times.
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
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 return end != text.c_str() ? std::optional<double>(value) : std::nullopt;
436 45 }
437
438 249 Layout ReadLayout(TiffReader& reader) {
439
1/1
✓ Branch 1 taken 249 times.
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
1/1
✓ Branch 1 taken 247 times.
249 ImageSize(ifd, info);
445
1/1
✓ Branch 1 taken 247 times.
247 info.bands = static_cast<std::size_t>(ifd.Single(kSamplesPerPixel, 1));
446
1/1
✓ Branch 1 taken 244 times.
247 ParseSampleFormat(ifd, info);
447
1/1
✓ Branch 1 taken 237 times.
244 ParseCompression(ifd, info);
448
1/1
✓ Branch 1 taken 235 times.
237 ParsePredictor(ifd, info);
449
1/1
✓ Branch 1 taken 235 times.
235 layout.planar = ifd.Single(kPlanarConfiguration, 1) == 2;
450
1/1
✓ Branch 1 taken 235 times.
235 info.tiled = ifd.Find(kTileWidth) != nullptr;
451
1/1
✓ Branch 1 taken 231 times.
235 ReadChunks(ifd, layout);
452
1/1
✓ Branch 1 taken 231 times.
231 info.geoKeys = ReadGeoKeys(ifd);
453
1/1
✓ Branch 1 taken 231 times.
231 info.transform = TransformFromTags(ifd, info.georeferenced);
454
7/7
✓ Branch 0 taken 228 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 228 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 223 times.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 226 times.
231 if (info.georeferenced && info.geoKeys.PixelIsPoint()) {
455
1/1
✓ Branch 1 taken 5 times.
5 info.transform = ShiftHalfPixel(info.transform, -1.0);
456 }
457
1/1
✓ Branch 1 taken 231 times.
231 const Entry* metadata = ifd.Find(kGdalMetadata);
458
1/1
✓ Branch 1 taken 231 times.
462 info.bandScaling = ParseGdalBandScaling(
459
3/3
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 211 times.
✓ Branch 3 taken 20 times.
693 metadata != nullptr ? TiffReader::Ascii(*metadata) : std::string(), info.bands);
460
1/1
✓ Branch 1 taken 231 times.
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
4/8
void terraingen::(anonymous namespace)::AccumulateRow<unsigned char>(unsigned char*, unsigned long, unsigned long):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void terraingen::(anonymous namespace)::AccumulateRow<unsigned int>(unsigned char*, unsigned long, unsigned long):
✓ Branch 0 taken 760 times.
✓ Branch 1 taken 38 times.
void terraingen::(anonymous namespace)::AccumulateRow<unsigned long>(unsigned char*, unsigned long, unsigned long):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void terraingen::(anonymous namespace)::AccumulateRow<unsigned short>(unsigned char*, unsigned long, unsigned long):
✓ Branch 0 taken 2280 times.
✓ Branch 1 taken 76 times.
3154 for (std::size_t i = stride; i < rowSamples; ++i) {
468 3040 T current = 0;
469 3040 T previous = 0;
470 3040 std::memcpy(&current, 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), &current, 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
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
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
4/4
✓ Branch 0 taken 187 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 51 times.
✓ Branch 3 taken 136 times.
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
2/2
✓ Branch 0 taken 13816 times.
✓ Branch 1 taken 142 times.
13958 for (std::size_t row = 0; row < rows; ++row) {
504 13816 std::uint8_t* p = bytes.data() + row * rowBytes;
505
2/2
✓ Branch 0 taken 114 times.
✓ Branch 1 taken 13702 times.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (sampleBytes == 1) {
516 ✗ return;
517 }
518
2/2
✓ Branch 1 taken 1822 times.
✓ Branch 2 taken 6 times.
1828 for (std::size_t i = 0; i + sampleBytes <= bytes.size(); i += sampleBytes) {
519
1/1
✓ Branch 4 taken 1822 times.
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
3/3
✓ Branch 0 taken 171631 times.
✓ Branch 1 taken 4389 times.
✓ Branch 2 taken 2793 times.
178813 switch (info.sampleFormat) {
532 171631 case TiffSampleFormat::Float:
533
2/2
✓ Branch 0 taken 170434 times.
✓ Branch 1 taken 1197 times.
171631 return info.bitsPerSample == 32 ? ReadSample<float>(p) : ReadSample<double>(p);
534 4389 case TiffSampleFormat::SignedInteger:
535
4/4
✓ Branch 0 taken 399 times.
✓ Branch 1 taken 3192 times.
✓ Branch 2 taken 399 times.
✓ Branch 3 taken 399 times.
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
3/4
✓ Branch 0 taken 399 times.
✓ Branch 1 taken 1995 times.
✓ Branch 2 taken 399 times.
✗ Branch 3 not taken.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 194 times.
194 if (stored == 0) {
564
0/1
✗ Branch 1 not taken.
✗ return std::vector<std::uint8_t>(expected, 0);
565 }
566
1/1
✓ Branch 2 taken 193 times.
194 std::vector<std::uint8_t> raw = reader.ReadBytes(layout.offsets[chunk], stored);
567
4/4
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 35 times.
193 switch (layout.info.compression) {
568 117 case TiffCompression::Lzw:
569
1/1
✓ Branch 2 taken 117 times.
117 return DecodeLzw(raw, expected);
570 1 case TiffCompression::PackBits:
571
1/1
✓ Branch 2 taken 1 time.
1 return DecodePackBits(raw, expected);
572 40 case TiffCompression::Deflate:
573
1/1
✓ Branch 2 taken 40 times.
40 return DecodeDeflate(raw, expected);
574 35 default:
575
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
35 if (raw.size() < expected) {
576
0/1
✗ Branch 1 not taken.
✗ reader.Fail("truncated uncompressed chunk");
577 }
578
1/1
✓ Branch 1 taken 35 times.
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
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 186 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 1 time.
193 if (info.bigEndian && info.predictor != 3) {
592
1/1
✓ Branch 1 taken 6 times.
6 SwapSamples(bytes, sampleBytes);
593 }
594
1/1
✓ Branch 2 taken 193 times.
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
2/2
✓ Branch 0 taken 3774 times.
✓ Branch 1 taken 193 times.
3967 for (std::size_t y = 0; y < height; ++y) {
611 3774 const std::uint8_t* row = bytes.data() + y * rowBytes;
612
2/2
✓ Branch 0 taken 178813 times.
✓ Branch 1 taken 3774 times.
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
1/1
✓ Branch 1 taken 139 times.
146 TiffReader reader(path);
623
1/1
✓ Branch 1 taken 139 times.
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
1/1
✓ Branch 1 taken 110 times.
115 TiffReader reader(path);
629
1/1
✓ Branch 1 taken 92 times.
110 const Layout layout = ReadLayout(reader);
630 92 const GeoTiffInfo& info = layout.info;
631
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 90 times.
92 if (band >= info.bands) {
632
5/5
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
✓ Branch 10 taken 2 times.
✓ Branch 13 taken 2 times.
12 reader.Fail("band " + std::to_string(band) + " requested of " + std::to_string(info.bands));
633 }
634
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 84 times.
90 const std::size_t planeOffset = layout.planar ? band * layout.across * layout.down : 0;
635
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 84 times.
90 const std::size_t sampleIndex = layout.planar ? 0 : band;
636
2/4
✓ Branch 1 taken 90 times.
✓ Branch 4 taken 90 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
90 GeoTiff result{Raster<float>(info.width, info.height), info.geoKeys};
637 90 result.raster.SetTransform(info.transform);
638
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 68 times.
90 if (info.noData) {
639 22 result.raster.SetNoData(static_cast<float>(*info.noData));
640 }
641
2/2
✓ Branch 0 taken 114 times.
✓ Branch 1 taken 89 times.
203 for (std::size_t cy = 0; cy < layout.down; ++cy) {
642 const std::size_t rows =
643
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 28 times.
142 info.tiled ? layout.chunkHeight
644 28 : std::min(layout.chunkHeight, info.height - cy * layout.chunkHeight);
645
2/2
✓ Branch 0 taken 194 times.
✓ Branch 1 taken 113 times.
307 for (std::size_t cx = 0; cx < layout.across; ++cx) {
646 194 const std::size_t chunk = planeOffset + cy * layout.across + cx;
647
1/1
✓ Branch 1 taken 193 times.
194 CopyChunk(DecodeChunk(reader, layout, chunk, rows), layout, cx, cy, rows, sampleIndex,
648
1/1
✓ Branch 1 taken 193 times.
194 result.raster);
649 }
650 }
651
2/2
✓ Branch 1 taken 89 times.
✓ Branch 5 taken 89 times.
89 LogInfo("raster", "Read ", info.width, "x", info.height, " GeoTIFF ", path.string(), " in ",
652
2/2
✓ Branch 1 taken 89 times.
✓ Branch 4 taken 89 times.
178 FormatDuration(stopwatch.Milliseconds()));
653 178 return result;
654 114 }
655
656 } // namespace terraingen
657