1
0
forked from 0ad/0ad

refactor: remove "wrapping" and "read" functionality for DynArray (in preparation for replacing it with template policies for more flexible Pool etc.)

This was SVN commit r10024.
This commit is contained in:
janwas 2011-08-17 08:38:53 +00:00
parent 939f056794
commit 9f97610cb2
11 changed files with 111 additions and 174 deletions

View File

@ -31,11 +31,6 @@
#include "lib/allocators/page_aligned.h"
// indicates that this DynArray must not be resized or freed
// (e.g. because it merely wraps an existing memory range).
// stored in da->prot to reduce size; doesn't conflict with any PROT_* flags.
const int DA_NOT_OUR_MEM = 0x40000000;
static Status validate_da(DynArray* da)
{
if(!da)
@ -57,7 +52,7 @@ static Status validate_da(DynArray* da)
WARN_RETURN(ERR::_4);
if(pos > cur_size || pos > max_size_pa)
WARN_RETURN(ERR::_5);
if(prot & ~(PROT_READ|PROT_WRITE|PROT_EXEC|DA_NOT_OUR_MEM))
if(prot & ~(PROT_READ|PROT_WRITE|PROT_EXEC))
WARN_RETURN(ERR::_6);
return INFO::OK;
@ -91,16 +86,12 @@ Status da_free(DynArray* da)
u8* p = da->base;
size_t size_pa = da->max_size_pa;
bool was_wrapped = (da->prot & DA_NOT_OUR_MEM) != 0;
// wipe out the DynArray for safety
// (must be done here because mem_Release may fail)
memset(da, 0, sizeof(*da));
// skip mem_Release if <da> was allocated via da_wrap_fixed
// (i.e. it doesn't actually own any memory). don't complain;
// da_free is supposed to be called even in the above case.
if(!was_wrapped && size_pa)
if(size_pa)
RETURN_STATUS_IF_ERR(mem_Release(p, size_pa));
return INFO::OK;
}
@ -110,9 +101,6 @@ Status da_set_size(DynArray* da, size_t new_size)
{
CHECK_DA(da);
if(da->prot & DA_NOT_OUR_MEM)
WARN_RETURN(ERR::LOGIC);
// determine how much to add/remove
const size_t cur_size_pa = Align<pageSize>(da->cur_size);
const size_t new_size_pa = Align<pageSize>(new_size);
@ -154,11 +142,6 @@ Status da_set_prot(DynArray* da, int prot)
{
CHECK_DA(da);
// somewhat more subtle: POSIX mprotect requires the memory have been
// mmap-ed, which it probably wasn't here.
if(da->prot & DA_NOT_OUR_MEM)
WARN_RETURN(ERR::LOGIC);
da->prot = prot;
RETURN_STATUS_IF_ERR(mem_Protect(da->base, da->cur_size_pa, prot));
@ -167,31 +150,6 @@ Status da_set_prot(DynArray* da, int prot)
}
Status da_wrap_fixed(DynArray* da, u8* p, size_t size)
{
da->base = p;
da->max_size_pa = Align<pageSize>(size);
da->cur_size = size;
da->cur_size_pa = da->max_size_pa;
da->prot = PROT_READ|PROT_WRITE|DA_NOT_OUR_MEM;
da->pos = 0;
CHECK_DA(da);
return INFO::OK;
}
Status da_read(DynArray* da, void* data, size_t size)
{
// make sure we have enough data to read
if(da->pos+size > da->cur_size)
WARN_RETURN(ERR::FAIL);
memcpy(data, da->base+da->pos, size);
da->pos += size;
return INFO::OK;
}
Status da_append(DynArray* da, const void* data, size_t size)
{
RETURN_STATUS_IF_ERR(da_reserve(da, size));

View File

@ -109,33 +109,6 @@ LIB_API Status da_reserve(DynArray* da, size_t size);
**/
LIB_API Status da_set_prot(DynArray* da, int prot);
/**
* "wrap" (i.e. store information about) the given buffer in a DynArray.
*
* this is used to allow calling da_read or da_append on normal buffers.
* da_free should be called when the DynArray is no longer needed,
* even though it doesn't free this memory (but does zero the DynArray).
*
* @param da DynArray. Note: any future operations on it that would
* change the underlying memory (e.g. da_set_size) will fail.
* @param p target memory (no alignment/padding requirements)
* @param size maximum size (no alignment requirements)
* @return Status.
**/
LIB_API Status da_wrap_fixed(DynArray* da, u8* p, size_t size);
/**
* "read" from array, i.e. copy into the given buffer.
*
* starts at offset DynArray.pos and advances this.
*
* @param da DynArray.
* @param data_dst destination memory
* @param size [bytes] to copy
* @return Status.
**/
LIB_API Status da_read(DynArray* da, void* data_dst, size_t size);
/**
* "write" to array, i.e. copy from the given buffer.
*

View File

@ -37,16 +37,5 @@ public:
TS_ASSERT_OK(da_set_size(&da, 1000));
TS_ASSERT_OK(da_set_prot(&da, PROT_NONE));
TS_ASSERT_OK(da_free(&da));
// test wrapping existing mem blocks for use with da_read
u8 data[4] = { 0x12, 0x34, 0x56, 0x78 };
TS_ASSERT_OK(da_wrap_fixed(&da, data, sizeof(data)));
u8 buf[4];
TS_ASSERT_OK(da_read(&da, buf, 4));
TS_ASSERT_EQUALS(read_le32(buf), (u32)0x78563412); // read correct value
debug_SkipErrors(ERR::FAIL);
TS_ASSERT(da_read(&da, buf, 1) < 0); // no more data left
TS_ASSERT_EQUALS((uint32_t)debug_StopSkippingErrors(), (uint32_t)1);
TS_ASSERT_OK(da_free(&da));
}
};

View File

@ -145,12 +145,12 @@ void tex_util_foreach_mipmap(size_t w, size_t h, size_t bpp, const u8* pixels, i
for(;;)
{
// used to skip past this mip level in <data>
const size_t level_data_size = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8);
const size_t level_dataSize = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8);
if(level >= 0)
cb((size_t)level, level_w, level_h, level_data, level_data_size, cbData);
cb((size_t)level, level_w, level_h, level_data, level_dataSize, cbData);
level_data += level_data_size;
level_data += level_dataSize;
// 1x1 reached - done
if(level_w == 1 && level_h == 1)
@ -179,11 +179,11 @@ struct CreateLevelData
size_t prev_level_w;
size_t prev_level_h;
const u8* prev_level_data;
size_t prev_level_data_size;
size_t prev_level_dataSize;
};
// uses 2x2 box filter
static void create_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData)
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)
{
CreateLevelData* cld = (CreateLevelData*)cbData;
const size_t src_w = cld->prev_level_w;
@ -194,8 +194,8 @@ static void create_level(size_t level, size_t level_w, size_t level_h, const u8*
// base level - must be copied over from source buffer
if(level == 0)
{
ENSURE(level_data_size == cld->prev_level_data_size);
memcpy(dst, src, level_data_size);
ENSURE(level_dataSize == cld->prev_level_dataSize);
memcpy(dst, src, level_dataSize);
}
else
{
@ -239,18 +239,18 @@ static void create_level(size_t level, size_t level_w, size_t level_h, const u8*
}
}
ENSURE(dst == level_data + level_data_size);
ENSURE(src == cld->prev_level_data + cld->prev_level_data_size);
ENSURE(dst == level_data + level_dataSize);
ENSURE(src == cld->prev_level_data + cld->prev_level_dataSize);
}
cld->prev_level_data = level_data;
cld->prev_level_data_size = level_data_size;
cld->prev_level_dataSize = level_dataSize;
cld->prev_level_w = level_w;
cld->prev_level_h = level_h;
}
static Status add_mipmaps(Tex* t, size_t w, size_t h, size_t bpp, void* newData, size_t data_size)
static Status add_mipmaps(Tex* t, size_t w, size_t h, size_t bpp, void* newData, size_t dataSize)
{
// this code assumes the image is of POT dimension; we don't
// go to the trouble of implementing image scaling because
@ -261,7 +261,7 @@ static Status add_mipmaps(Tex* t, size_t w, size_t h, size_t bpp, void* newData,
const size_t mipmap_size = tex_img_size(t);
shared_ptr<u8> mipmapData;
AllocateAligned(mipmapData, mipmap_size);
CreateLevelData cld = { bpp/8, w, h, (const u8*)newData, data_size };
CreateLevelData cld = { bpp/8, w, h, (const u8*)newData, dataSize };
tex_util_foreach_mipmap(w, h, bpp, mipmapData.get(), 0, 1, create_level, &cld);
t->data = mipmapData;
t->dataSize = mipmap_size;
@ -295,7 +295,7 @@ TIMER_ACCRUE(tc_plain_transform);
// extract texture info
const size_t w = t->w, h = t->h, bpp = t->bpp;
const size_t flags = t->flags;
u8* const data = tex_get_data(t);
u8* const srcStorage = tex_get_data(t);
// sanity checks (not errors, we just can't handle these cases)
// .. unknown transform
@ -307,15 +307,15 @@ TIMER_ACCRUE(tc_plain_transform);
if(!transforms)
return INFO::OK;
const size_t data_size = tex_img_size(t); // size of source
size_t new_data_size = data_size; // size of destination
const size_t srcSize = tex_img_size(t);
size_t dstSize = srcSize;
if(transforms & TEX_ALPHA)
{
// add alpha channel
if(bpp == 24)
{
new_data_size = (data_size / 3) * 4;
dstSize = (srcSize / 3) * 4;
t->bpp = 32;
}
// remove alpha channel
@ -323,7 +323,7 @@ TIMER_ACCRUE(tc_plain_transform);
{
return INFO::TEX_CODEC_CANNOT_HANDLE;
}
// can't have alpha with greyscale
// can't have alpha with grayscale
else
{
return INFO::TEX_CODEC_CANNOT_HANDLE;
@ -337,11 +337,11 @@ TIMER_ACCRUE(tc_plain_transform);
//
// this is necessary even when not flipping because the initial data
// is read-only.
shared_ptr<u8> newData;
AllocateAligned(newData, new_data_size);
shared_ptr<u8> dstStorage;
AllocateAligned(dstStorage, dstSize);
// setup row source/destination pointers (simplifies outer loop)
u8* dst = (u8*)newData.get();
u8* dst = (u8*)dstStorage.get();
const u8* src;
const size_t pitch = w * bpp/8; // source bpp (not necessarily dest bpp)
// .. avoid y*pitch multiply in row loop; instead, add row_ofs.
@ -350,19 +350,19 @@ TIMER_ACCRUE(tc_plain_transform);
// flipping rows (0,1,2 -> 2,1,0)
if(transforms & TEX_ORIENTATION)
{
src = (const u8*)data+data_size-pitch; // last row
src = (const u8*)srcStorage+srcSize-pitch; // last row
row_ofs = -(ssize_t)pitch;
}
// adding/removing alpha channel (can't convert in-place)
else if(transforms & TEX_ALPHA)
{
src = (const u8*)data;
src = (const u8*)srcStorage;
}
// do other transforms in-place
else
{
src = (const u8*)newData.get();
memcpy(newData.get(), data, data_size);
src = (const u8*)dstStorage.get();
memcpy(dstStorage.get(), srcStorage, srcSize);
}
// no conversion necessary
@ -448,12 +448,12 @@ TIMER_ACCRUE(tc_plain_transform);
return INFO::TEX_CODEC_CANNOT_HANDLE;
}
t->data = newData;
t->dataSize = new_data_size;
t->data = dstStorage;
t->dataSize = dstSize;
t->ofs = 0;
if(!(t->flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS)
RETURN_STATUS_IF_ERR(add_mipmaps(t, w, h, bpp, newData.get(), new_data_size));
RETURN_STATUS_IF_ERR(add_mipmaps(t, w, h, bpp, dstStorage.get(), dstSize));
CHECK_TEX(t);
return INFO::OK;
@ -671,10 +671,10 @@ u32 tex_get_average_colour(const Tex* t)
}
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_data_size, void* RESTRICT cbData)
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)
{
size_t* ptotal_size = (size_t*)cbData;
*ptotal_size += level_data_size;
*ptotal_size += level_dataSize;
}
// return total byte size of the image pixels. (including mipmaps!)
@ -714,33 +714,24 @@ size_t tex_hdr_size(const VfsPath& filename)
// read/write from memory and disk
//-----------------------------------------------------------------------------
Status tex_decode(const shared_ptr<u8>& data, size_t data_size, Tex* t)
Status tex_decode(const shared_ptr<u8>& data, size_t dataSize, Tex* t)
{
const TexCodecVTbl* c;
RETURN_STATUS_IF_ERR(tex_codec_for_header(data.get(), data_size, &c));
RETURN_STATUS_IF_ERR(tex_codec_for_header(data.get(), dataSize, &c));
// make sure the entire header is available
const size_t min_hdr_size = c->hdr_size(0);
if(data_size < min_hdr_size)
if(dataSize < min_hdr_size)
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
const size_t hdr_size = c->hdr_size(data.get());
if(data_size < hdr_size)
if(dataSize < hdr_size)
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
t->data = data;
t->dataSize = data_size;
t->dataSize = dataSize;
t->ofs = hdr_size;
// for orthogonality, encode and decode both receive the memory as a
// DynArray. package data into one and free it again after decoding:
DynArray da;
RETURN_STATUS_IF_ERR(da_wrap_fixed(&da, data.get(), data_size));
RETURN_STATUS_IF_ERR(c->decode(&da, t));
// note: not reached if decode fails. that's not a problem;
// this call just zeroes <da> and could be left out.
(void)da_free(&da);
RETURN_STATUS_IF_ERR(c->decode((rpU8)data.get(), dataSize, t));
// sanity checks
if(!t->w || !t->h || t->bpp > 32)

View File

@ -95,11 +95,9 @@ static size_t bmp_hdr_size(const u8* file)
// requirements: uncompressed, direct colour, bottom up
static Status bmp_decode(DynArray* RESTRICT da, Tex* RESTRICT t)
static Status bmp_decode(rpU8 data, size_t UNUSED(size), Tex* RESTRICT t)
{
u8* file = da->base;
const BmpHeader* hdr = (const BmpHeader*)file;
const BmpHeader* hdr = (const BmpHeader*)data;
const long w = (long)read_le32(&hdr->biWidth);
const long h_ = (long)read_le32(&hdr->biHeight);
const u16 bpp = read_le16(&hdr->biBitCount);

View File

@ -41,16 +41,15 @@ struct TexCodecVTbl
/**
* decode the file into a Tex structure.
*
* @param da input data array (not const, because the texture
* @param data input data array (non-const, because the texture
* may have to be flipped in-place - see "texture orientation").
* its size is guaranteed to be >= 4.
* (usually enough to compare the header's "magic" field;
* anyway, no legitimate file will be smaller)
* @param size [bytes] of data, always >= 4
* (this is usually enough to compare the header's "magic" field,
* and no legitimate file will be smaller)
* @param t output texture object
* @return Status
**/
Status (*decode)(DynArray* RESTRICT da, Tex * RESTRICT t);
Status (*decode)(u8* data, size_t size, Tex* RESTRICT t);
/**
* encode the texture data into the codec's file format (in memory).
@ -63,7 +62,7 @@ struct TexCodecVTbl
* by the caller.
* @return Status
**/
Status (*encode)(Tex* RESTRICT t, DynArray * RESTRICT da);
Status (*encode)(Tex* RESTRICT t, DynArray* RESTRICT da);
/**
* transform the texture's pixel format.

View File

@ -588,10 +588,9 @@ static size_t dds_hdr_size(const u8* UNUSED(file))
}
static Status dds_decode(DynArray* RESTRICT da, Tex* RESTRICT t)
static Status dds_decode(rpU8 data, size_t UNUSED(size), Tex* RESTRICT t)
{
u8* file = da->base;
const DDS_HEADER* sd = (const DDS_HEADER*)(file+4);
const DDS_HEADER* sd = (const DDS_HEADER*)(data+4);
RETURN_STATUS_IF_ERR(decode_sd(sd, t->w, t->h, t->bpp, t->flags));
return INFO::OK;
}

View File

@ -27,6 +27,7 @@
#ifndef INCLUDED_TEX_INTERNAL
#define INCLUDED_TEX_INTERNAL
#include "lib/pointer_typedefs.h"
#include "lib/allocators/dynarray.h"
#include "lib/file/io/io.h" // io::Allocate

View File

@ -169,13 +169,10 @@ METHODDEF(void) src_term(j_decompress_ptr UNUSED(cinfo))
* The caller is responsible for freeing it after finishing decompression.
*/
GLOBAL(void) src_prepare(j_decompress_ptr cinfo, DynArray* da)
GLOBAL(void) src_prepare(j_decompress_ptr cinfo, rpU8 data, size_t size)
{
SrcPtr src;
const u8* p = da->base;
const size_t size = da->cur_size;
/* Treat 0-length buffer as fatal error */
if(size == 0)
ERREXIT(cinfo, JERR_INPUT_EMPTY);
@ -207,7 +204,7 @@ GLOBAL(void) src_prepare(j_decompress_ptr cinfo, DynArray* da)
* if fill_input_buffer is called, the buffer was overrun.
*/
src->pub.bytes_in_buffer = size;
src->pub.next_input_byte = (JOCTET*)p;
src->pub.next_input_byte = (JOCTET*)data;
}
@ -443,9 +440,9 @@ static Status jpg_transform(Tex* UNUSED(t), size_t UNUSED(transforms))
// due to less copying.
static Status jpg_decode_impl(DynArray* da, jpeg_decompress_struct* cinfo, Tex* t)
static Status jpg_decode_impl(rpU8 data, size_t size, jpeg_decompress_struct* cinfo, Tex* t)
{
src_prepare(cinfo, da);
src_prepare(cinfo, data, size);
// ignore return value since:
// - suspension is not possible with the mem data source
@ -479,12 +476,12 @@ static Status jpg_decode_impl(DynArray* da, jpeg_decompress_struct* cinfo, Tex*
// alloc destination buffer
const size_t pitch = w * bpp / 8;
const size_t img_size = pitch * h; // for allow_rows
shared_ptr<u8> data;
AllocateAligned(data, img_size, pageSize);
const size_t imgSize = pitch * h; // for allow_rows
shared_ptr<u8> img;
AllocateAligned(img, imgSize, pageSize);
// read rows
std::vector<RowPtr> rows = tex_codec_alloc_rows(data.get(), h, pitch, TEX_TOP_DOWN, 0);
std::vector<RowPtr> rows = tex_codec_alloc_rows(img.get(), h, pitch, TEX_TOP_DOWN, 0);
// could use cinfo->output_scanline to keep track of progress,
// but we need to count lines_left anyway (paranoia).
JSAMPARRAY row = (JSAMPARRAY)&rows[0];
@ -507,8 +504,8 @@ static Status jpg_decode_impl(DynArray* da, jpeg_decompress_struct* cinfo, Tex*
ret = WARN::TEX_INVALID_DATA;
// store image info
t->data = data;
t->dataSize = img_size;
t->data = img;
t->dataSize = imgSize;
t->ofs = 0;
t->w = w;
t->h = h;
@ -588,7 +585,7 @@ static size_t jpg_hdr_size(const u8* UNUSED(file))
}
static Status jpg_decode(DynArray* RESTRICT da, Tex* RESTRICT t)
static Status jpg_decode(rpU8 data, size_t size, Tex* RESTRICT t)
{
// contains the JPEG decompression parameters and pointers to
// working space (allocated as needed by the JPEG library).
@ -600,7 +597,7 @@ static Status jpg_decode(DynArray* RESTRICT da, Tex* RESTRICT t)
jpeg_create_decompress(&cinfo);
Status ret = jpg_decode_impl(da, &cinfo, t);
Status ret = jpg_decode_impl(data, size, &cinfo, t);
jpeg_destroy_decompress(&cinfo); // releases a "good deal" of memory

View File

@ -49,13 +49,44 @@
//
//-----------------------------------------------------------------------------
class MemoryStream
{
public:
MemoryStream(rpU8 data, size_t size)
: data(data), size(size), pos(0)
{
}
size_t RemainingSize() const
{
ASSERT(pos <= size);
return size-pos;
}
void CopyTo(rpU8 dst, size_t dstSize)
{
memcpy(dst, data+pos, dstSize);
pos += dstSize;
}
private:
rpU8 data;
size_t size;
size_t pos;
};
// pass data from PNG file in memory to libpng
static void io_read(png_struct* png_ptr, u8* data, png_size_t length)
static void io_read(png_struct* png_ptr, rpU8 data, png_size_t size)
{
DynArray* da = (DynArray*)png_get_io_ptr(png_ptr);
if(da_read(da, data, length) != 0)
png_error(png_ptr, "io_read failed");
MemoryStream* stream = (MemoryStream*)png_get_io_ptr(png_ptr);
if(stream->RemainingSize() < size)
{
png_error(png_ptr, "PNG: not enough input");
return;
}
stream->CopyTo(data, size);
}
@ -87,9 +118,9 @@ static Status png_transform(Tex* UNUSED(t), size_t UNUSED(transforms))
// split out of png_decode to simplify resource cleanup and avoid
// "dtor / setjmp interaction" warning.
static Status png_decode_impl(DynArray* da, png_structp png_ptr, png_infop info_ptr, Tex* t)
static Status png_decode_impl(MemoryStream* stream, png_structp png_ptr, png_infop info_ptr, Tex* t)
{
png_set_read_fn(png_ptr, da, io_read);
png_set_read_fn(png_ptr, stream, io_read);
// read header and determine format
png_read_info(png_ptr, info_ptr);
@ -120,10 +151,10 @@ static Status png_decode_impl(DynArray* da, png_structp png_ptr, png_infop info_
png_read_end(png_ptr, info_ptr);
// success; make sure all data was consumed.
ENSURE(da->pos == da->cur_size);
ENSURE(stream->RemainingSize() == 0);
// store image info
t->data = data;
t->data = data;
t->dataSize = img_size;
t->ofs = 0;
t->w = w;
@ -200,11 +231,10 @@ static size_t png_hdr_size(const u8* UNUSED(file))
TIMER_ADD_CLIENT(tc_png_decode);
// limitation: palette images aren't supported
static Status png_decode(DynArray* RESTRICT da, Tex* RESTRICT t)
static Status png_decode(rpU8 data, size_t size, Tex* RESTRICT t)
{
TIMER_ACCRUE(tc_png_decode);
Status ret = ERR::FAIL;
png_infop info_ptr = 0;
// allocate PNG structures; use default stderr and longjmp error handlers
@ -213,17 +243,21 @@ TIMER_ACCRUE(tc_png_decode);
WARN_RETURN(ERR::FAIL);
info_ptr = png_create_info_struct(png_ptr);
if(!info_ptr)
goto fail;
{
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
WARN_RETURN(ERR::NO_MEM);
}
// setup error handling
if(setjmp(png_jmpbuf(png_ptr)))
{
// libpng longjmps here after an error
goto fail;
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
WARN_RETURN(ERR::FAIL);
}
ret = png_decode_impl(da, png_ptr, info_ptr, t);
MemoryStream stream(data, size);
Status ret = png_decode_impl(&stream, png_ptr, info_ptr, t);
fail:
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
return ret;

View File

@ -111,12 +111,10 @@ static size_t tga_hdr_size(const u8* file)
}
// requirements: uncompressed, direct colour, bottom up
static Status tga_decode(DynArray* RESTRICT da, Tex* RESTRICT t)
// requirements: uncompressed, direct color, bottom up
static Status tga_decode(rpU8 data, size_t UNUSED(size), Tex* RESTRICT t)
{
u8* file = da->base;
TgaHeader* hdr = (TgaHeader*)file;
const TgaHeader* hdr = (const TgaHeader*)data;
const u8 type = hdr->img_type;
const size_t w = read_le16(&hdr->w);
const size_t h = read_le16(&hdr->h);