LCOV - code coverage report
Current view: top level - source/lib/tex - tex_png.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 59 125 47.2 %
Date: 2022-06-14 00:41:00 Functions: 6 14 42.9 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  *
       3             :  * Permission is hereby granted, free of charge, to any person obtaining
       4             :  * a copy of this software and associated documentation files (the
       5             :  * "Software"), to deal in the Software without restriction, including
       6             :  * without limitation the rights to use, copy, modify, merge, publish,
       7             :  * distribute, sublicense, and/or sell copies of the Software, and to
       8             :  * permit persons to whom the Software is furnished to do so, subject to
       9             :  * the following conditions:
      10             :  *
      11             :  * The above copyright notice and this permission notice shall be included
      12             :  * in all copies or substantial portions of the Software.
      13             :  *
      14             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      15             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      16             :  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
      17             :  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
      18             :  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
      19             :  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      20             :  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      21             :  */
      22             : 
      23             : /*
      24             :  * PNG codec using libpng.
      25             :  */
      26             : 
      27             : #include "precompiled.h"
      28             : 
      29             : #include "lib/external_libraries/png.h"
      30             : 
      31             : #include "lib/byte_order.h"
      32             : #include "tex_codec.h"
      33             : #include "lib/allocators/shared_ptr.h"
      34             : #include "lib/timer.h"
      35             : 
      36             : #if MSC_VERSION
      37             : 
      38             : // squelch "dtor / setjmp interaction" warnings.
      39             : // all attempts to resolve the underlying problem failed; apparently
      40             : // the warning is generated if setjmp is used at all in C++ mode.
      41             : // (png_*_impl have no code that would trigger ctors/dtors, nor are any
      42             : // called in their prolog/epilog code).
      43             : # pragma warning(disable: 4611)
      44             : 
      45             : #endif  // MSC_VERSION
      46             : 
      47             : 
      48             : //-----------------------------------------------------------------------------
      49             : //
      50             : //-----------------------------------------------------------------------------
      51             : 
      52             : class MemoryStream
      53             : {
      54             : public:
      55             :     MemoryStream(u8* RESTRICT data, size_t size)
      56           3 :         : data(data), size(size), pos(0)
      57             :     {
      58             :     }
      59             : 
      60           0 :     size_t RemainingSize() const
      61             :     {
      62          54 :         ASSERT(pos <= size);
      63          54 :         return size-pos;
      64             :     }
      65             : 
      66           0 :     void CopyTo(u8* RESTRICT dst, size_t dstSize)
      67             :     {
      68         102 :         memcpy(dst, data+pos, dstSize);
      69          51 :         pos += dstSize;
      70           0 :     }
      71             : 
      72             : private:
      73             :     u8* RESTRICT data;
      74             :     size_t size;
      75             :     size_t pos;
      76             : };
      77             : 
      78             : 
      79             : // pass data from PNG file in memory to libpng
      80          51 : static void io_read(png_struct* png_ptr, u8* RESTRICT data, png_size_t size)
      81             : {
      82          51 :     MemoryStream* stream = (MemoryStream*)png_get_io_ptr(png_ptr);
      83          51 :     if(stream->RemainingSize() < size)
      84             :     {
      85           0 :         png_error(png_ptr, "PNG: not enough input");
      86           0 :         return;
      87             :     }
      88             : 
      89          51 :     stream->CopyTo(data, size);
      90             : }
      91             : 
      92             : 
      93             : // write libpng output to PNG file
      94           0 : static void io_write(png_struct* png_ptr, u8* data, png_size_t length)
      95             : {
      96           0 :     DynArray* da = (DynArray*)png_get_io_ptr(png_ptr);
      97           0 :     if(da_append(da, data, length) != 0)
      98           0 :         png_error(png_ptr, "io_write failed");
      99           0 : }
     100             : 
     101             : 
     102           0 : static void io_flush(png_structp UNUSED(png_ptr))
     103             : {
     104           0 : }
     105             : 
     106             : 
     107             : 
     108             : //-----------------------------------------------------------------------------
     109             : 
     110          13 : Status TexCodecPng::transform(Tex* UNUSED(t), size_t UNUSED(transforms)) const
     111             : {
     112          13 :     return INFO::TEX_CODEC_CANNOT_HANDLE;
     113             : }
     114             : 
     115             : 
     116             : // note: it's not worth combining png_encode and png_decode, due to
     117             : // libpng read/write interface differences (grr).
     118             : 
     119             : // split out of png_decode to simplify resource cleanup and avoid
     120             : // "dtor / setjmp interaction" warning.
     121           3 : static Status png_decode_impl(MemoryStream* stream, png_structp png_ptr, png_infop info_ptr, Tex* t)
     122             : {
     123           3 :     png_set_read_fn(png_ptr, stream, io_read);
     124             : 
     125             :     // read header and determine format
     126           3 :     png_read_info(png_ptr, info_ptr);
     127           3 :     png_uint_32 w, h;
     128           3 :     int bit_depth, color_type, interlace_type;
     129           3 :     png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, &interlace_type, 0, 0);
     130             : 
     131             :     // (The following is based on GdkPixbuf's PNG image loader)
     132             : 
     133             :     // Convert the following images to 8-bit RGB/RGBA:
     134             :     // * indexed colors
     135             :     // * grayscale with alpha
     136             :     // * transparency header
     137             :     // * bit depth of 16 or less than 8
     138             :     // * interlaced
     139           3 :     if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8)
     140           0 :         png_set_expand(png_ptr);
     141           3 :     else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
     142           0 :         png_set_expand(png_ptr);
     143           3 :     else if (bit_depth < 8)
     144           0 :         png_set_expand(png_ptr);
     145             : 
     146           3 :     if (bit_depth == 16)
     147           0 :         png_set_strip_16(png_ptr);
     148           3 :     if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
     149           0 :         png_set_gray_to_rgb(png_ptr);
     150           3 :     if (interlace_type != PNG_INTERLACE_NONE)
     151           0 :         png_set_interlace_handling(png_ptr);
     152             : 
     153             :     // Update info after transformations
     154           3 :     png_read_update_info(png_ptr, info_ptr);
     155             : 
     156           3 :     png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, &interlace_type, 0, 0);
     157             : 
     158             :     // make sure format is acceptable:
     159             :     // * non-zero dimensions
     160             :     // * 8-bit depth
     161             :     // * RGB, RGBA, or grayscale
     162             :     // * 1, 3 or 4 channels
     163           3 :     if (w == 0 || h == 0)
     164           0 :         WARN_RETURN(ERR::TEX_INVALID_SIZE);
     165           3 :     if (bit_depth != 8)
     166           0 :         WARN_RETURN(ERR::TEX_NOT_8BIT_PRECISION);
     167           3 :     if (!(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY))
     168           0 :         WARN_RETURN(ERR::TEX_INVALID_COLOR_TYPE);
     169             : 
     170           3 :     const int channels = png_get_channels(png_ptr, info_ptr);
     171           3 :     if (!(channels == 3 || channels == 4 || channels == 1))
     172           0 :         WARN_RETURN(ERR::TEX_FMT_INVALID);
     173             : 
     174           3 :     const size_t pitch = png_get_rowbytes(png_ptr, info_ptr);
     175           3 :     const u32 bpp = (u32)(pitch / w * 8);
     176             : 
     177           3 :     size_t flags = 0;
     178           3 :     if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
     179           2 :         flags |= TEX_ALPHA;
     180           3 :     if (color_type == PNG_COLOR_TYPE_GRAY)
     181           0 :         flags |= TEX_GREY;
     182             : 
     183           3 :     const size_t img_size = pitch * h;
     184           3 :     std::shared_ptr<u8> data;
     185           3 :     AllocateAligned(data, img_size, g_PageSize);
     186             : 
     187           6 :     std::vector<RowPtr> rows = tex_codec_alloc_rows(data.get(), h, pitch, TEX_TOP_DOWN, 0);
     188           3 :     png_read_image(png_ptr, (png_bytepp)&rows[0]);
     189           3 :     png_read_end(png_ptr, info_ptr);
     190             : 
     191             :     // success; make sure all data was consumed.
     192           3 :     ENSURE(stream->RemainingSize() == 0);
     193             : 
     194             :     // store image info and validate
     195           3 :     return t->wrap(w,h,bpp,flags,data,0);
     196             : }
     197             : 
     198             : 
     199             : // split out of png_encode to simplify resource cleanup and avoid
     200             : // "dtor / setjmp interaction" warning.
     201           0 : static Status png_encode_impl(Tex* t, png_structp png_ptr, png_infop info_ptr, DynArray* da)
     202             : {
     203           0 :     const png_uint_32 w = (png_uint_32)t->m_Width, h = (png_uint_32)t->m_Height;
     204           0 :     const size_t pitch = w * t->m_Bpp / 8;
     205             : 
     206           0 :     int color_type;
     207           0 :     switch(t->m_Flags & (TEX_GREY|TEX_ALPHA))
     208             :     {
     209             :     case TEX_GREY|TEX_ALPHA:
     210             :         color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
     211             :         break;
     212           0 :     case TEX_GREY:
     213           0 :         color_type = PNG_COLOR_TYPE_GRAY;
     214           0 :         break;
     215           0 :     case TEX_ALPHA:
     216           0 :         color_type = PNG_COLOR_TYPE_RGB_ALPHA;
     217           0 :         break;
     218           0 :     default:
     219           0 :         color_type = PNG_COLOR_TYPE_RGB;
     220           0 :         break;
     221             :     }
     222             : 
     223           0 :     png_set_write_fn(png_ptr, da, io_write, io_flush);
     224           0 :     png_set_IHDR(png_ptr, info_ptr, w, h, 8, color_type,
     225             :         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
     226             : 
     227           0 :     u8* data = t->get_data();
     228           0 :     std::vector<RowPtr> rows = tex_codec_alloc_rows(data, h, pitch, t->m_Flags, TEX_TOP_DOWN);
     229             : 
     230             :     // PNG is native RGB.
     231           0 :     const int png_transforms = (t->m_Flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
     232             : 
     233           0 :     png_set_rows(png_ptr, info_ptr, (png_bytepp)&rows[0]);
     234           0 :     png_write_png(png_ptr, info_ptr, png_transforms, 0);
     235             : 
     236           0 :     return INFO::OK;
     237             : }
     238             : 
     239             : 
     240             : 
     241           4 : bool TexCodecPng::is_hdr(const u8* file) const
     242             : {
     243             :     // don't use png_sig_cmp, so we don't pull in libpng for
     244             :     // this check alone (it might not actually be used).
     245           4 :     return *(u32*)file == FOURCC('\x89','P','N','G');
     246             : }
     247             : 
     248             : 
     249           0 : bool TexCodecPng::is_ext(const OsPath& extension) const
     250             : {
     251           0 :     return extension == L".png";
     252             : }
     253             : 
     254             : 
     255           6 : size_t TexCodecPng::hdr_size(const u8* UNUSED(file)) const
     256             : {
     257           6 :     return 0;   // libpng returns decoded image data; no header
     258             : }
     259             : 
     260           0 : static void user_warning_fn(png_structp UNUSED(png_ptr), png_const_charp warning_msg)
     261             : {
     262             :     // Suppress this warning because it's useless and occurs on a large number of files
     263             :     // see http://trac.wildfiregames.com/ticket/2184
     264           0 :     if (strcmp(warning_msg, "iCCP: known incorrect sRGB profile") == 0)
     265             :         return;
     266           0 :     debug_printf("libpng warning: %s\n", warning_msg);
     267             : }
     268             : 
     269             : TIMER_ADD_CLIENT(tc_png_decode);
     270             : 
     271             : // limitation: palette images aren't supported
     272           3 : Status TexCodecPng::decode(u8* RESTRICT data, size_t size, Tex* RESTRICT t) const
     273             : {
     274           6 :     TIMER_ACCRUE(tc_png_decode);
     275             : 
     276           3 :     png_infop info_ptr = 0;
     277             : 
     278             :     // allocate PNG structures; use default stderr and longjmp error handler, use custom
     279             :     // warning handler to filter out useless messages
     280           3 :     png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, user_warning_fn);
     281           3 :     if(!png_ptr)
     282             :         return ERR::FAIL;
     283           3 :     info_ptr = png_create_info_struct(png_ptr);
     284           3 :     if(!info_ptr)
     285             :     {
     286           0 :         png_destroy_read_struct(&png_ptr, &info_ptr, 0);
     287             :         return ERR::NO_MEM;
     288             :     }
     289             :     // setup error handling
     290           3 :     if(setjmp(png_jmpbuf(png_ptr)))
     291             :     {
     292             :         // libpng longjmps here after an error
     293           0 :         png_destroy_read_struct(&png_ptr, &info_ptr, 0);
     294             :         return ERR::FAIL;
     295             :     }
     296             : 
     297           6 :     MemoryStream stream(data, size);
     298           3 :     Status ret = png_decode_impl(&stream, png_ptr, info_ptr, t);
     299             : 
     300           3 :     png_destroy_read_struct(&png_ptr, &info_ptr, 0);
     301             : 
     302             :     return ret;
     303             : }
     304             : 
     305             : 
     306             : // limitation: palette images aren't supported
     307           0 : Status TexCodecPng::encode(Tex* RESTRICT t, DynArray* RESTRICT da) const
     308             : {
     309           0 :     png_infop info_ptr = 0;
     310             : 
     311             :     // allocate PNG structures; use default stderr and longjmp error handlers
     312           0 :     png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
     313           0 :     if(!png_ptr)
     314           0 :         WARN_RETURN(ERR::FAIL);
     315           0 :     info_ptr = png_create_info_struct(png_ptr);
     316           0 :     if(!info_ptr)
     317             :     {
     318           0 :         png_destroy_write_struct(&png_ptr, &info_ptr);
     319           0 :         WARN_RETURN(ERR::NO_MEM);
     320             :     }
     321             :     // setup error handling
     322           0 :     if(setjmp(png_jmpbuf(png_ptr)))
     323             :     {
     324             :         // libpng longjmps here after an error
     325           0 :         png_destroy_write_struct(&png_ptr, &info_ptr);
     326           0 :         WARN_RETURN(ERR::FAIL);
     327             :     }
     328             : 
     329           0 :     Status ret = png_encode_impl(t, png_ptr, info_ptr, da);
     330             : 
     331           0 :     png_destroy_write_struct(&png_ptr, &info_ptr);
     332             : 
     333             :     return ret;
     334             : }

Generated by: LCOV version 1.13