LCOV - code coverage report
Current view: top level - source/lib/tex - tex.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 261 331 78.9 %
Date: 2023-01-19 00:18:29 Functions: 20 24 83.3 %

          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 : }

Generated by: LCOV version 1.13