Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
encode.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
7#include <algorithm>
8
9namespace {
10
11using namespace Haio;
12
13/** mapped, truecolor or grey: the three the format has, and none of them compressed */
14constexpr uint8_t imageTypeOf(Color colour) {
15 if (colour == Color::PALETTE) return 1;
16 if (colour == Color::GRAY8) return 3;
17 return 2;
18}
19
20/**
21 * how many of the sixteen bits the file claims are an alpha.
22 *
23 * it is the only thing that tells a reader apart the two sixteen bit colours and the
24 * only thing that tells it a thirty two bit picture is not padded, so it is written
25 * from the colour rather than left at zero the way a lot of writers leave it.
26 */
27constexpr uint8_t attributeBitsOf(Color colour) {
28 if (colour == Color::BGRA8888) return 8;
29 if (colour == Color::RGBA5551) return 1;
30 return 0;
31}
32
33void appendU16LE(std::vector<uint8_t>& out, uint16_t value) {
34 out.push_back(static_cast<uint8_t>(value));
35 out.push_back(static_cast<uint8_t>(value >> 8));
36}
37
38/** haio's rgba5551 rotated back one bit, which is the format's own A1R5G5B5 */
39void appendRotated(std::vector<uint8_t>& out, Bytes pixels) {
40 for (size_t at = 0; at + 1 < pixels.size(); at += 2) {
41 const auto packed = static_cast<uint16_t>(pixels[at] | (pixels[at + 1] << 8));
42 appendU16LE(out, static_cast<uint16_t>((packed >> 1) | (packed << 15)));
43 }
44}
45
46/**
47 * one body for every colour the container can hold.
48 *
49 * everything it writes is uncompressed and starts at the top left, which is the one
50 * corner every reader agrees on. the picture is already in the layout the file wants
51 * for all but rgba5551, so this is a header, a colour map when there is one, and a
52 * copy.
53 *
54 * @todo the runs are not written. tga's run length encoding is trivial and would pay
55 * for itself on flat art, which is most of what gets saved as a tga; a plain file is
56 * simply the version that every reader takes.
57 */
58template <Color P>
59Result<Blob> encodeTga(Image<P> img) {
60 const Size size{img.width, img.height};
61 HAIO_TRY(expected, sizeOf(P, size));
62 if (img.data.size() != expected) HAIO_FAIL(InvalidInput, "tga payload does not match its size");
63 if (img.width > 0xffff || img.height > 0xffff) {
64 HAIO_FAIL(InvalidInput, "a tga says its size in two bytes, and this picture is wider than that reaches");
65 }
66
67 std::vector<uint32_t> entries;
68 if constexpr (P == Color::PALETTE) {
69 entries = img.entries;
70 if (entries.empty()) HAIO_FAIL(InvalidInput, "this picture is indices with no palette to write beside them");
71 if (entries.size() > 256) HAIO_FAIL(InvalidInput, "a tga colour map here is reached by one byte, so it holds at most 256 colours");
72 if (const auto highest = std::ranges::max(img.data); highest >= entries.size()) {
73 HAIO_FAIL(InvalidInput, "the picture points at colour " + std::to_string(highest)
74 + " and the palette has " + std::to_string(entries.size()));
75 }
76 }
77
78 std::vector<uint8_t> out;
79 out.reserve(Codecs::Tga::headerBytes + entries.size() * 3 + img.data.size() + Codecs::Tga::footerBytes);
80
81 out.push_back(0); // no id field
82 out.push_back(entries.empty() ? 0 : 1);
83 out.push_back(imageTypeOf(P));
84 appendU16LE(out, 0); // the map starts at its first colour
85 appendU16LE(out, static_cast<uint16_t>(entries.size()));
86 out.push_back(entries.empty() ? 0 : 24);
87 appendU16LE(out, 0); // where it sits on a screen is
88 appendU16LE(out, 0); // not part of the picture
89 appendU16LE(out, static_cast<uint16_t>(img.width));
90 appendU16LE(out, static_cast<uint16_t>(img.height));
91 out.push_back(static_cast<uint8_t>(strideOf(P) * 8));
92 // bit five is the origin: haio writes top left, so nothing downstream has to flip
93 out.push_back(static_cast<uint8_t>(0x20 | attributeBitsOf(P)));
94
95 /**
96 * the map goes out as three bytes an entry.
97 *
98 * a palette here is 0x00RRGGBB and its top byte is not read anywhere: the
99 * conversion back to full colour forces every pixel opaque. writing a four byte
100 * map would put an alpha in the file that haio never had, and the first tool to
101 * read it back would find a palette of invisible colours.
102 */
103 for (const auto colour : entries) {
104 out.push_back(static_cast<uint8_t>(colour));
105 out.push_back(static_cast<uint8_t>(colour >> 8));
106 out.push_back(static_cast<uint8_t>(colour >> 16));
107 }
108
109 if constexpr (P == Color::RGBA5551) {
110 appendRotated(out, img.data);
111 } else {
112 out.insert(out.end(), img.data.begin(), img.data.end());
113 }
114
115 // the footer is what makes a tga recognisable at all, so haio's own files carry
116 // one even though nothing else in them needs the two offsets it holds
117 for (int i = 0; i < 8; i++) out.push_back(0);
118 out.insert(out.end(), Codecs::Tga::footerSignature.begin(), Codecs::Tga::footerSignature.end());
119 out.push_back(0);
120
121 return Blob{Format::TGA, P, "image/x-tga", {}, std::move(out)};
122}
123
124}
125
126namespace Haio::Codecs {
127
128/**
129 * @addtogroup encode
130 * @{
131 */
132/** everything a picture arrives with, which is why this is what a bare tga means */
133template <>
135 return encodeTga<Color::BGRA8888>(std::move(img));
136}
137/** @} */
138
139/**
140 * @addtogroup encode
141 * @{
142 */
143template <>
144Result<Blob> Encode<Format::TGA, Color::BGR888>(Image<Color::BGR888> img) {
145 return encodeTga<Color::BGR888>(std::move(img));
146}
147/** @} */
148
149/**
150 * @addtogroup encode
151 * @{
152 */
153template <>
154Result<Blob> Encode<Format::TGA, Color::RGBA5551>(Image<Color::RGBA5551> img) {
155 return encodeTga<Color::RGBA5551>(std::move(img));
156}
157/** @} */
158
159/**
160 * @addtogroup encode
161 * @{
162 */
163template <>
164Result<Blob> Encode<Format::TGA, Color::RGB555>(Image<Color::RGB555> img) {
165 return encodeTga<Color::RGB555>(std::move(img));
166}
167/** @} */
168
169/**
170 * @addtogroup encode
171 * @{
172 */
173/**
174 * the indices as they are, with their own colours beside them.
175 *
176 * this is the one container haio has that stores a palette rather than expanding it,
177 * so "-filter bayer -palete cga out.tga" comes out as one byte a pixel instead of
178 * four holding sixteen distinct values.
179 */
180template <>
181Result<Blob> Encode<Format::TGA, Color::PALETTE>(Image<Color::PALETTE> img) {
182 return encodeTga<Color::PALETTE>(std::move(img));
183}
184/** @} */
185
186/**
187 * @addtogroup encode
188 * @{
189 */
190/**
191 * one byte a pixel and no colour at all. nothing converts full colour down to grey
192 * yet, so this is reached by a typed pipe from something already grey, such as a
193 * netpbm P5.
194 */
195template <>
196Result<Blob> Encode<Format::TGA, Color::GRAY8>(Image<Color::GRAY8> img) {
197 return encodeTga<Color::GRAY8>(std::move(img));
198}
199/** @} */
200
201}
Result< Blob > Encode< Format::TGA, Color::RGB555 >(Image< Color::RGB555 > img)
Definition encode.cpp:164
Result< Blob > Encode< Format::TGA, Color::BGR888 >(Image< Color::BGR888 > img)
Definition encode.cpp:144
Result< Blob > Encode< Format::TGA, Color::RGBA5551 >(Image< Color::RGBA5551 > img)
Definition encode.cpp:154
Result< Blob > Encode< Format::TGA, Color::GRAY8 >(Image< Color::GRAY8 > img)
Definition encode.cpp:196
Result< Blob > Encode< Format::TGA, Color::PALETTE >(Image< Color::PALETTE > img)
Definition encode.cpp:181
Result< Blob > Encode< Format::TGA, Color::BGRA8888 >(Image< Color::BGRA8888 > img)
Definition encode.cpp:134
#define HAIO_TRY(name, expr)
#define HAIO_FAIL(why, message)
constexpr size_t footerBytes
Definition header.hpp:35
constexpr std::string_view footerSignature
Definition header.hpp:34
constexpr size_t headerBytes
Definition header.hpp:17
Definition haio.hpp:11
std::expected< T, Error > Result
Result< size_t > sizeOf(Color color, Size size)
Definition sizes.cpp:6
constexpr size_t strideOf(Color color)
std::span< const uint8_t > Bytes
std::vector< uint8_t > data