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 : #include "precompiled.h"
24 :
25 : #include "tex.h"
26 :
27 : #include "lib/allocators/shared_ptr.h"
28 : #include "lib/bits.h"
29 : #include "lib/sysdep/cpu.h"
30 : #include "lib/tex/tex_codec.h"
31 : #include "lib/timer.h"
32 :
33 : #include <algorithm>
34 : #include <cmath>
35 : #include <cstdlib>
36 :
37 : static const StatusDefinition texStatusDefinitions[] =
38 : {
39 : { ERR::TEX_UNKNOWN_FORMAT, L"Unknown texture format" },
40 : { ERR::TEX_FMT_INVALID, L"Invalid/unsupported texture format" },
41 : { ERR::TEX_INVALID_COLOR_TYPE, L"Invalid color type" },
42 : { ERR::TEX_NOT_8BIT_PRECISION, L"Not 8-bit channel precision" },
43 : { ERR::TEX_INVALID_LAYOUT, L"Unsupported texel layout, e.g. right-to-left" },
44 : { ERR::TEX_COMPRESSED, L"Unsupported texture compression" },
45 : { WARN::TEX_INVALID_DATA, L"Warning: invalid texel data encountered" },
46 : { ERR::TEX_INVALID_SIZE, L"Texture size is incorrect" },
47 : { INFO::TEX_CODEC_CANNOT_HANDLE, L"Texture codec cannot handle the given format" }
48 : };
49 1 : STATUS_ADD_DEFINITIONS(texStatusDefinitions);
50 :
51 :
52 : //-----------------------------------------------------------------------------
53 : // validation
54 : //-----------------------------------------------------------------------------
55 :
56 : // be careful not to use other tex_* APIs here because they call us.
57 183 : Status Tex::validate() const
58 : {
59 183 : if(m_Flags & TEX_UNDEFINED_FLAGS)
60 0 : return ERR::_1;
61 :
62 : // pixel data (only check validity if the image is still in memory).
63 183 : if(m_Data)
64 : {
65 : // file size smaller than header+pixels.
66 : // possible causes: texture file header is invalid,
67 : // or file wasn't loaded completely.
68 183 : if(m_DataSize < m_Ofs + m_Width*m_Height*m_Bpp/8)
69 0 : return ERR::_2;
70 : }
71 :
72 : // bits per pixel
73 : // (we don't bother checking all values; a sanity check is enough)
74 183 : if(m_Bpp % 4 || m_Bpp > 32)
75 0 : return ERR::_3;
76 :
77 : // flags
78 : // .. DXT value
79 183 : const size_t dxt = m_Flags & TEX_DXT;
80 183 : if(dxt != 0 && dxt != 1 && dxt != DXT1A && dxt != 3 && dxt != 5)
81 0 : return ERR::_4;
82 : // .. orientation
83 183 : const size_t orientation = m_Flags & TEX_ORIENTATION;
84 183 : if(orientation == (TEX_BOTTOM_UP|TEX_TOP_DOWN))
85 0 : return ERR::_5;
86 :
87 183 : return INFO::OK;
88 : }
89 :
90 : #define CHECK_TEX(t) RETURN_STATUS_IF_ERR((t->validate()))
91 :
92 :
93 : // check if the given texture format is acceptable: 8bpp grey,
94 : // 24bpp color or 32bpp color+alpha (BGR / upside down are permitted).
95 : // basically, this is the "plain" format understood by all codecs and
96 : // tex_codec_plain_transform.
97 15 : Status tex_validate_plain_format(size_t bpp, size_t flags)
98 : {
99 15 : const bool alpha = (flags & TEX_ALPHA ) != 0;
100 15 : const bool grey = (flags & TEX_GREY ) != 0;
101 15 : const bool dxt = (flags & TEX_DXT ) != 0;
102 15 : const bool mipmaps = (flags & TEX_MIPMAPS) != 0;
103 :
104 15 : if(dxt || mipmaps)
105 0 : WARN_RETURN(ERR::TEX_FMT_INVALID);
106 :
107 : // grey must be 8bpp without alpha, or it's invalid.
108 15 : if(grey)
109 : {
110 0 : if(bpp == 8 && !alpha)
111 0 : return INFO::OK;
112 0 : WARN_RETURN(ERR::TEX_FMT_INVALID);
113 : }
114 :
115 15 : if(bpp == 24 && !alpha)
116 10 : return INFO::OK;
117 5 : if(bpp == 32 && alpha)
118 5 : return INFO::OK;
119 :
120 0 : WARN_RETURN(ERR::TEX_FMT_INVALID);
121 : }
122 :
123 :
124 : //-----------------------------------------------------------------------------
125 : // mipmaps
126 : //-----------------------------------------------------------------------------
127 :
128 59 : void tex_util_foreach_mipmap(size_t w, size_t h, size_t bpp, const u8* pixels, int levels_to_skip, size_t data_padding, MipmapCB cb, void* RESTRICT cbData)
129 : {
130 59 : ENSURE(levels_to_skip >= 0 || levels_to_skip == TEX_BASE_LEVEL_ONLY);
131 :
132 59 : size_t level_w = w, level_h = h;
133 59 : const u8* level_data = pixels;
134 :
135 : // we iterate through the loop (necessary to skip over image data),
136 : // but do not actually call back until the requisite number of
137 : // levels have been skipped (i.e. level == 0).
138 59 : int level = (levels_to_skip == TEX_BASE_LEVEL_ONLY)? 0 : -levels_to_skip;
139 :
140 : // until at level 1x1:
141 : for(;;)
142 : {
143 : // used to skip past this mip level in <data>
144 137 : const size_t level_dataSize = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8);
145 :
146 137 : if(level >= 0)
147 137 : cb((size_t)level, level_w, level_h, level_data, level_dataSize, cbData);
148 :
149 137 : level_data += level_dataSize;
150 :
151 : // 1x1 reached - done
152 137 : if(level_w == 1 && level_h == 1)
153 34 : break;
154 103 : level_w /= 2;
155 103 : level_h /= 2;
156 : // if the texture is non-square, one of the dimensions will become
157 : // 0 before the other. to satisfy OpenGL's expectations, change it
158 : // back to 1.
159 103 : if(level_w == 0) level_w = 1;
160 103 : if(level_h == 0) level_h = 1;
161 103 : level++;
162 :
163 : // special case: no mipmaps, we were only supposed to call for
164 : // the base level
165 103 : if(levels_to_skip == TEX_BASE_LEVEL_ONLY)
166 25 : break;
167 78 : }
168 59 : }
169 :
170 :
171 : struct CreateLevelData
172 : {
173 : size_t num_components;
174 :
175 : size_t prev_level_w;
176 : size_t prev_level_h;
177 : const u8* prev_level_data;
178 : size_t prev_level_dataSize;
179 : };
180 :
181 : // uses 2x2 box filter
182 2 : static void create_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_dataSize, void* RESTRICT cbData)
183 : {
184 2 : CreateLevelData* cld = (CreateLevelData*)cbData;
185 2 : const size_t src_w = cld->prev_level_w;
186 2 : const size_t src_h = cld->prev_level_h;
187 2 : const u8* src = cld->prev_level_data;
188 2 : u8* dst = (u8*)level_data;
189 :
190 : // base level - must be copied over from source buffer
191 2 : if(level == 0)
192 : {
193 1 : ENSURE(level_dataSize == cld->prev_level_dataSize);
194 1 : memcpy(dst, src, level_dataSize);
195 : }
196 : else
197 : {
198 1 : const size_t num_components = cld->num_components;
199 1 : const size_t dx = num_components, dy = dx*src_w;
200 :
201 : // special case: image is too small for 2x2 filter
202 1 : if(cld->prev_level_w == 1 || cld->prev_level_h == 1)
203 : {
204 : // image is either a horizontal or vertical line.
205 : // their memory layout is the same (packed pixels), so no special
206 : // handling is needed; just pick max dimension.
207 0 : for(size_t y = 0; y < std::max(src_w, src_h); y += 2)
208 : {
209 0 : for(size_t i = 0; i < num_components; i++)
210 : {
211 0 : *dst++ = (src[0]+src[dx]+1)/2;
212 0 : src += 1;
213 : }
214 :
215 0 : src += dx; // skip to next pixel (since box is 2x2)
216 0 : }
217 : }
218 : // normal
219 : else
220 : {
221 2 : for(size_t y = 0; y < src_h; y += 2)
222 : {
223 2 : for(size_t x = 0; x < src_w; x += 2)
224 : {
225 4 : for(size_t i = 0; i < num_components; i++)
226 : {
227 3 : *dst++ = (src[0]+src[dx]+src[dy]+src[dx+dy]+2)/4;
228 3 : src += 1;
229 : }
230 :
231 1 : src += dx; // skip to next pixel (since box is 2x2)
232 : }
233 :
234 1 : src += dy; // skip to next row (since box is 2x2)
235 : }
236 : }
237 :
238 1 : ENSURE(dst == level_data + level_dataSize);
239 1 : ENSURE(src == cld->prev_level_data + cld->prev_level_dataSize);
240 : }
241 :
242 2 : cld->prev_level_data = level_data;
243 2 : cld->prev_level_dataSize = level_dataSize;
244 2 : cld->prev_level_w = level_w;
245 2 : cld->prev_level_h = level_h;
246 2 : }
247 :
248 :
249 1 : static Status add_mipmaps(Tex* t, size_t w, size_t h, size_t bpp, void* newData, size_t dataSize)
250 : {
251 : // this code assumes the image is of POT dimension; we don't
252 : // go to the trouble of implementing image scaling because
253 : // the only place this is used (backend textures) requires POT anyway.
254 1 : if(!is_pow2(w) || !is_pow2(h))
255 0 : WARN_RETURN(ERR::TEX_INVALID_SIZE);
256 1 : t->m_Flags |= TEX_MIPMAPS; // must come before tex_img_size!
257 1 : const size_t mipmap_size = t->img_size();
258 2 : std::shared_ptr<u8> mipmapData;
259 1 : AllocateAligned(mipmapData, mipmap_size);
260 1 : CreateLevelData cld = { bpp/8, w, h, (const u8*)newData, dataSize };
261 1 : tex_util_foreach_mipmap(w, h, bpp, mipmapData.get(), 0, 1, create_level, &cld);
262 1 : t->m_Data = mipmapData;
263 1 : t->m_DataSize = mipmap_size;
264 1 : t->m_Ofs = 0;
265 :
266 1 : return INFO::OK;
267 : }
268 :
269 :
270 : //-----------------------------------------------------------------------------
271 : // pixel format conversion (transformation)
272 : //-----------------------------------------------------------------------------
273 :
274 1 : TIMER_ADD_CLIENT(tc_plain_transform);
275 :
276 : // handles BGR and row flipping in "plain" format (see below).
277 : //
278 : // called by codecs after they get their format-specific transforms out of
279 : // the way. note that this approach requires several passes over the image,
280 : // but is much easier to maintain than providing all<->all conversion paths.
281 : //
282 : // somewhat optimized (loops are hoisted, cache associativity accounted for)
283 14 : static Status plain_transform(Tex* t, size_t transforms)
284 : {
285 28 : TIMER_ACCRUE(tc_plain_transform);
286 14 : CHECK_TEX(t);
287 :
288 : // extract texture info
289 14 : const size_t w = t->m_Width, h = t->m_Height, bpp = t->m_Bpp;
290 14 : const size_t flags = t->m_Flags;
291 14 : u8* const srcStorage = t->get_data();
292 :
293 : // sanity checks (not errors, we just can't handle these cases)
294 : // .. unknown transform
295 14 : if(transforms & ~(TEX_BGR|TEX_ORIENTATION|TEX_MIPMAPS|TEX_ALPHA))
296 0 : return INFO::TEX_CODEC_CANNOT_HANDLE;
297 : // .. data is not in "plain" format
298 14 : RETURN_STATUS_IF_ERR(tex_validate_plain_format(bpp, flags));
299 : // .. nothing to do
300 14 : if(!transforms)
301 0 : return INFO::OK;
302 :
303 14 : const size_t srcSize = t->img_size();
304 14 : size_t dstSize = srcSize;
305 :
306 14 : if(transforms & TEX_ALPHA)
307 : {
308 : // add alpha channel
309 9 : if(bpp == 24)
310 : {
311 9 : dstSize = (srcSize / 3) * 4;
312 9 : t->m_Bpp = 32;
313 : }
314 : // remove alpha channel
315 0 : else if(bpp == 32)
316 : {
317 0 : return INFO::TEX_CODEC_CANNOT_HANDLE;
318 : }
319 : // can't have alpha with grayscale
320 : else
321 : {
322 0 : return INFO::TEX_CODEC_CANNOT_HANDLE;
323 : }
324 : }
325 :
326 : // allocate copy of the image data.
327 : // rationale: L1 cache is typically A2 => swapping in-place with a
328 : // line buffer leads to thrashing. we'll assume the whole texture*2
329 : // fits in cache, allocate a copy, and transfer directly from there.
330 : //
331 : // this is necessary even when not flipping because the initial data
332 : // is read-only.
333 28 : std::shared_ptr<u8> dstStorage;
334 14 : AllocateAligned(dstStorage, dstSize);
335 :
336 : // setup row source/destination pointers (simplifies outer loop)
337 14 : u8* dst = (u8*)dstStorage.get();
338 : const u8* src;
339 14 : const size_t pitch = w * bpp/8; // source bpp (not necessarily dest bpp)
340 : // .. avoid y*pitch multiply in row loop; instead, add row_ofs.
341 14 : ssize_t row_ofs = (ssize_t)pitch;
342 :
343 : // flipping rows (0,1,2 -> 2,1,0)
344 14 : if(transforms & TEX_ORIENTATION)
345 : {
346 7 : src = (const u8*)srcStorage+srcSize-pitch; // last row
347 7 : row_ofs = -(ssize_t)pitch;
348 : }
349 : // adding/removing alpha channel (can't convert in-place)
350 7 : else if(transforms & TEX_ALPHA)
351 : {
352 3 : src = (const u8*)srcStorage;
353 : }
354 : // do other transforms in-place
355 : else
356 : {
357 4 : src = (const u8*)dstStorage.get();
358 4 : memcpy(dstStorage.get(), srcStorage, srcSize);
359 : }
360 :
361 : // no conversion necessary
362 14 : if(!(transforms & (TEX_BGR | TEX_ALPHA)))
363 : {
364 2 : if(src != dst) // avoid overlapping memcpy if not flipping rows
365 : {
366 65 : for(size_t y = 0; y < h; y++)
367 : {
368 64 : memcpy(dst, src, pitch);
369 64 : dst += pitch;
370 64 : src += row_ofs;
371 : }
372 : }
373 : }
374 : // RGB -> BGRA, BGR -> RGBA
375 12 : else if(bpp == 24 && (transforms & TEX_ALPHA) && (transforms & TEX_BGR))
376 : {
377 87 : for(size_t y = 0; y < h; y++)
378 : {
379 4244 : for(size_t x = 0; x < w; x++)
380 : {
381 : // need temporaries in case src == dst (i.e. not flipping)
382 4166 : const u8 b = src[0], g = src[1], r = src[2];
383 4166 : dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = 0xFF;
384 4166 : dst += 4;
385 4166 : src += 3;
386 : }
387 78 : src += row_ofs - pitch; // flip? previous row : stay
388 9 : }
389 : }
390 : // RGB -> RGBA, BGR -> BGRA
391 3 : else if(bpp == 24 && (transforms & TEX_ALPHA) && !(transforms & TEX_BGR))
392 : {
393 0 : for(size_t y = 0; y < h; y++)
394 : {
395 0 : for(size_t x = 0; x < w; x++)
396 : {
397 : // need temporaries in case src == dst (i.e. not flipping)
398 0 : const u8 r = src[0], g = src[1], b = src[2];
399 0 : dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = 0xFF;
400 0 : dst += 4;
401 0 : src += 3;
402 : }
403 0 : src += row_ofs - pitch; // flip? previous row : stay
404 0 : }
405 : }
406 : // RGB <-> BGR
407 3 : else if(bpp == 24 && !(transforms & TEX_ALPHA))
408 : {
409 0 : for(size_t y = 0; y < h; y++)
410 : {
411 0 : for(size_t x = 0; x < w; x++)
412 : {
413 : // need temporaries in case src == dst (i.e. not flipping)
414 0 : const u8 b = src[0], g = src[1], r = src[2];
415 0 : dst[0] = r; dst[1] = g; dst[2] = b;
416 0 : dst += 3;
417 0 : src += 3;
418 : }
419 0 : src += row_ofs - pitch; // flip? previous row : stay
420 0 : }
421 : }
422 : // RGBA <-> BGRA
423 3 : else if(bpp == 32 && !(transforms & TEX_ALPHA))
424 : {
425 195 : for(size_t y = 0; y < h; y++)
426 : {
427 12480 : for(size_t x = 0; x < w; x++)
428 : {
429 : // need temporaries in case src == dst (i.e. not flipping)
430 12288 : const u8 b = src[0], g = src[1], r = src[2], a = src[3];
431 12288 : dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = a;
432 12288 : dst += 4;
433 12288 : src += 4;
434 : }
435 192 : src += row_ofs - pitch; // flip? previous row : stay
436 3 : }
437 : }
438 : else
439 : {
440 0 : debug_warn(L"unsupported transform");
441 0 : return INFO::TEX_CODEC_CANNOT_HANDLE;
442 : }
443 :
444 14 : t->m_Data = dstStorage;
445 14 : t->m_DataSize = dstSize;
446 14 : t->m_Ofs = 0;
447 :
448 14 : if(!(t->m_Flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS)
449 1 : RETURN_STATUS_IF_ERR(add_mipmaps(t, w, h, bpp, dstStorage.get(), dstSize));
450 :
451 14 : CHECK_TEX(t);
452 14 : return INFO::OK;
453 : }
454 :
455 :
456 1 : TIMER_ADD_CLIENT(tc_transform);
457 :
458 : // change the pixel format by flipping the state of all TEX_* flags
459 : // that are set in transforms.
460 15 : Status Tex::transform(size_t transforms)
461 : {
462 30 : TIMER_ACCRUE(tc_transform);
463 15 : CHECK_TEX(this);
464 :
465 15 : const size_t target_flags = m_Flags ^ transforms;
466 : size_t remaining_transforms;
467 : for(;;)
468 : {
469 32 : remaining_transforms = target_flags ^ m_Flags;
470 : // we're finished (all required transforms have been done)
471 32 : if(remaining_transforms == 0)
472 2 : return INFO::OK;
473 :
474 30 : Status ret = tex_codec_transform(this, remaining_transforms);
475 30 : UpdateMIPLevels();
476 30 : if(ret != INFO::OK)
477 13 : break;
478 17 : }
479 :
480 : // last chance
481 13 : RETURN_STATUS_IF_ERR(plain_transform(this, remaining_transforms));
482 13 : UpdateMIPLevels();
483 13 : return INFO::OK;
484 : }
485 :
486 :
487 : // change the pixel format to the new format specified by <new_flags>.
488 : // (note: this is equivalent to transform(t, t->flags^new_flags).
489 15 : Status Tex::transform_to(size_t new_flags)
490 : {
491 : // transform takes care of validating
492 15 : const size_t transforms = m_Flags ^ new_flags;
493 15 : return transform(transforms);
494 : }
495 :
496 :
497 : //-----------------------------------------------------------------------------
498 : // image orientation
499 : //-----------------------------------------------------------------------------
500 :
501 : // see "Default Orientation" in docs.
502 :
503 : static int global_orientation = TEX_TOP_DOWN;
504 :
505 : // set the orientation (either TEX_BOTTOM_UP or TEX_TOP_DOWN) to which
506 : // all loaded images will automatically be converted
507 : // (excepting file formats that don't specify their orientation, i.e. DDS).
508 0 : void tex_set_global_orientation(int o)
509 : {
510 0 : ENSURE(o == TEX_TOP_DOWN || o == TEX_BOTTOM_UP);
511 0 : global_orientation = o;
512 0 : }
513 :
514 :
515 14 : static void flip_to_global_orientation(Tex* t)
516 : {
517 : // (can't use normal CHECK_TEX due to void return)
518 14 : WARN_IF_ERR(t->validate());
519 :
520 14 : size_t orientation = t->m_Flags & TEX_ORIENTATION;
521 : // if codec knows which way around the image is (i.e. not DDS):
522 14 : if(orientation)
523 : {
524 : // flip image if necessary
525 1 : size_t transforms = orientation ^ global_orientation;
526 1 : WARN_IF_ERR(plain_transform(t, transforms));
527 : }
528 :
529 : // indicate image is at global orientation. this is still done even
530 : // if the codec doesn't know: the default orientation should be chosen
531 : // to make that work correctly (see "Default Orientation" in docs).
532 14 : t->m_Flags = (t->m_Flags & ~TEX_ORIENTATION) | global_orientation;
533 :
534 : // (can't use normal CHECK_TEX due to void return)
535 14 : WARN_IF_ERR(t->validate());
536 14 : }
537 :
538 :
539 : // indicate if the orientation specified by <src_flags> matches
540 : // dst_orientation (if the latter is 0, then the global_orientation).
541 : // (we ask for src_flags instead of src_orientation so callers don't
542 : // have to mask off TEX_ORIENTATION)
543 3 : bool tex_orientations_match(size_t src_flags, size_t dst_orientation)
544 : {
545 3 : const size_t src_orientation = src_flags & TEX_ORIENTATION;
546 3 : if(dst_orientation == 0)
547 3 : dst_orientation = global_orientation;
548 3 : return (src_orientation == dst_orientation);
549 : }
550 :
551 :
552 : //-----------------------------------------------------------------------------
553 : // misc. API
554 : //-----------------------------------------------------------------------------
555 :
556 : // indicate if <filename>'s extension is that of a texture format
557 : // supported by Tex::load. case-insensitive.
558 : //
559 : // rationale: Tex::load complains if the given file is of an
560 : // unsupported type. this API allows users to preempt that warning
561 : // (by checking the filename themselves), and also provides for e.g.
562 : // enumerating only images in a file picker.
563 : // an alternative might be a flag to suppress warning about invalid files,
564 : // but this is open to misuse.
565 0 : bool tex_is_known_extension(const VfsPath& pathname)
566 : {
567 : const ITexCodec* dummy;
568 : // found codec for it => known extension
569 0 : const OsPath extension = pathname.Extension();
570 0 : if(tex_codec_for_filename(extension, &dummy) == INFO::OK)
571 0 : return true;
572 :
573 0 : return false;
574 : }
575 :
576 :
577 : // store the given image data into a Tex object; this will be as if
578 : // it had been loaded via Tex::load.
579 : //
580 : // rationale: support for in-memory images is necessary for
581 : // emulation of glCompressedTexImage2D and useful overall.
582 : // however, we don't want to provide an alternate interface for each API;
583 : // these would have to be changed whenever fields are added to Tex.
584 : // instead, provide one entry point for specifying images.
585 : //
586 : // we need only add bookkeeping information and "wrap" it in
587 : // our Tex struct, hence the name.
588 13 : Status Tex::wrap(size_t w, size_t h, size_t bpp, size_t flags, const std::shared_ptr<u8>& data, size_t ofs)
589 : {
590 13 : m_Width = w;
591 13 : m_Height = h;
592 13 : m_Bpp = bpp;
593 13 : m_Flags = flags;
594 13 : m_Data = data;
595 13 : m_DataSize = ofs + w*h*bpp/8;
596 13 : m_Ofs = ofs;
597 :
598 13 : CHECK_TEX(this);
599 :
600 13 : UpdateMIPLevels();
601 :
602 13 : return INFO::OK;
603 : }
604 :
605 :
606 : // free all resources associated with the image and make further
607 : // use of it impossible.
608 24 : void Tex::free()
609 : {
610 : // do not validate - this is called from Tex::load if loading
611 : // failed, so not all fields may be valid.
612 :
613 24 : m_Data.reset();
614 :
615 : // TODO: refactor fields zeroing.
616 24 : }
617 :
618 :
619 : //-----------------------------------------------------------------------------
620 : // getters
621 : //-----------------------------------------------------------------------------
622 :
623 : // returns a pointer to the image data (pixels), taking into account any
624 : // header(s) that may come before it.
625 37 : u8* Tex::get_data()
626 : {
627 : // (can't use normal CHECK_TEX due to u8* return value)
628 37 : WARN_IF_ERR(validate());
629 :
630 37 : u8* p = m_Data.get();
631 37 : if(!p)
632 0 : return nullptr;
633 37 : return p + m_Ofs;
634 : }
635 :
636 : // returns color of 1x1 mipmap level
637 6 : u32 Tex::get_average_color() const
638 : {
639 : // require mipmaps
640 6 : if(!(m_Flags & TEX_MIPMAPS))
641 0 : return 0;
642 :
643 : // find the total size of image data
644 6 : size_t size = img_size();
645 :
646 : // compute the size of the last (1x1) mipmap level
647 6 : const size_t data_padding = (m_Flags & TEX_DXT)? 4 : 1;
648 6 : size_t last_level_size = (size_t)(data_padding * data_padding * m_Bpp/8);
649 :
650 : // construct a new texture based on the current one,
651 : // but only include the last mipmap level
652 : // do this so that we can use the general conversion methods for the pixel data
653 12 : Tex basetex = *this;
654 6 : uint8_t *data = new uint8_t[last_level_size];
655 6 : memcpy(data, m_Data.get() + m_Ofs + size - last_level_size, last_level_size);
656 12 : std::shared_ptr<uint8_t> sdata(data, ArrayDeleter());
657 6 : basetex.wrap(1, 1, m_Bpp, m_Flags, sdata, 0);
658 :
659 : // convert to BGRA
660 6 : WARN_IF_ERR(basetex.transform_to(TEX_BGR | TEX_ALPHA));
661 :
662 : // extract components into u32
663 6 : ENSURE(basetex.m_DataSize >= basetex.m_Ofs+4);
664 6 : u8 b = basetex.m_Data.get()[basetex.m_Ofs];
665 6 : u8 g = basetex.m_Data.get()[basetex.m_Ofs+1];
666 6 : u8 r = basetex.m_Data.get()[basetex.m_Ofs+2];
667 6 : u8 a = basetex.m_Data.get()[basetex.m_Ofs+3];
668 6 : return b + (g << 8) + (r << 16) + (a << 24);
669 : }
670 :
671 :
672 125 : static void add_level_size(size_t UNUSED(level), size_t UNUSED(level_w), size_t UNUSED(level_h), const u8* RESTRICT UNUSED(level_data), size_t level_dataSize, void* RESTRICT cbData)
673 : {
674 125 : size_t* ptotal_size = (size_t*)cbData;
675 125 : *ptotal_size += level_dataSize;
676 125 : }
677 :
678 : // return total byte size of the image pixels. (including mipmaps!)
679 : // this is preferable to calculating manually because it's
680 : // less error-prone (e.g. confusing bits_per_pixel with bytes).
681 48 : size_t Tex::img_size() const
682 : {
683 : // (can't use normal CHECK_TEX due to size_t return value)
684 48 : WARN_IF_ERR(validate());
685 :
686 48 : const int levels_to_skip = (m_Flags & TEX_MIPMAPS)? 0 : TEX_BASE_LEVEL_ONLY;
687 48 : const size_t data_padding = (m_Flags & TEX_DXT)? 4 : 1;
688 48 : size_t out_size = 0;
689 48 : tex_util_foreach_mipmap(m_Width, m_Height, m_Bpp, 0, levels_to_skip, data_padding, add_level_size, &out_size);
690 48 : return out_size;
691 : }
692 :
693 :
694 : // return the minimum header size (i.e. offset to pixel data) of the
695 : // file format indicated by <fn>'s extension (that is all it need contain:
696 : // e.g. ".bmp"). returns 0 on error (i.e. no codec found).
697 : // this can be used to optimize calls to tex_write: when allocating the
698 : // buffer that will hold the image, allocate this much extra and
699 : // pass the pointer as base+hdr_size. this allows writing the header
700 : // directly into the output buffer and makes for zero-copy IO.
701 0 : size_t tex_hdr_size(const VfsPath& filename)
702 : {
703 : const ITexCodec* c;
704 :
705 0 : const OsPath extension = filename.Extension();
706 0 : WARN_RETURN_STATUS_IF_ERR(tex_codec_for_filename(extension, &c));
707 0 : return c->hdr_size(0);
708 : }
709 :
710 :
711 : //-----------------------------------------------------------------------------
712 : // read/write from memory and disk
713 : //-----------------------------------------------------------------------------
714 :
715 14 : Status Tex::decode(const std::shared_ptr<u8>& Data, size_t DataSize)
716 : {
717 : const ITexCodec* c;
718 14 : RETURN_STATUS_IF_ERR(tex_codec_for_header(Data.get(), DataSize, &c));
719 :
720 : // make sure the entire header is available
721 14 : const size_t min_hdr_size = c->hdr_size(0);
722 14 : if(DataSize < min_hdr_size)
723 0 : return ERR::TEX_INCOMPLETE_HEADER;
724 14 : const size_t hdr_size = c->hdr_size(Data.get());
725 14 : if(DataSize < hdr_size)
726 0 : return ERR::TEX_INCOMPLETE_HEADER;
727 :
728 14 : m_Data = Data;
729 14 : m_DataSize = DataSize;
730 14 : m_Ofs = hdr_size;
731 :
732 14 : RETURN_STATUS_IF_ERR(c->decode(Data.get(), DataSize, this));
733 :
734 : // sanity checks
735 14 : if(!m_Width || !m_Height || m_Bpp > 32)
736 0 : return ERR::TEX_FMT_INVALID;
737 14 : if(m_DataSize < m_Ofs + img_size())
738 0 : return ERR::TEX_INVALID_SIZE;
739 :
740 14 : flip_to_global_orientation(this);
741 :
742 14 : CHECK_TEX(this);
743 :
744 14 : UpdateMIPLevels();
745 :
746 14 : return INFO::OK;
747 : }
748 :
749 :
750 0 : Status Tex::encode(const OsPath& extension, DynArray* da)
751 : {
752 0 : CHECK_TEX(this);
753 0 : WARN_RETURN_STATUS_IF_ERR(tex_validate_plain_format(m_Bpp, m_Flags));
754 :
755 : // we could be clever here and avoid the extra alloc if our current
756 : // memory block ensued from the same kind of texture file. this is
757 : // most likely the case if in_img == get_data() + c->hdr_size(0).
758 : // this would make for zero-copy IO.
759 :
760 0 : const size_t max_out_size = img_size()*4 + 256*KiB;
761 0 : RETURN_STATUS_IF_ERR(da_alloc(da, max_out_size));
762 :
763 : const ITexCodec* c;
764 0 : WARN_RETURN_STATUS_IF_ERR(tex_codec_for_filename(extension, &c));
765 :
766 : // encode into <da>
767 0 : Status err = c->encode(this, da);
768 0 : if(err < 0)
769 : {
770 0 : (void)da_free(da);
771 0 : WARN_RETURN(err);
772 : }
773 :
774 0 : return INFO::OK;
775 : }
776 :
777 70 : void Tex::UpdateMIPLevels()
778 : {
779 70 : m_MIPLevels.clear();
780 :
781 70 : if (m_Flags & TEX_MIPMAPS)
782 : {
783 : // We add one because we need to account the smallest 1x1 level.
784 14 : m_MIPLevels.reserve(ceil_log2(std::max(m_Width, m_Height)) + 1);
785 : }
786 :
787 70 : u8* levelData = m_Data.get();
788 70 : levelData += m_Ofs;
789 :
790 70 : const u32 dataPadding = (m_Flags & TEX_DXT) != 0 ? 4 : 1;
791 70 : u32 levelWidth = m_Width, levelHeight = m_Height;
792 110 : for (u32 level = 0; ; ++level)
793 : {
794 110 : const u32 levelDataSize = round_up(levelWidth, dataPadding) * round_up(levelHeight, dataPadding) * m_Bpp / 8;
795 110 : m_MIPLevels.emplace_back();
796 110 : m_MIPLevels.back().data = levelData;
797 110 : m_MIPLevels.back().dataSize = levelDataSize;
798 110 : m_MIPLevels.back().width = levelWidth;
799 110 : m_MIPLevels.back().height = levelHeight;
800 :
801 110 : if (!(m_Flags & TEX_MIPMAPS))
802 56 : break;
803 :
804 54 : if (levelWidth == 1 && levelHeight == 1)
805 14 : break;
806 :
807 40 : levelData += levelDataSize;
808 40 : levelWidth = std::max<u32>(levelWidth / 2, 1);
809 40 : levelHeight = std::max<u32>(levelHeight / 2, 1);
810 40 : }
811 73 : }
|