/src/serenity/Userland/Libraries/LibGfx/ImageFormats/TGALoader.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, Tom Needham <06needhamt@gmail.com> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/MemoryStream.h> |
8 | | #include <AK/Span.h> |
9 | | #include <AK/StdLibExtraDetails.h> |
10 | | #include <AK/String.h> |
11 | | #include <AK/Traits.h> |
12 | | #include <LibGfx/ImageFormats/TGALoader.h> |
13 | | |
14 | | namespace Gfx { |
15 | | |
16 | | enum TGADataType : u8 { |
17 | | None = 0, |
18 | | UncompressedColorMapped = 1, |
19 | | UncompressedRGB = 2, |
20 | | UncompressedBlackAndWhite = 3, |
21 | | RunLengthEncodedColorMapped = 9, |
22 | | RunLengthEncodedRGB = 10, |
23 | | CompressedBlackAndWhite = 11, |
24 | | CompressedColorMapped = 32, |
25 | | CompressedColorMappedFourPass = 33 |
26 | | }; |
27 | | |
28 | | struct [[gnu::packed]] TGAHeader { |
29 | | u8 id_length; |
30 | | u8 color_map_type; |
31 | | TGADataType data_type_code; |
32 | | i16 color_map_origin; |
33 | | i16 color_map_length; |
34 | | u8 color_map_depth; |
35 | | i16 x_origin; |
36 | | i16 y_origin; |
37 | | u16 width; |
38 | | u16 height; |
39 | | u8 bits_per_pixel; |
40 | | u8 image_descriptor; |
41 | | }; |
42 | | static_assert(sizeof(TGAHeader) == 18); |
43 | | |
44 | | } |
45 | | |
46 | | class TGAImageIdentifier : public Gfx::Metadata { |
47 | | public: |
48 | | TGAImageIdentifier(String identifier) |
49 | 88 | : m_identifier(move(identifier)) |
50 | 88 | { |
51 | 88 | } |
52 | | |
53 | | protected: |
54 | | virtual void fill_main_tags() const override |
55 | 0 | { |
56 | 0 | if (!m_identifier.is_empty()) |
57 | 0 | m_main_tags.set("Identifier"sv, m_identifier); |
58 | 0 | } |
59 | | |
60 | | String m_identifier; |
61 | | }; |
62 | | |
63 | | template<> |
64 | | struct AK::Traits<Gfx::TGAHeader> : public DefaultTraits<Gfx::TGAHeader> { |
65 | 0 | static constexpr bool is_trivially_serializable() { return true; } |
66 | | }; |
67 | | |
68 | | namespace Gfx { |
69 | | |
70 | | struct TGALoadingContext { |
71 | | TGALoadingContext(ReadonlyBytes bytes, FixedMemoryStream stream) |
72 | 504 | : bytes(bytes) |
73 | 504 | , stream(move(stream)) |
74 | 504 | { |
75 | 504 | } |
76 | | ReadonlyBytes bytes; |
77 | | FixedMemoryStream stream; |
78 | | TGAHeader header {}; |
79 | | RefPtr<Gfx::Bitmap> bitmap; |
80 | | OwnPtr<TGAImageIdentifier> identifier_metadata; |
81 | | }; |
82 | | |
83 | | TGAImageDecoderPlugin::TGAImageDecoderPlugin(NonnullOwnPtr<TGALoadingContext> context) |
84 | 504 | : m_context(move(context)) |
85 | 504 | { |
86 | 504 | } |
87 | | |
88 | 504 | TGAImageDecoderPlugin::~TGAImageDecoderPlugin() = default; |
89 | | |
90 | | IntSize TGAImageDecoderPlugin::size() |
91 | 0 | { |
92 | 0 | return IntSize { m_context->header.width, m_context->header.height }; |
93 | 0 | } |
94 | | |
95 | | static ErrorOr<void> ensure_header_validity(TGAHeader const& header, size_t whole_image_stream_size) |
96 | 499 | { |
97 | 499 | if ((header.bits_per_pixel % 8) != 0 || header.bits_per_pixel < 8 || header.bits_per_pixel > 32) |
98 | 128 | return Error::from_string_literal("Invalid bit depth"); |
99 | 371 | auto bytes_remaining = whole_image_stream_size - sizeof(TGAHeader) - header.id_length; |
100 | 371 | if (header.data_type_code == TGADataType::UncompressedRGB && bytes_remaining < static_cast<u64>(header.width) * header.height * (header.bits_per_pixel / 8)) |
101 | 36 | return Error::from_string_literal("Not enough data to read an image with the expected size"); |
102 | 335 | return {}; |
103 | 371 | } |
104 | | |
105 | | ErrorOr<void> TGAImageDecoderPlugin::decode_tga_header() |
106 | 504 | { |
107 | 504 | m_context->header = TRY(m_context->stream.read_value<TGAHeader>()); |
108 | 499 | if (m_context->header.id_length > 0) { |
109 | 421 | auto identifier = String::from_stream(m_context->stream, m_context->header.id_length); |
110 | 421 | if (!identifier.is_error()) |
111 | 88 | m_context->identifier_metadata = make<TGAImageIdentifier>(identifier.release_value()); |
112 | 333 | else |
113 | 333 | dbgln("FIXME: Handle non-utf8 identifiers in TGALoader"); |
114 | 421 | } |
115 | 499 | TRY(ensure_header_validity(m_context->header, m_context->bytes.size())); |
116 | 335 | return {}; |
117 | 499 | } |
118 | | |
119 | | bool TGAImageDecoderPlugin::validate_before_create(ReadonlyBytes data) |
120 | 0 | { |
121 | 0 | FixedMemoryStream stream { data }; |
122 | 0 | auto header_or_err = stream.read_value<Gfx::TGAHeader>(); |
123 | 0 | if (header_or_err.is_error()) |
124 | 0 | return false; |
125 | 0 | return !ensure_header_validity(header_or_err.release_value(), data.size()).is_error(); |
126 | 0 | } |
127 | | |
128 | | ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> TGAImageDecoderPlugin::create(ReadonlyBytes data) |
129 | 504 | { |
130 | 504 | FixedMemoryStream stream { data }; |
131 | 504 | auto context = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TGALoadingContext(data, move(stream)))); |
132 | 504 | auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TGAImageDecoderPlugin(move(context)))); |
133 | 504 | TRY(plugin->decode_tga_header()); |
134 | 335 | return plugin; |
135 | 504 | } |
136 | | |
137 | | static ErrorOr<ARGB32> read_pixel_from_stream(Stream& stream, size_t bytes_size) |
138 | 3.90M | { |
139 | | // NOTE: We support 8-bit, 24-bit and 32-bit color pixels |
140 | 3.90M | VERIFY(bytes_size == 1 || bytes_size == 3 || bytes_size == 4); |
141 | | |
142 | 3.90M | if (bytes_size == 1) { |
143 | 3.17M | auto raw = TRY(stream.read_value<u8>()); |
144 | 3.17M | return Color(raw, raw, raw).value(); |
145 | 3.17M | } |
146 | 729k | if (bytes_size == 3) { |
147 | 729k | Array<u8, 3> raw; |
148 | 729k | TRY(stream.read_until_filled(raw.span())); |
149 | 729k | return Color(raw[2], raw[1], raw[0]).value(); |
150 | 729k | } |
151 | 218 | return stream.read_value<ARGB32>(); |
152 | 729k | } |
153 | | |
154 | | struct TGAPixelPacketHeader { |
155 | | bool raw { false }; |
156 | | u8 pixels_count { 0 }; |
157 | | }; |
158 | | |
159 | | static ErrorOr<TGAPixelPacketHeader> read_pixel_packet_header(Stream& stream) |
160 | 1.82M | { |
161 | 1.82M | auto const pixel_packet_header = TRY(stream.read_value<u8>()); |
162 | 1.82M | bool pixels_raw_in_packet = !(pixel_packet_header & 0x80); |
163 | 1.82M | u8 pixels_count_in_packet = (pixel_packet_header & 0x7f); |
164 | | // NOTE: Run-length-encoded/Raw pixel packets cannot encode zero pixels, |
165 | | // so value 0 stands for 1 pixel, 1 stands for 2, etc... |
166 | 1.82M | pixels_count_in_packet++; |
167 | 1.82M | VERIFY(pixels_count_in_packet > 0); |
168 | 1.82M | return TGAPixelPacketHeader { pixels_raw_in_packet, pixels_count_in_packet }; |
169 | 1.82M | } |
170 | | |
171 | | Optional<Metadata const&> TGAImageDecoderPlugin::metadata() |
172 | 0 | { |
173 | 0 | if (m_context->identifier_metadata) |
174 | 0 | return *m_context->identifier_metadata; |
175 | 0 | return OptionalNone {}; |
176 | 0 | } |
177 | | |
178 | | ErrorOr<ImageFrameDescriptor> TGAImageDecoderPlugin::frame(size_t index, Optional<IntSize>) |
179 | 335 | { |
180 | 335 | auto bits_per_pixel = m_context->header.bits_per_pixel; |
181 | 335 | auto color_map = m_context->header.color_map_type; |
182 | 335 | auto data_type = m_context->header.data_type_code; |
183 | 335 | auto width = m_context->header.width; |
184 | 335 | auto height = m_context->header.height; |
185 | 335 | auto image_descriptior = m_context->header.image_descriptor; |
186 | | |
187 | 335 | if (index != 0) |
188 | 0 | return Error::from_string_literal("TGAImageDecoderPlugin: frame index must be 0"); |
189 | | |
190 | 335 | if (color_map > 1) |
191 | 40 | return Error::from_string_literal("TGAImageDecoderPlugin: Invalid color map type"); |
192 | | |
193 | 295 | if (m_context->bitmap) |
194 | 0 | return ImageFrameDescriptor { m_context->bitmap, 0 }; |
195 | | |
196 | 295 | RefPtr<Gfx::Bitmap> bitmap; |
197 | 295 | switch (bits_per_pixel) { |
198 | 200 | case 8: |
199 | 243 | case 24: |
200 | 243 | bitmap = TRY(Bitmap::create(BitmapFormat::BGRx8888, { m_context->header.width, m_context->header.height })); |
201 | 227 | break; |
202 | | |
203 | 51 | case 32: |
204 | 51 | bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, { m_context->header.width, m_context->header.height })); |
205 | 40 | break; |
206 | | |
207 | 1 | default: |
208 | | // FIXME: Implement other TGA bit depths |
209 | 1 | return Error::from_string_literal("TGAImageDecoderPlugin: Can only handle 8, 24 and 32 bits per pixel"); |
210 | 295 | } |
211 | | |
212 | 267 | auto is_top_to_bottom = (image_descriptior & 1 << 5) == 0; |
213 | 267 | auto is_left_to_right = (image_descriptior & 1 << 4) == 0; |
214 | | |
215 | 267 | VERIFY((bits_per_pixel % 8) == 0); |
216 | 267 | auto bytes_per_pixel = bits_per_pixel / 8; |
217 | | |
218 | 267 | switch (data_type) { |
219 | 41 | case TGADataType::UncompressedBlackAndWhite: |
220 | 100 | case TGADataType::UncompressedRGB: { |
221 | 891 | for (int row = 0; row < height; ++row) { |
222 | 8.10k | for (int col = 0; col < width; ++col) { |
223 | 7.31k | auto actual_row = row; |
224 | 7.31k | if (is_top_to_bottom) |
225 | 972 | actual_row = height - 1 - row; |
226 | 7.31k | auto actual_col = col; |
227 | 7.31k | if (!is_left_to_right) |
228 | 6.30k | actual_col = width - 1 - col; |
229 | 7.31k | bitmap->scanline(actual_row)[actual_col] = TRY(read_pixel_from_stream(m_context->stream, bytes_per_pixel)); |
230 | 7.22k | } |
231 | 888 | } |
232 | 100 | break; |
233 | 100 | } |
234 | | |
235 | 148 | case TGADataType::RunLengthEncodedRGB: { |
236 | 148 | size_t pixel_index = 0; |
237 | 148 | size_t pixel_count = height * width; |
238 | 1.82M | while (pixel_index < pixel_count) { |
239 | 1.82M | auto pixel_packet_header = TRY(read_pixel_packet_header(m_context->stream)); |
240 | 1.82M | VERIFY(pixel_packet_header.pixels_count > 0); |
241 | | |
242 | 1.82M | auto pixel = TRY(read_pixel_from_stream(m_context->stream, bytes_per_pixel)); |
243 | 1.82M | auto max_pixel_index = min(pixel_index + pixel_packet_header.pixels_count, pixel_count); |
244 | 206M | for (size_t current_pixel_index = pixel_index; current_pixel_index < max_pixel_index; ++current_pixel_index) { |
245 | 204M | int row = current_pixel_index / width; |
246 | 204M | int col = current_pixel_index % width; |
247 | 204M | auto actual_row = row; |
248 | 204M | if (is_top_to_bottom) |
249 | 12.6M | actual_row = height - 1 - row; |
250 | 204M | auto actual_col = col; |
251 | 204M | if (!is_left_to_right) |
252 | 192M | actual_col = width - 1 - col; |
253 | 204M | bitmap->scanline(actual_row)[actual_col] = pixel; |
254 | 204M | if (pixel_packet_header.raw && (current_pixel_index + 1) < max_pixel_index) |
255 | 2.07M | pixel = TRY(read_pixel_from_stream(m_context->stream, bytes_per_pixel)); |
256 | 204M | } |
257 | 1.82M | pixel_index += pixel_packet_header.pixels_count; |
258 | 1.82M | } |
259 | 9 | break; |
260 | 148 | } |
261 | 19 | default: |
262 | | // FIXME: Implement other TGA data types |
263 | 19 | return Error::from_string_literal("TGAImageDecoderPlugin: Can currently only handle the UncompressedRGB, CompressedRGB or UncompressedBlackAndWhite data type"); |
264 | 267 | } |
265 | | |
266 | 12 | m_context->bitmap = bitmap; |
267 | 12 | return ImageFrameDescriptor { m_context->bitmap, 0 }; |
268 | 267 | } |
269 | | |
270 | | } |