Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : /*
24 : * read/write 2d texture files; allows conversion between pixel formats
25 : * and automatic orientation correction.
26 : */
27 :
28 : /**
29 :
30 : Introduction
31 : ------------
32 :
33 : This module allows reading/writing 2d images in various file formats and
34 : encapsulates them in Tex objects.
35 : It supports converting between pixel formats; this is to an extent done
36 : automatically when reading/writing. Provision is also made for flipping
37 : all images to a default orientation.
38 :
39 :
40 : Format Conversion
41 : -----------------
42 :
43 : Image file formats have major differences in their native pixel format:
44 : some store in BGR order, or have rows arranged bottom-up.
45 : We must balance runtime cost/complexity and convenience for the
46 : application (not dumping the entire problem on its lap).
47 : That means rejecting really obscure formats (e.g. right-to-left pixels),
48 : but converting everything else to uncompressed RGB "plain" format
49 : except where noted in enum TexFlags (1).
50 :
51 : Note: conversion is implemented as a pipeline: e.g. "DDS decompress +
52 : vertical flip" would be done by decompressing to RGB (DDS codec) and then
53 : flipping (generic transform). This is in contrast to all<->all
54 : conversion paths: that would be much more complex, if more efficient.
55 :
56 : Since any kind of preprocessing at runtime is undesirable (the absolute
57 : priority is minimizing load time), prefer file formats that are
58 : close to the final pixel format.
59 :
60 : 1) one of the exceptions is S3TC compressed textures. glCompressedTexImage2D
61 : requires these be passed in their original format; decompressing would be
62 : counterproductive. In this and similar cases, TexFlags indicates such
63 : deviations from the plain format.
64 :
65 :
66 : Default Orientation
67 : -------------------
68 :
69 : After loading, all images (except DDS, because its orientation is
70 : indeterminate) are automatically converted to the global row
71 : orientation: top-down or bottom-up, as specified by
72 : tex_set_global_orientation. If that isn't called, the default is top-down
73 : to match Photoshop's DDS output (since this is meant to be the
74 : no-preprocessing-required optimized format).
75 : Reasons to change it might be to speed up loading bottom-up
76 : BMP or TGA images, or to match OpenGL's convention for convenience;
77 : however, be aware of the abovementioned issues with DDS.
78 :
79 : Rationale: it is not expected that this will happen at the renderer layer
80 : (a 'flip all texcoords' flag is too much trouble), so the
81 : application would have to do the same anyway. By taking care of it here,
82 : we unburden the app and save time, since some codecs (e.g. PNG) can
83 : flip for free when loading.
84 :
85 :
86 : Codecs / IO Implementation
87 : --------------------------
88 :
89 : To ease adding support for new formats, they are organized as codecs.
90 : The interface aims to minimize code duplication, so it's organized
91 : following the principle of "Template Method" - this module both
92 : calls into codecs, and provides helper functions that they use.
93 :
94 : IO is done via VFS, but the codecs are decoupled from this and
95 : work with memory buffers. Access to them is endian-safe.
96 :
97 : When "writing", the image is put into an expandable memory region.
98 : This supports external libraries like libpng that do not know the
99 : output size beforehand, but avoids the need for a buffer between
100 : library and IO layer. Read and write are zero-copy.
101 :
102 : **/
103 :
104 : #ifndef INCLUDED_TEX
105 : #define INCLUDED_TEX
106 :
107 : #include "lib/os_path.h"
108 : #include "lib/file/vfs/vfs_path.h"
109 : #include "lib/allocators/dynarray.h"
110 :
111 : #include <vector>
112 :
113 : namespace ERR
114 : {
115 : const Status TEX_UNKNOWN_FORMAT = -120100;
116 : const Status TEX_INCOMPLETE_HEADER = -120101;
117 : const Status TEX_FMT_INVALID = -120102;
118 : const Status TEX_INVALID_COLOR_TYPE = -120103;
119 : const Status TEX_NOT_8BIT_PRECISION = -120104;
120 : const Status TEX_INVALID_LAYOUT = -120105;
121 : const Status TEX_COMPRESSED = -120106;
122 : const Status TEX_INVALID_SIZE = -120107;
123 : }
124 :
125 : namespace WARN
126 : {
127 : const Status TEX_INVALID_DATA = +120108;
128 : }
129 :
130 : namespace INFO
131 : {
132 : const Status TEX_CODEC_CANNOT_HANDLE = +120109;
133 : }
134 :
135 :
136 : /**
137 : * flags describing the pixel format. these are to be interpreted as
138 : * deviations from "plain" format, i.e. uncompressed RGB.
139 : **/
140 : enum TexFlags
141 : {
142 : /**
143 : * flags & TEX_DXT is a field indicating compression.
144 : * if 0, the texture is uncompressed;
145 : * otherwise, it holds the S3TC type: 1,3,5 or DXT1A.
146 : * not converted by default - glCompressedTexImage2D receives
147 : * the compressed data.
148 : **/
149 : TEX_DXT = 0x7, // mask
150 :
151 : /**
152 : * we need a special value for DXT1a to avoid having to consider
153 : * flags & TEX_ALPHA to determine S3TC type.
154 : * the value is arbitrary; do not rely on it!
155 : **/
156 : DXT1A = 7,
157 :
158 : /**
159 : * indicates B and R pixel components are exchanged. depending on
160 : * flags & TEX_ALPHA or bpp, this means either BGR or BGRA.
161 : * not converted by default - it's an acceptable format for OpenGL.
162 : **/
163 : TEX_BGR = 0x08,
164 :
165 : /**
166 : * indicates the image contains an alpha channel. this is set for
167 : * your convenience - there are many formats containing alpha and
168 : * divining this information from them is hard.
169 : * (conversion is not applicable here)
170 : **/
171 : TEX_ALPHA = 0x10,
172 :
173 : /**
174 : * indicates the image is 8bpp greyscale. this is required to
175 : * differentiate between alpha-only and intensity formats.
176 : * not converted by default - it's an acceptable format for OpenGL.
177 : **/
178 : TEX_GREY = 0x20,
179 :
180 : /**
181 : * flags & TEX_ORIENTATION is a field indicating orientation,
182 : * i.e. in what order the pixel rows are stored.
183 : *
184 : * tex_load always sets this to the global orientation
185 : * (and flips the image accordingly to match).
186 : * texture codecs may in intermediate steps during loading set this
187 : * to 0 if they don't know which way around they are (e.g. DDS),
188 : * or to whatever their file contains.
189 : **/
190 : TEX_BOTTOM_UP = 0x40,
191 : TEX_TOP_DOWN = 0x80,
192 : TEX_ORIENTATION = TEX_BOTTOM_UP|TEX_TOP_DOWN, /// mask
193 :
194 : /**
195 : * indicates the image data includes mipmaps. they are stored from lowest
196 : * to highest (1x1), one after the other.
197 : * (conversion is not applicable here)
198 : **/
199 : TEX_MIPMAPS = 0x100,
200 :
201 : TEX_UNDEFINED_FLAGS = ~0x1FF
202 : };
203 :
204 : /**
205 : * Stores all data describing an image.
206 : * TODO: rename to TextureData.
207 : **/
208 24 : class Tex
209 : {
210 : public:
211 : struct MIPLevel
212 : {
213 : // A pointer to the mip level image data (pixels).
214 : u8* data;
215 : u32 dataSize;
216 : u32 width;
217 : u32 height;
218 : };
219 :
220 : /**
221 : * file buffer or image data. note: during the course of transforms
222 : * (which may occur when being loaded), this may be replaced with
223 : * a new buffer (e.g. if decompressing file contents).
224 : **/
225 : std::shared_ptr<u8> m_Data;
226 :
227 : size_t m_DataSize;
228 :
229 : /**
230 : * offset to image data in file. this is required since
231 : * tex_get_data needs to return the pixels, but data
232 : * returns the actual file buffer. zero-copy load and
233 : * write-back to file is also made possible.
234 : **/
235 : size_t m_Ofs;
236 :
237 : size_t m_Width;
238 : size_t m_Height;
239 : size_t m_Bpp;
240 :
241 : /// see TexFlags and "Format Conversion" in docs.
242 : size_t m_Flags;
243 :
244 24 : ~Tex()
245 24 : {
246 24 : free();
247 24 : }
248 :
249 : /**
250 : * Is the texture object valid and self-consistent?
251 : *
252 : * @return Status
253 : **/
254 : Status validate() const;
255 :
256 : /**
257 : * free all resources associated with the image and make further
258 : * use of it impossible.
259 : *
260 : * @return Status
261 : **/
262 : void free();
263 :
264 : /**
265 : * decode an in-memory texture file into texture object.
266 : *
267 : * FYI, currently BMP, TGA, JPG, JP2, PNG, DDS are supported - but don't
268 : * rely on this (not all codecs may be included).
269 : *
270 : * @param data Input data.
271 : * @param data_size Its size [bytes].
272 : * @return Status.
273 : **/
274 : Status decode(const std::shared_ptr<u8>& data, size_t data_size);
275 :
276 : /**
277 : * encode a texture into a memory buffer in the desired file format.
278 : *
279 : * @param extension (including '.').
280 : * @param da Output memory array. Allocated here; caller must free it
281 : * when no longer needed. Invalid unless function succeeds.
282 : * @return Status
283 : **/
284 : Status encode(const OsPath& extension, DynArray* da);
285 :
286 : /**
287 : * store the given image data into a Tex object; this will be as if
288 : * it had been loaded via tex_load.
289 : *
290 : * rationale: support for in-memory images is necessary for
291 : * emulation of glCompressedTexImage2D and useful overall.
292 : * however, we don't want to provide an alternate interface for each API;
293 : * these would have to be changed whenever fields are added to Tex.
294 : * instead, provide one entry point for specifying images.
295 : * note: since we do not know how \<img\> was allocated, the caller must free
296 : * it themselves (after calling tex_free, which is required regardless of
297 : * alloc type).
298 : *
299 : * we need only add bookkeeping information and "wrap" it in
300 : * our Tex struct, hence the name.
301 : *
302 : * @param w,h Pixel dimensions.
303 : * @param bpp Bits per pixel.
304 : * @param flags TexFlags.
305 : * @param data Img texture data. note: size is calculated from other params.
306 : * @param ofs
307 : * @return Status
308 : **/
309 : Status wrap(size_t w, size_t h, size_t bpp, size_t flags, const std::shared_ptr<u8>& data, size_t ofs);
310 :
311 : //
312 : // modify image
313 : //
314 :
315 : /**
316 : * Change the pixel format.
317 : *
318 : * @param transforms TexFlags that are to be flipped.
319 : * @return Status
320 : **/
321 : Status transform(size_t transforms);
322 :
323 : /**
324 : * Change the pixel format (2nd version)
325 : * (note: this is equivalent to Tex::transform(t, t-\>flags^new_flags).
326 : *
327 : * @param new_flags desired new value of TexFlags.
328 : * @return Status
329 : **/
330 : Status transform_to(size_t new_flags);
331 :
332 : //
333 : // return image information
334 : //
335 :
336 : /**
337 : * return a pointer to the image data (pixels), taking into account any
338 : * header(s) that may come before it.
339 : *
340 : * @return pointer to the data.
341 : **/
342 : u8* get_data();
343 :
344 6 : const std::vector<MIPLevel>& GetMIPLevels() const { return m_MIPLevels; }
345 :
346 : /**
347 : * return the ARGB value of the 1x1 mipmap level of the texture.
348 : *
349 : * @return ARGB value (or 0 if texture does not have mipmaps)
350 : **/
351 : u32 get_average_color() const;
352 :
353 : /**
354 : * return total byte size of the image pixels. (including mipmaps!)
355 : * rationale: this is preferable to calculating manually because it's
356 : * less error-prone (e.g. confusing bits_per_pixel with bytes).
357 : *
358 : * @return size [bytes]
359 : **/
360 : size_t img_size() const;
361 :
362 : private:
363 : void UpdateMIPLevels();
364 :
365 : std::vector<MIPLevel> m_MIPLevels;
366 : };
367 :
368 :
369 : /**
370 : * Set the orientation to which all loaded images will
371 : * automatically be converted (excepting file formats that don't specify
372 : * their orientation, i.e. DDS). See "Default Orientation" in docs.
373 : * @param orientation Either TEX_BOTTOM_UP or TEX_TOP_DOWN.
374 : **/
375 : extern void tex_set_global_orientation(int orientation);
376 :
377 :
378 : /**
379 : * special value for levels_to_skip: the callback will only be called
380 : * for the base mipmap level (i.e. 100%)
381 : **/
382 : const int TEX_BASE_LEVEL_ONLY = -1;
383 :
384 : /**
385 : * callback function for each mipmap level.
386 : *
387 : * @param level number; 0 for base level (i.e. 100%), or the first one
388 : * in case some were skipped.
389 : * @param level_w, level_h pixel dimensions (powers of 2, never 0)
390 : * @param level_data the level's texels
391 : * @param level_data_size [bytes]
392 : * @param cbData passed through from tex_util_foreach_mipmap.
393 : **/
394 : typedef void (*MipmapCB)(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData);
395 :
396 : /**
397 : * for a series of mipmaps stored from base to highest, call back for
398 : * each level.
399 : *
400 : * @param w,h Pixel dimensions.
401 : * @param bpp Bits per pixel.
402 : * @param data Series of mipmaps.
403 : * @param levels_to_skip Number of levels (counting from base) to skip, or
404 : * TEX_BASE_LEVEL_ONLY to only call back for the base image.
405 : * Rationale: this avoids needing to special case for images with or
406 : * without mipmaps.
407 : * @param data_padding Minimum pixel dimensions of mipmap levels.
408 : * This is used in S3TC images, where each level is actually stored in
409 : * 4x4 blocks. usually 1 to indicate levels are consecutive.
410 : * @param cb MipmapCB to call.
411 : * @param cbData Extra data to pass to cb.
412 : **/
413 : extern void tex_util_foreach_mipmap(size_t w, size_t h, size_t bpp, const u8* data, int levels_to_skip, size_t data_padding, MipmapCB cb, void* RESTRICT cbData);
414 :
415 :
416 : //
417 : // image writing
418 : //
419 :
420 : /**
421 : * Is the file's extension that of a texture format supported by tex_load?
422 : *
423 : * Rationale: tex_load complains if the given file is of an
424 : * unsupported type. this API allows users to preempt that warning
425 : * (by checking the filename themselves), and also provides for e.g.
426 : * enumerating only images in a file picker.
427 : * an alternative might be a flag to suppress warning about invalid files,
428 : * but this is open to misuse.
429 : *
430 : * @param pathname Only the extension (starting with '.') is used. case-insensitive.
431 : * @return bool
432 : **/
433 : extern bool tex_is_known_extension(const VfsPath& pathname);
434 :
435 : /**
436 : * return the minimum header size (i.e. offset to pixel data) of the
437 : * file format corresponding to the filename.
438 : *
439 : * rationale: this can be used to optimize calls to tex_write: when
440 : * allocating the buffer that will hold the image, allocate this much
441 : * extra and pass the pointer as base+hdr_size. this allows writing the
442 : * header directly into the output buffer and makes for zero-copy IO.
443 : *
444 : * @param filename Filename; only the extension (that after '.') is used.
445 : * case-insensitive.
446 : * @return size [bytes] or 0 on error (i.e. no codec found).
447 : **/
448 : extern size_t tex_hdr_size(const VfsPath& filename);
449 :
450 : #endif // INCLUDED_TEX
|