Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
decode.cpp
Go to the documentation of this file.
1#include "header.hpp"
2
3#include <haio_codec.hpp>
4#include <haio_codecs.hpp>
5#include <haio_convert.hpp>
6#include <haio_util.hpp>
7
8#include <algorithm>
9
10namespace {
11
12using namespace Haio;
14
15/** a five bit channel widens by repeating its high bits, so 31 reaches 255 exactly */
16constexpr uint8_t expand5(unsigned value) { return static_cast<uint8_t>((value << 3) | (value >> 2)); }
17
18/**
19 * the pixels in file order, with any runs written out.
20 *
21 * a run length packet is a count and then either one unit to repeat or that many to
22 * copy, and the spec says a packet stops at the end of a scanline while half the
23 * encoders in the world carry on across it. reading the stream as one long sequence
24 * is what both kinds of file have in common.
25 */
26Result<std::vector<uint8_t>> unpack(Bytes data, const Header& header) {
27 const auto unit = header.unit();
28 std::vector<uint8_t> out(header.pixels() * unit);
29
30 if (!header.rle()) {
31 // readHeader already measured this one, so the copy cannot run off the end
32 std::copy_n(data.begin() + static_cast<ptrdiff_t>(header.dataOffset), out.size(), out.begin());
33 return out;
34 }
35
36 size_t at = header.dataOffset;
37 size_t to = 0;
38 while (to < out.size()) {
39 if (at >= data.size()) {
40 return std::unexpected(Error{ErrorCode::InvalidInput, "this tga ends before its picture does"});
41 }
42
43 const uint8_t packet = data[at++];
44 const size_t run = (packet & 0x7fu) + 1u;
45 const size_t bytes = run * unit;
46 if (to + bytes > out.size()) {
47 return std::unexpected(Error{ErrorCode::InvalidInput, "a run in this tga carries past the end of the picture"});
48 }
49
50 if ((packet & 0x80u) != 0) {
51 if (!Util::hasBytes(data, at, unit)) {
52 return std::unexpected(Error{ErrorCode::InvalidInput, "this tga ends in the middle of a run"});
53 }
54 for (size_t k = 0; k < run; k++, to += unit) {
55 std::copy_n(data.begin() + static_cast<ptrdiff_t>(at), unit, out.begin() + static_cast<ptrdiff_t>(to));
56 }
57 at += unit;
58 } else {
59 if (!Util::hasBytes(data, at, bytes)) {
60 return std::unexpected(Error{ErrorCode::InvalidInput, "this tga ends in the middle of a packet"});
61 }
62 std::copy_n(data.begin() + static_cast<ptrdiff_t>(at), bytes, out.begin() + static_cast<ptrdiff_t>(to));
63 at += bytes;
64 to += bytes;
65 }
66 }
67 return out;
68}
69
70/**
71 * the origin bits, honoured rather than passed on.
72 *
73 * a tga names which corner it started from, and the default is the bottom left
74 * because the format was written for a frame buffer that was addressed that way.
75 * everything downstream of a decoder here reads top left first, so this is where the
76 * two agree.
77 */
78void orient(std::vector<uint8_t>& pixels, const Header& header) {
79 const auto unit = header.unit();
80 const auto width = static_cast<size_t>(header.width);
81 const auto height = static_cast<size_t>(header.height);
82 const auto row = width * unit;
83
84 if (header.flipX()) {
85 for (size_t y = 0; y < height; y++) {
86 auto* line = pixels.data() + y * row;
87 for (size_t x = 0; x < width / 2; x++) {
88 std::swap_ranges(line + x * unit, line + (x + 1) * unit, line + (width - 1 - x) * unit);
89 }
90 }
91 }
92
93 if (header.flipY()) {
94 for (size_t y = 0; y < height / 2; y++) {
95 std::swap_ranges(pixels.data() + y * row, pixels.data() + (y + 1) * row,
96 pixels.data() + (height - 1 - y) * row);
97 }
98 }
99}
100
101/**
102 * a sixteen bit tga pixel is A1R5G5B5 and haio's rgba5551 is the same five bit
103 * channels with the alpha at the other end, so the whole conversion is a rotate: one
104 * bit left, the alpha falling off the top and landing at the bottom.
105 */
106void rotateToRgba5551(std::vector<uint8_t>& pixels) {
107 for (size_t at = 0; at + 1 < pixels.size(); at += 2) {
108 const auto packed = static_cast<uint16_t>(pixels[at] | (pixels[at + 1] << 8));
109 const auto rotated = static_cast<uint16_t>((packed << 1) | (packed >> 15));
110 pixels[at] = static_cast<uint8_t>(rotated);
111 pixels[at + 1] = static_cast<uint8_t>(rotated >> 8);
112 }
113}
114
115/**
116 * the colour map, as the colours an index reaches rather than as the bytes it was
117 * stored in.
118 *
119 * the first entry field says the map starts part way up, so the entries before it are
120 * filled in as black: an index is an index, and a picture that points below the map
121 * is pointing at something the file never gave it.
122 */
123Result<std::vector<uint32_t>> readMap(Bytes data, const Header& header) {
124 const auto entries = static_cast<size_t>(header.mapFirst) + static_cast<size_t>(header.mapCount);
125 if (entries > 256) {
126 return std::unexpected(Error{ErrorCode::InvalidInput,
127 "this tga names colour " + std::to_string(entries - 1)
128 + " and an index of one byte reaches 255"});
129 }
130
131 const auto width = (static_cast<size_t>(header.mapBits) + 7) / 8;
132 if (!Util::hasBytes(data, header.mapOffset, static_cast<size_t>(header.mapCount) * width)) {
133 return std::unexpected(Error{ErrorCode::InvalidInput, "this tga ends inside its colour map"});
134 }
135
136 std::vector<uint32_t> out(entries, 0xff000000u);
137 for (size_t i = 0; i < header.mapCount; i++) {
138 const auto at = header.mapOffset + i * width;
139 uint32_t colour = 0xff000000u;
140 if (width == 2) {
141 const auto packed = static_cast<unsigned>(data[at] | (data[at + 1] << 8));
142 colour |= static_cast<uint32_t>(expand5((packed >> 10) & 0x1f)) << 16;
143 colour |= static_cast<uint32_t>(expand5((packed >> 5) & 0x1f)) << 8;
144 colour |= expand5(packed & 0x1f);
145 } else {
146 colour |= static_cast<uint32_t>(data[at + 2]) << 16;
147 colour |= static_cast<uint32_t>(data[at + 1]) << 8;
148 colour |= data[at + 0];
149 // the fourth byte is an alpha only where the descriptor says somebody
150 // meant it to be; a map written by a tool that zeroed the field would
151 // otherwise come back as a palette of invisible colours
152 if (width == 4 && header.attributeBits() != 0) {
153 colour = (colour & 0x00ffffffu) | (static_cast<uint32_t>(data[at + 3]) << 24);
154 }
155 }
156 out[static_cast<size_t>(header.mapFirst) + i] = colour;
157 }
158 return out;
159}
160
161/**
162 * one body for every colour the container can hold; the caller names which.
163 *
164 * the colours were chosen so that this is a copy: a tga already stores bgr888,
165 * bgra8888, grey and indices exactly as haio keeps them, so unpacking runs and
166 * turning the picture the right way up is the entire decoder.
167 */
168template <Color P>
169Result<Image<P>> decodeTga(const Blob& blob) {
171
172 const auto colour = Codecs::Tga::colorOf(header);
173 if (!colour || *colour != P) {
174 // only reachable when a command line named the pair itself, since detection
175 // hands every file to the decoder its own header asked for
176 static constexpr const char* kinds[] = {"", "mapped", "truecolor", "grey"};
177 HAIO_FAIL(InvalidInput, std::string("this tga is ") + kinds[header.kind()] + " at "
178 + std::to_string(header.depth) + " bits a pixel, which is not the colour it was asked to be");
179 }
180 if constexpr (P == Color::PALETTE) {
181 if (header.depth != 8) {
182 HAIO_FAIL(UnsupportedFormat, "this tga indexes its colours two bytes at a time, and a palette picture here is one index per pixel");
183 }
184 }
185
186 HAIO_TRY(pixels, unpack(blob.data, header));
187 orient(pixels, header);
188
189 if constexpr (P == Color::RGBA5551) rotateToRgba5551(pixels);
190
191 Image<P> out{header.width, header.height, std::move(pixels)};
192 if constexpr (P == Color::PALETTE) {
193 HAIO_TRY(entries, readMap(blob.data, header));
194 out.entries = std::move(entries);
195 }
196 return out;
197}
198
199}
200
201namespace Haio::Codecs {
202
203/**
204 * @addtogroup decode
205 * @{
206 */
207template <>
209 return decodeTga<Color::BGRA8888>(blob);
210}
211/** @} */
212
213/**
214 * @addtogroup decode
215 * @{
216 */
217template <>
219 return decodeTga<Color::BGR888>(blob);
220}
221/** @} */
222
223/**
224 * @addtogroup decode
225 * @{
226 */
227template <>
229 return decodeTga<Color::RGBA5551>(blob);
230}
231/** @} */
232
233/**
234 * @addtogroup decode
235 * @{
236 */
237template <>
239 return decodeTga<Color::RGB555>(blob);
240}
241/** @} */
242
243/**
244 * @addtogroup decode
245 * @{
246 */
247/** the indices and the colours they point at, which is what a palette image is */
248template <>
250 return decodeTga<Color::PALETTE>(blob);
251}
252/** @} */
253
254/**
255 * @addtogroup decode
256 * @{
257 */
258template <>
260 return decodeTga<Color::GRAY8>(blob);
261}
262/** @} */
263
264}
265
266// touched
267
268// touched twice
Result< Image< Color::RGBA5551 > > Decode< Format::TGA, Color::RGBA5551 >(const Blob &blob)
Definition decode.cpp:228
Result< Image< Color::PALETTE > > Decode< Format::TGA, Color::PALETTE >(const Blob &blob)
Definition decode.cpp:249
Result< Image< Color::GRAY8 > > Decode< Format::TGA, Color::GRAY8 >(const Blob &blob)
Definition decode.cpp:259
Result< Image< Color::BGRA8888 > > Decode< Format::TGA, Color::BGRA8888 >(const Blob &blob)
Definition decode.cpp:208
Result< Image< Color::BGR888 > > Decode< Format::TGA, Color::BGR888 >(const Blob &blob)
Definition decode.cpp:218
Result< Image< Color::RGB555 > > Decode< Format::TGA, Color::RGB555 >(const Blob &blob)
Definition decode.cpp:238
#define HAIO_TRY(name, expr)
#define HAIO_FAIL(why, message)
std::optional< Color > colorOf(const Header &header)
Definition header.hpp:176
Result< Header > readHeader(Bytes data)
Definition header.hpp:92
bool hasBytes(Bytes data, size_t off, size_t len)
Definition bytes.cpp:5
Definition haio.hpp:11
std::vector< uint8_t > data
std::expected< T, Error > Result
std::span< const uint8_t > Bytes