From 65ad57a4480caf203a072ab8442d6af56cdbc336 Mon Sep 17 00:00:00 2001 From: Jupeyy Date: Sat, 11 Jun 2022 09:09:42 +0200 Subject: [PATCH] Change from pnglite to libpng for PNG reading This is desirable mainly because libpng is maintained and pnglite is not. pnglite was last updated in 2007 (15 years ago) and probably has a lot of security vulnerabilities. libpng is an actively maintained library also used by browsers like Firefox or Chromium, so it's less likely to contain security vulnerabilities, also it's more likely to be packaged by Linux distributions. --- .github/workflows/build.yaml | 2 +- CMakeLists.txt | 24 +- Dockerfile | 2 +- README.md | 6 +- cmake/FindPNG.cmake | 46 ++ cmake/FindPnglite.cmake | 46 -- ddnet-libs | 2 +- src/engine/client/graphics_threaded.cpp | 103 ++- src/engine/external/pnglite/VERSION.txt | 1 - src/engine/external/pnglite/pnglite.c | 877 ------------------------ src/engine/external/pnglite/pnglite.h | 227 ------ src/engine/shared/image_loader.cpp | 287 ++++++++ src/engine/shared/image_loader.h | 28 + src/tools/dilate.cpp | 109 +-- src/tools/map_convert_07.cpp | 63 +- src/tools/map_extract.cpp | 25 +- src/tools/map_replace_image.cpp | 72 +- 17 files changed, 554 insertions(+), 1366 deletions(-) create mode 100644 cmake/FindPNG.cmake delete mode 100644 cmake/FindPnglite.cmake delete mode 100644 src/engine/external/pnglite/VERSION.txt delete mode 100644 src/engine/external/pnglite/pnglite.c delete mode 100644 src/engine/external/pnglite/pnglite.h create mode 100644 src/engine/shared/image_loader.cpp create mode 100644 src/engine/shared/image_loader.h diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 75235ac8b..3d649e3a8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -58,7 +58,7 @@ jobs: run: | sudo apt-get update -y sudo apt-get upgrade -y - sudo apt-get install pkg-config cmake ninja-build libfreetype6-dev libnotify-dev libsdl2-dev libsqlite3-dev libvulkan-dev glslang-tools spirv-tools libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libx264-dev valgrind -y + sudo apt-get install pkg-config cmake ninja-build libfreetype6-dev libnotify-dev libsdl2-dev libsqlite3-dev libvulkan-dev glslang-tools spirv-tools libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libx264-dev libpng-dev valgrind -y - name: Prepare Linux (non-fancy) if: ${{ contains(matrix.os, 'ubuntu') && !matrix.fancy }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f1b515d3..da708cefe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -545,7 +545,7 @@ endif() find_package(Ogg) find_package(Opus) find_package(Opusfile) -find_package(Pnglite) +find_package(PNG) find_package(PythonInterp 3) find_package(SDL2) find_package(SQLite3) @@ -631,7 +631,7 @@ show_dependency_status("Ogg" OGG) show_dependency_status("OpenSSL Crypto" CRYPTO) show_dependency_status("Opus" OPUS) show_dependency_status("Opusfile" OPUSFILE) -show_dependency_status("Pnglite" PNGLITE) +show_dependency_status("PNG" PNG) show_dependency_status("PythonInterp" PYTHONINTERP) show_dependency_status("SDL2" SDL2) show_dependency_status("SQLite3" SQLite3) @@ -680,6 +680,9 @@ if(DISCORD_DYNAMIC) message(SEND_ERROR "You must enable the DISCORD flag if you want to link the Discord SDK") endif() endif() +if(NOT(PNG_FOUND)) + message(SEND_ERROR "You must install libpng to compile DDNet") +endif() if(CLIENT AND NOT(FREETYPE_FOUND)) message(SEND_ERROR "You must install Freetype to compile the ${CMAKE_PROJECT_NAME} client") endif() @@ -1562,6 +1565,7 @@ set(COPY_FILES ${CURL_COPY_FILES} ${FREETYPE_COPY_FILES} ${OPUSFILE_COPY_FILES} + ${PNG_COPY_FILES} ${SDL2_COPY_FILES} ${SQLite3_COPY_FILES} ${FFMPEG_COPY_FILES} @@ -1755,6 +1759,8 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared http.h huffman.cpp huffman.h + image_loader.cpp + image_loader.h image_manipulation.cpp image_manipulation.h jobs.cpp @@ -1868,6 +1874,7 @@ set(LIBS ${CURL_LIBRARIES} ${SQLite3_LIBRARIES} ${WEBSOCKETS_LIBRARIES} + ${PNG_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS} # Add pthreads (on non-Windows) at the end, so that other libraries can depend @@ -1877,6 +1884,7 @@ set(LIBS # Targets add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_UUID_SHARED} ${BASE}) +target_include_directories(engine-shared PRIVATE ${PNG_INCLUDE_DIRS}) add_library(game-shared EXCLUDE_FROM_ALL OBJECT ${GAME_SHARED} ${GAME_GENERATED_SHARED}) list(APPEND TARGETS_OWN engine-shared game-shared) @@ -2102,14 +2110,14 @@ if(CLIENT) ) set(CLIENT_SRC ${ENGINE_CLIENT} ${PLATFORM_CLIENT} ${GAME_CLIENT} ${GAME_EDITOR} ${GAME_GENERATED_CLIENT}) - set(DEPS_CLIENT ${DEPS} ${GLEW_DEP} ${PNGLITE_DEP} ${WAVPACK_DEP}) + set(DEPS_CLIENT ${DEPS} ${GLEW_DEP} ${WAVPACK_DEP}) # Libraries set(LIBS_CLIENT ${LIBS} ${FREETYPE_LIBRARIES} ${GLEW_LIBRARIES} - ${PNGLITE_LIBRARIES} + ${PNG_LIBRARIES} ${SDL2_LIBRARIES} ${WAVPACK_LIBRARIES} ${FFMPEG_LIBRARIES} @@ -2185,7 +2193,7 @@ if(CLIENT) ${OGG_INCLUDE_DIRS} ${OPUSFILE_INCLUDE_DIRS} ${OPUS_INCLUDE_DIRS} - ${PNGLITE_INCLUDE_DIRS} + ${PNG_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS} ${WAVPACK_INCLUDE_DIRS} ${FFMPEG_INCLUDE_DIRS} @@ -2343,6 +2351,7 @@ if(SERVER) # Libraries set(LIBS_SERVER ${LIBS} + ${PNG_LIBRARIES} ${MYSQL_LIBRARIES} ${TARGET_ANTIBOT} ${MINIUPNPC_LIBRARIES} @@ -2361,6 +2370,7 @@ if(SERVER) $ ) target_link_libraries(${TARGET_SERVER} ${LIBS_SERVER}) + target_include_directories(${TARGET_SERVER} PRIVATE ${PNG_INCLUDE_DIRS}) list(APPEND TARGETS_OWN ${TARGET_SERVER}) list(APPEND TARGETS_LINK ${TARGET_SERVER}) @@ -2413,9 +2423,7 @@ if(TOOLS) set(TOOL_DEPS ${DEPS}) set(TOOL_LIBS ${LIBS}) if(TOOL MATCHES "^(dilate|map_convert_07|map_optimize|map_extract|map_replace_image)$") - list(APPEND TOOL_DEPS ${PNGLITE_DEP}) - list(APPEND TOOL_LIBS ${PNGLITE_LIBRARIES}) - list(APPEND TOOL_INCLUDE_DIRS ${PNGLITE_INCLUDE_DIRS}) + list(APPEND TOOL_INCLUDE_DIRS ${PNG_INCLUDE_DIRS}) endif() if(TOOL MATCHES "^config_") list(APPEND EXTRA_TOOL_SRC "src/tools/config_common.h") diff --git a/Dockerfile b/Dockerfile index 3957a4b0f..c1061340e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update && apt-get install -y mingw-w64 \ libglew-dev \ libogg-dev \ libopus-dev \ - libpnglite-dev \ + libpng-dev \ libwavpack-dev \ libopusfile-dev \ libsdl2-dev \ diff --git a/README.md b/README.md index 1739207c6..17866a874 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Dependencies on Linux / macOS You can install the required libraries on your system, `touch CMakeLists.txt` and CMake will use the system-wide libraries by default. You can install all required dependencies and CMake on Debian or Ubuntu like this: - sudo apt install build-essential cmake git google-mock libcurl4-openssl-dev libssl-dev libfreetype6-dev libglew-dev libnotify-dev libogg-dev libopus-dev libopusfile-dev libpnglite-dev libsdl2-dev libsqlite3-dev libwavpack-dev python libx264-dev libavfilter-dev libavdevice-dev libavformat-dev libavcodec-extra libavutil-dev libvulkan-dev glslang-tools spirv-tools + sudo apt install build-essential cmake git google-mock libcurl4-openssl-dev libssl-dev libfreetype6-dev libglew-dev libnotify-dev libogg-dev libopus-dev libopusfile-dev libpnglite-dev libsdl2-dev libsqlite3-dev libwavpack-dev python libx264-dev libavfilter-dev libavdevice-dev libavformat-dev libavcodec-extra libavutil-dev libvulkan-dev glslang-tools spirv-tools libpng-dev On older distributions like Ubuntu 18.04 don't install `google-mock`, but instead set `-DDOWNLOAD_GTEST=ON` when building to get a more recent gtest/gmock version. @@ -44,9 +44,7 @@ Or on CentOS, RedHat and AlmaLinux like this: Or on Arch Linux like this: - sudo pacman -S --needed base-devel cmake curl freetype2 git glew gmock libnotify opusfile python sdl2 sqlite wavpack x264 ffmpeg vulkan-icd-loader vulkan-headers glslang spirv-tools - -There is an [AUR package for pnglite](https://aur.archlinux.org/packages/pnglite/). For instructions on installing it, see [AUR packages installation instructions on ArchWiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages). + sudo pacman -S --needed base-devel cmake curl freetype2 git glew gmock libnotify opusfile python sdl2 sqlite wavpack x264 ffmpeg vulkan-icd-loader vulkan-headers glslang spirv-tools libpng On macOS you can use [homebrew](https://brew.sh/) to install build dependencies like this: diff --git a/cmake/FindPNG.cmake b/cmake/FindPNG.cmake new file mode 100644 index 000000000..39f72127b --- /dev/null +++ b/cmake/FindPNG.cmake @@ -0,0 +1,46 @@ +if(NOT PREFER_BUNDLED_LIBS) + set(CMAKE_MODULE_PATH ${ORIGINAL_CMAKE_MODULE_PATH}) + find_package(PNG) + set(CMAKE_MODULE_PATH ${OWN_CMAKE_MODULE_PATH}) + if(PNG_FOUND) + set(PNG_BUNDLED OFF) + set(PNG_DEP) + endif() +endif() + +if(NOT PNG_FOUND) + set_extra_dirs_lib(PNG png) + find_library(PNG_LIBRARY + NAMES png16 libpng16 libpng16-16 png16-16 + HINTS ${HINTS_PNG_LIBDIR} ${PC_PNG_LIBDIR} ${PC_PNG_LIBRARY_DIRS} + PATHS ${PATHS_PNG_LIBDIR} + ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} + ) + + set_extra_dirs_include(PNG png "${PNG_LIBRARY}") + find_path(PNG_INCLUDEDIR + NAMES png.h + HINTS ${HINTS_PNG_INCLUDEDIR} ${PC_PNG_INCLUDEDIR} ${PC_PNG_INCLUDE_DIRS} + PATHS ${PATHS_PNG_INCLUDEDIR} + ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} + ) + + mark_as_advanced(PNG_LIBRARY PNG_INCLUDEDIR) + + if(PNG_LIBRARY AND PNG_INCLUDEDIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(PNG DEFAULT_MSG PNG_LIBRARY PNG_INCLUDEDIR) + + set(PNG_LIBRARIES ${PNG_LIBRARY}) + set(PNG_INCLUDE_DIRS ${PNG_INCLUDEDIR}) + endif() +endif() + +if(PNG_FOUND) + is_bundled(PNG_BUNDLED "${PNG_LIBRARY}") + if(PNG_BUNDLED AND TARGET_OS STREQUAL "windows") + set(PNG_COPY_FILES "${EXTRA_PNG_LIBDIR}/libpng16-16.dll") + else() + set(PNG_COPY_FILES) + endif() +endif() diff --git a/cmake/FindPnglite.cmake b/cmake/FindPnglite.cmake deleted file mode 100644 index 6a877c257..000000000 --- a/cmake/FindPnglite.cmake +++ /dev/null @@ -1,46 +0,0 @@ -if(NOT PREFER_BUNDLED_LIBS) - if(NOT CMAKE_CROSSCOMPILING) - find_package(PkgConfig QUIET) - pkg_check_modules(PC_PNGLITE pnglite) - endif() - - find_library(PNGLITE_LIBRARY - NAMES pnglite - HINTS ${PC_PNGLITE_LIBDIR} ${PC_PNGLITE_LIBRARY_DIRS} - ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} - ) - find_path(PNGLITE_INCLUDEDIR - NAMES pnglite.h - HINTS ${PC_PNGLITE_INCLUDEDIR} ${PC_PNGLITE_INCLUDE_DIRS} - ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} - ) - - mark_as_advanced(PNGLITE_LIBRARY PNGLITE_INCLUDEDIR) - - if(PNGLITE_LIBRARY AND PNGLITE_INCLUDEDIR) - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Pnglite DEFAULT_MSG PNGLITE_LIBRARY PNGLITE_INCLUDEDIR) - - set(PNGLITE_LIBRARIES ${PNGLITE_LIBRARY}) - set(PNGLITE_INCLUDE_DIRS ${PNGLITE_INCLUDEDIR}) - set(PNGLITE_BUNDLED OFF) - endif() -endif() - -if(NOT PNGLITE_FOUND) - set(PNGLITE_SRC_DIR src/engine/external/pnglite) - set_src(PNGLITE_SRC GLOB ${PNGLITE_SRC_DIR} pnglite.c pnglite.h) - add_library(pnglite EXCLUDE_FROM_ALL OBJECT ${PNGLITE_SRC}) - list(APPEND TARGETS_DEP pnglite) - - set(PNGLITE_INCLUDEDIR ${PNGLITE_SRC_DIR}) - target_include_directories(pnglite PRIVATE ${ZLIB_INCLUDE_DIRS}) - - set(PNGLITE_DEP $) - set(PNGLITE_INCLUDE_DIRS ${PNGLITE_INCLUDEDIR}) - set(PNGLITE_LIBRARIES) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Pnglite DEFAULT_MSG PNGLITE_INCLUDEDIR) - set(PNGLITE_BUNDLED ON) -endif() diff --git a/ddnet-libs b/ddnet-libs index 02f8b2e0f..8d38bd56b 160000 --- a/ddnet-libs +++ b/ddnet-libs @@ -1 +1 @@ -Subproject commit 02f8b2e0f3436347820cd36f35455f632ec8c590 +Subproject commit 8d38bd56b441fbf473d53c65499dd8f1f3071b66 diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 928aa1a90..0198ce075 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -10,7 +10,7 @@ #include -#include +#include #include #include @@ -135,8 +135,6 @@ CGraphics_Threaded::CGraphics_Threaded() m_RenderEnable = true; m_DoScreenshot = false; - - png_init(0, 0); } void CGraphics_Threaded::ClipEnable(int x, int y, int w, int h) @@ -627,54 +625,49 @@ bool CGraphics_Threaded::UpdateTextTexture(CTextureHandle TextureID, int x, int int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) { char aCompleteFilename[IO_MAX_PATH_LENGTH]; - - // open file for reading IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename)); - if(!File) + if(File) + { + io_seek(File, 0, IOSEEK_END); + unsigned int FileSize = io_tell(File); + io_seek(File, 0, IOSEEK_START); + + TImageByteBuffer ByteBuffer; + SImageByteBuffer ImageByteBuffer(&ByteBuffer); + + ByteBuffer.resize(FileSize); + io_read(File, &ByteBuffer.front(), FileSize); + + io_close(File); + + uint8_t *pImgBuffer = NULL; + EImageFormat ImageFormat; + if(::LoadPNG(ImageByteBuffer, pFilename, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat)) + { + pImg->m_pData = pImgBuffer; + + if(ImageFormat == IMAGE_FORMAT_RGB) // ignore_convention + pImg->m_Format = CImageInfo::FORMAT_RGB; + else if(ImageFormat == IMAGE_FORMAT_RGBA) // ignore_convention + pImg->m_Format = CImageInfo::FORMAT_RGBA; + else + { + free(pImgBuffer); + return 0; + } + } + else + { + dbg_msg("game/png", "image had unsupported image format. filename='%s'", pFilename); + return 0; + } + } + else { dbg_msg("game/png", "failed to open file. filename='%s'", pFilename); return 0; } - png_t Png; - int Error = png_open_read(&Png, 0, File); - if(Error != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to open file. filename='%s', pnglite: %s", aCompleteFilename, png_error_string(Error)); - io_close(File); - return 0; - } - - if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) - { - dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); - io_close(File); - return 0; - } - - unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * Png.bpp); - Error = png_get_data(&Png, pBuffer); - if(Error != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to read image. filename='%s', pnglite: %s", aCompleteFilename, png_error_string(Error)); - free(pBuffer); - io_close(File); - return 0; - } - io_close(File); - - pImg->m_Width = Png.width; - pImg->m_Height = Png.height; - if(Png.color_type == PNG_TRUECOLOR) - pImg->m_Format = CImageInfo::FORMAT_RGB; - else if(Png.color_type == PNG_TRUECOLOR_ALPHA) - pImg->m_Format = CImageInfo::FORMAT_RGBA; - else - { - free(pBuffer); - return 0; - } - pImg->m_pData = pBuffer; return 1; } @@ -806,21 +799,21 @@ bool CGraphics_Threaded::ScreenshotDirect() { // find filename char aWholePath[1024]; - png_t Png; IOHANDLE File = m_pStorage->OpenFile(m_aScreenshotName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath)); - if(!File) + if(File) { - dbg_msg("game/screenshot", "failed to open file. filename='%s'", aWholePath); - } - else - { - // save png char aBuf[256]; str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA(1.0f, 0.6f, 0.3f, 1.0f)); - png_open_write(&Png, 0, File); - png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Image.m_pData); + + // save png + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA{1.0f, 0.6f, 0.3f, 1.0f}); + + TImageByteBuffer ByteBuffer; + SImageByteBuffer ImageByteBuffer(&ByteBuffer); + + if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)Image.m_pData, ImageByteBuffer, Image.m_Width, Image.m_Height)) + io_write(File, &ByteBuffer.front(), ByteBuffer.size()); io_close(File); } diff --git a/src/engine/external/pnglite/VERSION.txt b/src/engine/external/pnglite/VERSION.txt deleted file mode 100644 index 44a7df273..000000000 --- a/src/engine/external/pnglite/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -0.1.17 \ No newline at end of file diff --git a/src/engine/external/pnglite/pnglite.c b/src/engine/external/pnglite/pnglite.c deleted file mode 100644 index 3de4a8d4e..000000000 --- a/src/engine/external/pnglite/pnglite.c +++ /dev/null @@ -1,877 +0,0 @@ -/* pnglite.c - pnglite library - For conditions of distribution and use, see copyright notice in pnglite.h -*/ -#define DO_CRC_CHECKS 1 -#define USE_ZLIB 1 - -#if USE_ZLIB -#include -#else -#include "zlite.h" -#endif - -#include -#include -#include -#include "pnglite.h" - - - -static png_alloc_t png_alloc; -static png_free_t png_free; - -static size_t file_read(png_t* png, void* out, size_t size, size_t numel) -{ - size_t result; - if(png->read_fun) - { - result = png->read_fun(out, size, numel, png->user_pointer); - } - else - { - if(!out) - { - result = fseek(png->user_pointer, (long)(size*numel), SEEK_CUR); - } - else - { - result = fread(out, size, numel, png->user_pointer); - } - } - - return result; -} - -static size_t file_write(png_t* png, void* p, size_t size, size_t numel) -{ - size_t result; - - if(png->write_fun) - { - result = png->write_fun(p, size, numel, png->user_pointer); - } - else - { - result = fwrite(p, size, numel, png->user_pointer); - } - - return result; -} - -static int file_read_ul(png_t* png, unsigned *out) -{ - unsigned char buf[4]; - - if(file_read(png, buf, 1, 4) != 4) - return PNG_FILE_ERROR; - - *out = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; - - return PNG_NO_ERROR; -} - -static int file_write_ul(png_t* png, unsigned in) -{ - unsigned char buf[4]; - - buf[0] = (in>>24) & 0xff; - buf[1] = (in>>16) & 0xff; - buf[2] = (in>>8) & 0xff; - buf[3] = (in) & 0xff; - - if(file_write(png, buf, 1, 4) != 4) - return PNG_FILE_ERROR; - - return PNG_NO_ERROR; -} - - -static unsigned get_ul(unsigned char* buf) -{ - unsigned result; - unsigned char foo[4]; - - memcpy(foo, buf, 4); - - result = (foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | foo[3]; - - return result; -} - -static unsigned set_ul(unsigned char* buf, unsigned in) -{ - buf[0] = (in>>24) & 0xff; - buf[1] = (in>>16) & 0xff; - buf[2] = (in>>8) & 0xff; - buf[3] = (in) & 0xff; - - return PNG_NO_ERROR; -} - -int png_init(png_alloc_t pngalloc, png_free_t pngfree) -{ - if(pngalloc) - png_alloc = pngalloc; - else - png_alloc = (png_alloc_t)&malloc; - - if(pngfree) - png_free = pngfree; - else - png_free = &free; - - return PNG_NO_ERROR; -} - -static int png_get_bpp(png_t* png) -{ - int bpp; - - switch(png->color_type) - { - case PNG_GREYSCALE: - bpp = 1; break; - case PNG_TRUECOLOR: - bpp = 3; break; - case PNG_INDEXED: - bpp = 1; break; - case PNG_GREYSCALE_ALPHA: - bpp = 2; break; - case PNG_TRUECOLOR_ALPHA: - bpp = 4; break; - default: - return PNG_FILE_ERROR; - } - - bpp *= png->depth/8; - - return bpp; -} - -static int png_read_ihdr(png_t* png) -{ - unsigned length; -#if DO_CRC_CHECKS - unsigned orig_crc; - unsigned calc_crc; -#endif - unsigned char ihdr[13+4]; /* length should be 13, make room for type (IHDR) */ - - file_read_ul(png, &length); - - if(length != 13) - { - printf("%d\n", length); - return PNG_CRC_ERROR; - } - - if(file_read(png, ihdr, 1, 13+4) != 13+4) - return PNG_EOF_ERROR; -#if DO_CRC_CHECKS - file_read_ul(png, &orig_crc); - - calc_crc = crc32(0L, 0, 0); - calc_crc = crc32(calc_crc, ihdr, 13+4); - - if(orig_crc != calc_crc) - return PNG_CRC_ERROR; -#else - file_read_ul(png); -#endif - - png->width = get_ul(ihdr+4); - png->height = get_ul(ihdr+8); - png->depth = ihdr[12]; - png->color_type = ihdr[13]; - png->compression_method = ihdr[14]; - png->filter_method = ihdr[15]; - png->interlace_method = ihdr[16]; - - if(png->color_type == PNG_INDEXED) - return PNG_NOT_SUPPORTED; - - if(png->depth != 8 && png->depth != 16) - return PNG_NOT_SUPPORTED; - - if(png->interlace_method) - return PNG_NOT_SUPPORTED; - - return PNG_NO_ERROR; -} - -static int png_write_ihdr(png_t* png) -{ - unsigned char ihdr[13+4]; - unsigned char *p = ihdr; - unsigned crc; - - file_write(png, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 1, 8); - - file_write_ul(png, 13); - - *p = 'I'; p++; - *p = 'H'; p++; - *p = 'D'; p++; - *p = 'R'; p++; - set_ul(p, png->width); p+=4; - set_ul(p, png->height); p+=4; - *p = png->depth; p++; - *p = png->color_type; p++; - *p = 0; p++; - *p = 0; p++; - *p = 0; p++; - - file_write(png, ihdr, 1, 13+4); - - crc = crc32(0L, 0, 0); - crc = crc32(crc, ihdr, 13+4); - - file_write_ul(png, crc); - - return PNG_NO_ERROR; -} - -void png_print_info(png_t* png) -{ - printf("PNG INFO:\n"); - printf("\twidth:\t\t%d\n", png->width); - printf("\theight:\t\t%d\n", png->height); - printf("\tdepth:\t\t%d\n", png->depth); - printf("\tcolor:\t\t"); - - switch(png->color_type) - { - case PNG_GREYSCALE: printf("greyscale\n"); break; - case PNG_TRUECOLOR: printf("truecolor\n"); break; - case PNG_INDEXED: printf("palette\n"); break; - case PNG_GREYSCALE_ALPHA: printf("greyscale with alpha\n"); break; - case PNG_TRUECOLOR_ALPHA: printf("truecolor with alpha\n"); break; - default: printf("unknown, this is not good\n"); break; - } - - printf("\tcompression:\t%s\n", png->compression_method?"unknown, this is not good":"inflate/deflate"); - printf("\tfilter:\t\t%s\n", png->filter_method?"unknown, this is not good":"adaptive"); - printf("\tinterlace:\t%s\n", png->interlace_method?"interlace":"no interlace"); -} - -int png_open_read(png_t* png, png_read_callback_t read_fun, void* user_pointer) -{ - char header[8]; - int result; - - png->read_fun = read_fun; - png->write_fun = 0; - png->user_pointer = user_pointer; - - if(!read_fun && !user_pointer) - return PNG_WRONG_ARGUMENTS; - - if(file_read(png, header, 1, 8) != 8) - return PNG_EOF_ERROR; - - if(memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) != 0) - return PNG_HEADER_ERROR; - - result = png_read_ihdr(png); - - png->bpp = (unsigned char)png_get_bpp(png); - - return result; -} - -int png_open_write(png_t* png, png_write_callback_t write_fun, void* user_pointer) -{ - png->write_fun = write_fun; - png->read_fun = 0; - png->user_pointer = user_pointer; - - if(!write_fun && !user_pointer) - return PNG_WRONG_ARGUMENTS; - - return PNG_NO_ERROR; -} - -int png_open(png_t* png, png_read_callback_t read_fun, void* user_pointer) -{ - return png_open_read(png, read_fun, user_pointer); -} - -int png_open_file_read(png_t *png, const char* filename) -{ - FILE* fp = fopen(filename, "rb"); - - if(!fp) - return PNG_FILE_ERROR; - - return png_open_read(png, 0, fp); -} - -int png_open_file_write(png_t *png, const char* filename) -{ - FILE* fp = fopen(filename, "wb"); - - if(!fp) - return PNG_FILE_ERROR; - - return png_open_write(png, 0, fp); -} - -int png_open_file(png_t *png, const char* filename) -{ - return png_open_file_read(png, filename); -} - -int png_close_file(png_t* png) -{ - fclose(png->user_pointer); - - return PNG_NO_ERROR; -} - -static int png_init_deflate(png_t* png, unsigned char* data, int datalen) -{ - z_stream *stream; - png->zs = png_alloc(sizeof(z_stream)); - - stream = png->zs; - - if(!stream) - return PNG_MEMORY_ERROR; - - memset(stream, 0, sizeof(z_stream)); - - if(deflateInit(stream, Z_DEFAULT_COMPRESSION) != Z_OK) - return PNG_ZLIB_ERROR; - - stream->next_in = data; - stream->avail_in = datalen; - - return PNG_NO_ERROR; -} - -static int png_init_inflate(png_t* png) -{ -#if USE_ZLIB - z_stream *stream; - png->zs = png_alloc(sizeof(z_stream)); -#else - zl_stream *stream; - png->zs = png_alloc(sizeof(zl_stream)); -#endif - - stream = png->zs; - - if(!stream) - return PNG_MEMORY_ERROR; - - - -#if USE_ZLIB - memset(stream, 0, sizeof(z_stream)); - if(inflateInit(stream) != Z_OK) - return PNG_ZLIB_ERROR; -#else - memset(stream, 0, sizeof(zl_stream)); - if(z_inflateInit(stream) != Z_OK) - return PNG_ZLIB_ERROR; -#endif - - stream->next_out = png->png_data; - stream->avail_out = png->png_datalen; - - return PNG_NO_ERROR; -} - -static int png_end_deflate(png_t* png) -{ - z_stream *stream = png->zs; - - if(!stream) - return PNG_MEMORY_ERROR; - - deflateEnd(stream); - - png_free(png->zs); - - return PNG_NO_ERROR; -} - -static int png_end_inflate(png_t* png) -{ -#if USE_ZLIB - z_stream *stream = png->zs; -#else - zl_stream *stream = png->zs; -#endif - - if(!stream) - return PNG_MEMORY_ERROR; - -#if USE_ZLIB - if(inflateEnd(stream) != Z_OK) -#else - if(z_inflateEnd(stream) != Z_OK) -#endif - { - printf("ZLIB says: %s\n", stream->msg); - return PNG_ZLIB_ERROR; - } - - png_free(png->zs); - - return PNG_NO_ERROR; -} - -static int png_inflate(png_t* png, char* data, int len) -{ - int result; -#if USE_ZLIB - z_stream *stream = png->zs; -#else - zl_stream *stream = png->zs; -#endif - - if(!stream) - return PNG_MEMORY_ERROR; - - stream->next_in = (unsigned char*)data; - stream->avail_in = len; - -#if USE_ZLIB - result = inflate(stream, Z_SYNC_FLUSH); -#else - result = z_inflate(stream); -#endif - - if(result != Z_STREAM_END && result != Z_OK) - { - printf("%s\n", stream->msg); - return PNG_ZLIB_ERROR; - } - - if(stream->avail_in != 0) - return PNG_ZLIB_ERROR; - - return PNG_NO_ERROR; -} - -static int png_deflate(png_t* png, char* outdata, int outlen, int *outwritten) -{ - int result; - - z_stream *stream = png->zs; - - - if(!stream) - return PNG_MEMORY_ERROR; - - stream->next_out = (unsigned char*)outdata; - stream->avail_out = outlen; - - result = deflate(stream, Z_SYNC_FLUSH); - - *outwritten = outlen - stream->avail_out; - - if(result != Z_STREAM_END && result != Z_OK) - { - printf("%s\n", stream->msg); - return PNG_ZLIB_ERROR; - } - - return result; -} - -static int png_write_idats(png_t* png, unsigned char* data) -{ - unsigned char *chunk; - unsigned long written; - unsigned long crc; - unsigned size = png->width * png->height * png->bpp + png->height; - - (void)png_init_deflate; - (void)png_end_deflate; - (void)png_deflate; - - chunk = png_alloc(size+8); - memcpy(chunk, "IDAT", 4); - - written = size; - compress(chunk+4, &written, data, size); - - crc = crc32(0L, Z_NULL, 0); - crc = crc32(crc, chunk, written+4); - set_ul(chunk+written+4, crc); - file_write_ul(png, written); - file_write(png, chunk, 1, written+8); - png_free(chunk); - - file_write_ul(png, 0); - file_write(png, "IEND", 1, 4); - crc = crc32(0L, (const unsigned char *)"IEND", 4); - file_write_ul(png, crc); - - return PNG_NO_ERROR; -} - -static int png_read_idat(png_t* png, unsigned firstlen) -{ - unsigned type = 0; - char *chunk; - int result; - unsigned length = firstlen; - unsigned old_len = length; - -#if DO_CRC_CHECKS - unsigned orig_crc; - unsigned calc_crc; -#endif - - chunk = png_alloc(firstlen); - - result = png_init_inflate(png); - - if(result != PNG_NO_ERROR) - { - png_end_inflate(png); - png_free(chunk); - return result; - } - - do - { - if(file_read(png, chunk, 1, length) != length) - { - png_end_inflate(png); - png_free(chunk); - return PNG_FILE_ERROR; - } - -#if DO_CRC_CHECKS - calc_crc = crc32(0L, Z_NULL, 0); - calc_crc = crc32(calc_crc, (unsigned char*)"IDAT", 4); - calc_crc = crc32(calc_crc, (unsigned char*)chunk, length); - - file_read_ul(png, &orig_crc); - - if(orig_crc != calc_crc) - { - result = PNG_CRC_ERROR; - break; - } -#else - file_read_ul(png); -#endif - - result = png_inflate(png, chunk, length); - - if(result != PNG_NO_ERROR) break; - - file_read_ul(png, &length); - - if(length > old_len) - { - png_free(chunk); - chunk = png_alloc(length); - old_len = length; - } - - if(file_read(png, &type, 1, 4) != 4) - { - result = PNG_FILE_ERROR; - break; - } - - }while(type == *(unsigned int*)"IDAT"); - - if(type == *(unsigned int*)"IEND") - result = PNG_DONE; - - png_free(chunk); - png_end_inflate(png); - - return result; -} - -static int png_process_chunk(png_t* png) -{ - int result = PNG_NO_ERROR; - unsigned type; - unsigned length; - - file_read_ul(png, &length); - - if(file_read(png, &type, 1, 4) != 4) - return PNG_FILE_ERROR; - - if(type == *(unsigned int*)"IDAT") /* if we found an idat, all other idats should be followed with no other chunks in between */ - { - png->png_datalen = png->width * png->height * png->bpp + png->height; - png->png_data = png_alloc(png->png_datalen); - - if(!png->png_data) - return PNG_MEMORY_ERROR; - - return png_read_idat(png, length); - } - else if(type == *(unsigned int*)"IEND") - { - return PNG_DONE; - } - else - { - file_read(png, 0, 1, length + 4); /* unknown chunk */ - } - - return result; -} - -static void png_filter_sub(int stride, unsigned char* in, unsigned char* out, int len) -{ - int i; - unsigned char a = 0; - - for(i = 0; i < len; i++) - { - if(i >= stride) - a = out[i - stride]; - - out[i] = in[i] + a; - } -} - -static void png_filter_up(int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len) -{ - int i; - - if(prev_line) - { - for(i = 0; i < len; i++) - out[i] = in[i] + prev_line[i]; - } - else - memcpy(out, in, len); -} - -static void png_filter_average(int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len) -{ - int i; - unsigned char a = 0; - unsigned char b = 0; - unsigned int sum = 0; - - for(i = 0; i < len; i++) - { - if(prev_line) - b = prev_line[i]; - - if(i >= stride) - a = out[i - stride]; - - sum = a; - sum += b; - - out[i] = (char)(in[i] + sum/2); - } -} - -static unsigned char png_paeth(unsigned char a, unsigned char b, unsigned char c) -{ - int p = (int)a + b - c; - int pa = abs(p - a); - int pb = abs(p - b); - int pc = abs(p - c); - - int pr; - - if(pa <= pb && pa <= pc) - pr = a; - else if(pb <= pc) - pr = b; - else - pr = c; - - return (char)pr; -} - -static void png_filter_paeth(int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len) -{ - int i; - unsigned char a; - unsigned char b; - unsigned char c; - - for(i = 0; i < len; i++) - { - if(prev_line && i >= stride) - { - a = out[i - stride]; - b = prev_line[i]; - c = prev_line[i - stride]; - } - else - { - if(prev_line) - b = prev_line[i]; - else - b = 0; - - if(i >= stride) - a = out[i - stride]; - else - a = 0; - - c = 0; - } - - out[i] = in[i] + png_paeth(a, b, c); - } -} - -static int png_filter(png_t* png, unsigned char* data) -{ - - - return PNG_NO_ERROR; -} - -static int png_unfilter(png_t* png, unsigned char* data) -{ - unsigned i; - unsigned pos = 0; - unsigned outpos = 0; - unsigned char *filtered = png->png_data; - - int stride = png->bpp; - - while(pos < png->png_datalen) - { - unsigned char filter = filtered[pos]; - - pos++; - - if(png->depth == 16) - { - for(i = 0; i < png->width * stride; i+=2) - { - *(short*)(filtered+pos+i) = (filtered[pos+i] << 8) | filtered[pos+i+1]; - } - } - - switch(filter) - { - case 0: /* none */ - memcpy(data+outpos, filtered+pos, png->width * stride); - break; - case 1: /* sub */ - png_filter_sub(stride, filtered+pos, data+outpos, png->width * stride); - break; - case 2: /* up */ - if(outpos) - png_filter_up(stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride); - else - png_filter_up(stride, filtered+pos, data+outpos, 0, png->width*stride); - break; - case 3: /* average */ - if(outpos) - png_filter_average(stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride); - else - png_filter_average(stride, filtered+pos, data+outpos, 0, png->width*stride); - break; - case 4: /* paeth */ - if(outpos) - png_filter_paeth(stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride); - else - png_filter_paeth(stride, filtered+pos, data+outpos, 0, png->width*stride); - break; - default: - return PNG_UNKNOWN_FILTER; - } - - outpos += png->width * stride; - pos += png->width * stride; - } - - return PNG_NO_ERROR; -} - -int png_get_data(png_t* png, unsigned char* data) -{ - int result = PNG_NO_ERROR; - - while(result == PNG_NO_ERROR) - { - result = png_process_chunk(png); - } - - if(result != PNG_DONE) - { - png_free(png->png_data); - return result; - } - - result = png_unfilter(png, data); - - png_free(png->png_data); - - return result; -} - -int png_set_data(png_t* png, unsigned width, unsigned height, char depth, int color, unsigned char* data) -{ - int i; - unsigned char *filtered; - png->width = width; - png->height = height; - png->depth = depth; - png->color_type = color; - png->bpp = png_get_bpp(png); - - filtered = png_alloc(width * height * png->bpp + height); - - for(i = 0; i < png->height; i++) - { - filtered[i*png->width*png->bpp+i] = 0; - memcpy(&filtered[i*png->width*png->bpp+i+1], data + i * png->width*png->bpp, png->width*png->bpp); - } - - png_filter(png, filtered); - png_write_ihdr(png); - png_write_idats(png, filtered); - - png_free(filtered); - return PNG_NO_ERROR; -} - - -char* png_error_string(int error) -{ - switch(error) - { - case PNG_NO_ERROR: - return "No error"; - case PNG_FILE_ERROR: - return "Unknown file error."; - case PNG_HEADER_ERROR: - return "No PNG header found. Are you sure this is a PNG?"; - case PNG_IO_ERROR: - return "Failure while reading file."; - case PNG_EOF_ERROR: - return "Reached end of file."; - case PNG_CRC_ERROR: - return "CRC or chunk length error."; - case PNG_MEMORY_ERROR: - return "Could not allocate memory."; - case PNG_ZLIB_ERROR: - return "zlib reported an error."; - case PNG_UNKNOWN_FILTER: - return "Unknown filter method used in scanline."; - case PNG_DONE: - return "PNG done"; - case PNG_NOT_SUPPORTED: - return "The PNG is unsupported by pnglite, too bad for you!"; - case PNG_WRONG_ARGUMENTS: - return "Wrong combination of arguments passed to png_open. You must use either a read_function or supply a file pointer to use."; - default: - return "Unknown error."; - }; -} diff --git a/src/engine/external/pnglite/pnglite.h b/src/engine/external/pnglite/pnglite.h deleted file mode 100644 index 578d3695d..000000000 --- a/src/engine/external/pnglite/pnglite.h +++ /dev/null @@ -1,227 +0,0 @@ -/* pnglite.h - Interface for pnglite library - Copyright (c) 2007 Daniel Karling - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. - - Daniel Karling - daniel.karling@gmail.com - */ - - -#ifndef _PNGLITE_H_ -#define _PNGLITE_H_ - -#ifdef __cplusplus -extern "C"{ -#endif - -/* - Enumerations for pnglite. - Negative numbers are error codes and 0 and up are okay responses. -*/ - -enum -{ - PNG_DONE = 1, - PNG_NO_ERROR = 0, - PNG_FILE_ERROR = -1, - PNG_HEADER_ERROR = -2, - PNG_IO_ERROR = -3, - PNG_EOF_ERROR = -4, - PNG_CRC_ERROR = -5, - PNG_MEMORY_ERROR = -6, - PNG_ZLIB_ERROR = -7, - PNG_UNKNOWN_FILTER = -8, - PNG_NOT_SUPPORTED = -9, - PNG_WRONG_ARGUMENTS = -10 -}; - -/* - The five different kinds of color storage in PNG files. -*/ - -enum -{ - PNG_GREYSCALE = 0, - PNG_TRUECOLOR = 2, - PNG_INDEXED = 3, - PNG_GREYSCALE_ALPHA = 4, - PNG_TRUECOLOR_ALPHA = 6 -}; - -/* - Typedefs for callbacks. -*/ - -typedef unsigned (*png_write_callback_t)(void* input, unsigned long size, unsigned long numel, void* user_pointer); -typedef unsigned (*png_read_callback_t)(void* output, unsigned long size, unsigned long numel, void* user_pointer); -typedef void (*png_free_t)(void* p); -typedef void * (*png_alloc_t)(unsigned long s); - -typedef struct -{ - void* zs; /* pointer to z_stream */ - png_read_callback_t read_fun; - png_write_callback_t write_fun; - void* user_pointer; - - unsigned char* png_data; - unsigned png_datalen; - - unsigned width; - unsigned height; - unsigned char depth; - unsigned char color_type; - unsigned char compression_method; - unsigned char filter_method; - unsigned char interlace_method; - unsigned char bpp; -}png_t; - -/* - Function: png_init - - This function initializes pnglite. The parameters can be used to set your own memory allocation routines following these formats: - - > void* (*custom_alloc)(unsigned long s) - > void (*custom_free)(void* p) - Parameters: - pngalloc - Pointer to custom allocation routine. If 0 is passed, malloc from libc will be used. - pngfree - Pointer to custom free routine. If 0 is passed, free from libc will be used. - - Returns: - Always returns PNG_NO_ERROR. -*/ - -int png_init(png_alloc_t pngalloc, png_free_t pngfree); - -/* - Function: png_open_file - - This function is used to open a png file with the internal file IO system. This function should be used instead of - png_open if no custom read function is used. - - Parameters: - png - Empty png_t struct. - filename - Filename of the file to be opened. - - Returns: - PNG_NO_ERROR on success, otherwise an error code. -*/ - -int png_open_file(png_t *png, const char* filename); - -int png_open_file_read(png_t *png, const char* filename); -int png_open_file_write(png_t *png, const char* filename); - -/* - Function: png_open - - This function reads or writes a png from/to the specified callback. The callbacks should be of the format: - - > unsigned long (*png_write_callback_t)(void* input, unsigned long size, unsigned long numel, void* user_pointer); - > unsigned long (*png_read_callback_t)(void* output, unsigned long size, unsigned long numel, void* user_pointer). - - Only one callback has to be specified. The read callback in case of PNG reading, otherwise the write callback. - - Writing: - The callback will be called like fwrite. - - Reading: - The callback will be called each time pnglite needs more data. The callback should read as much data as requested, - or return 0. This should always be possible if the PNG is sane. If the output-buffer is a null-pointer the callback - should only skip ahead the specified number of elements. If the callback is a null-pointer the user_pointer will be - treated as a file pointer (use png_open_file instead). - - Parameters: - png - png_t struct - read_fun - Callback function for reading. - user_pointer - User pointer to be passed to read_fun. - - Returns: - PNG_NO_ERROR on success, otherwise an error code. -*/ - -int png_open(png_t* png, png_read_callback_t read_fun, void* user_pointer); - -int png_open_read(png_t* png, png_read_callback_t read_fun, void* user_pointer); -int png_open_write(png_t* png, png_write_callback_t write_fun, void* user_pointer); - -/* - Function: png_print_info - - This function prints some info about the opened png file to stdout. - - Parameters: - png - png struct to get info from. -*/ - -void png_print_info(png_t* png); - -/* - Function: png_error_string - - This function translates an error code to a human readable string. - - Parameters: - error - Error code. - - Returns: - Pointer to string. -*/ - -char* png_error_string(int error); - -/* - Function: png_get_data - - This function decodes the opened png file and stores the result in data. data should be big enough to hold the decoded png. Required size will be: - - > width*height*(bytes per pixel) - - Parameters: - data - Where to store result. - - Returns: - PNG_NO_ERROR on success, otherwise an error code. -*/ - -int png_get_data(png_t* png, unsigned char* data); - -int png_set_data(png_t* png, unsigned width, unsigned height, char depth, int color, unsigned char* data); - -/* - Function: png_close_file - - Closes an open png file pointer. Should only be used when the png has been opened with png_open_file. - - Parameters: - png - png to close. - - Returns: - PNG_NO_ERROR -*/ - -int png_close_file(png_t* png); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/engine/shared/image_loader.cpp b/src/engine/shared/image_loader.cpp new file mode 100644 index 000000000..94ef3535b --- /dev/null +++ b/src/engine/shared/image_loader.cpp @@ -0,0 +1,287 @@ +#include "image_loader.h" +#include +#include + +#include + +struct SLibPNGWarningItem +{ + SImageByteBuffer *m_pByteLoader; + const char *pFileName; +}; + +static void LibPNGError(png_structp png_ptr, png_const_charp error_msg) +{ + SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr); + pUserStruct->m_pByteLoader->m_Err = -1; + dbg_msg("libpng", "error for file \"%s\": %s", pUserStruct->pFileName, error_msg); +} + +static void LibPNGWarning(png_structp png_ptr, png_const_charp warning_msg) +{ + SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr); + dbg_msg("libpng", "warning for file \"%s\": %s", pUserStruct->pFileName, warning_msg); +} + +static bool FileMatchesImageType(SImageByteBuffer &ByteLoader) +{ + if(ByteLoader.m_pLoadedImageBytes->size() >= 8) + return png_sig_cmp((png_bytep) & (*ByteLoader.m_pLoadedImageBytes)[0], 0, 8) == 0; + return false; +} + +static void ReadDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToRead) +{ + png_voidp pIO_Ptr = png_get_io_ptr(pPNGStruct); + + SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr; + + if(pByteLoader->m_pLoadedImageBytes->size() >= pByteLoader->m_LoadOffset + (size_t)ByteCountToRead) + { + mem_copy(pOutBytes, &(*pByteLoader->m_pLoadedImageBytes)[pByteLoader->m_LoadOffset], (size_t)ByteCountToRead); + + pByteLoader->m_LoadOffset += (size_t)ByteCountToRead; + } + else + { + pByteLoader->m_Err = -1; + dbg_msg("png", "could not read bytes, file was too small."); + } +} + +static int LibPNGGetColorChannelCount(int LibPNGColorType) +{ + if(LibPNGColorType == PNG_COLOR_TYPE_GRAY) + return 1; + else if(LibPNGColorType == PNG_COLOR_TYPE_PALETTE || LibPNGColorType == PNG_COLOR_TYPE_RGB) + return 3; + else if(LibPNGColorType == PNG_COLOR_TYPE_RGBA) + return 4; + + return 4; +} + +static void LibPNGSetImageFormat(EImageFormat &ImageFormat, int LibPNGColorType) +{ + ImageFormat = IMAGE_FORMAT_RGBA; + if(LibPNGColorType == PNG_COLOR_TYPE_GRAY) + ImageFormat = IMAGE_FORMAT_R; + else if(LibPNGColorType == PNG_COLOR_TYPE_PALETTE || LibPNGColorType == PNG_COLOR_TYPE_RGB) + ImageFormat = IMAGE_FORMAT_RGB; + else if(LibPNGColorType == PNG_COLOR_TYPE_RGBA) + ImageFormat = IMAGE_FORMAT_RGBA; +} + +static void LibPNGDeleteReadStruct(png_structp pPNGStruct, png_infop pPNGInfo) +{ + png_destroy_info_struct(pPNGStruct, &pPNGInfo); + png_destroy_read_struct(&pPNGStruct, NULL, NULL); +} + +bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &Width, int &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat) +{ + png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + + if(pPNGStruct == NULL) + { + dbg_msg("png", "libpng internal failure: png_create_read_struct failed."); + return false; + } + + png_infop pPNGInfo = png_create_info_struct(pPNGStruct); + + if(pPNGInfo == NULL) + { + png_destroy_read_struct(&pPNGStruct, NULL, NULL); + dbg_msg("png", "libpng internal failure: png_create_info_struct failed."); + return false; + } + + SLibPNGWarningItem UserErrorStruct = {&ByteLoader, pFileName}; + png_set_error_fn(pPNGStruct, &UserErrorStruct, LibPNGError, LibPNGWarning); + + if(!FileMatchesImageType(ByteLoader)) + { + LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo); + return false; + } + + ByteLoader.m_LoadOffset = 8; + + png_set_read_fn(pPNGStruct, (png_bytep)&ByteLoader, ReadDataFromLoadedBytes); + + png_set_sig_bytes(pPNGStruct, 8); + + png_read_info(pPNGStruct, pPNGInfo); + + if(ByteLoader.m_Err != 0) + { + LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo); + return false; + } + + Width = png_get_image_width(pPNGStruct, pPNGInfo); + Height = png_get_image_height(pPNGStruct, pPNGInfo); + int ColorType = png_get_color_type(pPNGStruct, pPNGInfo); + png_byte BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo); + + bool PNGErr = false; + + if(BitDepth == 16) + png_set_strip_16(pPNGStruct); + else if(BitDepth > 8) + { + dbg_msg("png", "non supported bit depth."); + PNGErr = true; + } + + if(Width == 0 || Height == 0 || BitDepth == 0) + { + dbg_msg("png", "image had width or height of 0."); + PNGErr = true; + } + + if(!PNGErr) + { + if(ColorType == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(pPNGStruct); + + if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8) + png_set_expand_gray_1_2_4_to_8(pPNGStruct); + + if(png_get_valid(pPNGStruct, pPNGInfo, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(pPNGStruct); + + png_read_update_info(pPNGStruct, pPNGInfo); + + int ColorChannelCount = LibPNGGetColorChannelCount(ColorType); + int BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo); + + if(BytesInRow == Width * ColorChannelCount) + { + png_bytepp pRowPointers = new png_bytep[Height]; + for(int y = 0; y < Height; ++y) + { + pRowPointers[y] = new png_byte[BytesInRow]; + } + + png_read_image(pPNGStruct, pRowPointers); + + if(ByteLoader.m_Err == 0) + pImageBuff = (uint8_t *)malloc((size_t)Height * (size_t)Width * (size_t)ColorChannelCount * sizeof(uint8_t)); + else + PNGErr = true; + + for(int i = 0; i < Height; ++i) + { + if(ByteLoader.m_Err == 0) + mem_copy(&pImageBuff[i * BytesInRow], pRowPointers[i], BytesInRow); + delete[] pRowPointers[i]; + } + delete[] pRowPointers; + + LibPNGSetImageFormat(ImageFormat, ColorType); + } + else + PNGErr = true; + } + + png_destroy_info_struct(pPNGStruct, &pPNGInfo); + png_destroy_read_struct(&pPNGStruct, NULL, NULL); + + return !PNGErr; +} + +static void WriteDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite) +{ + if(ByteCountToWrite > 0) + { + png_voidp pIO_Ptr = png_get_io_ptr(pPNGStruct); + + SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr; + + size_t NewSize = pByteLoader->m_LoadOffset + (size_t)ByteCountToWrite; + pByteLoader->m_pLoadedImageBytes->resize(NewSize); + + mem_copy(&(*pByteLoader->m_pLoadedImageBytes)[pByteLoader->m_LoadOffset], pOutBytes, (size_t)ByteCountToWrite); + pByteLoader->m_LoadOffset = NewSize; + } +} + +static void FlushPNGWrite(png_structp png_ptr) {} + +static int ImageLoaderHelperFormatToColorChannel(EImageFormat Format) +{ + if(Format == IMAGE_FORMAT_R) + return 1; + else if(Format == IMAGE_FORMAT_RGB) + return 3; + else if(Format == IMAGE_FORMAT_RGBA) + return 4; + + return 4; +} + +bool SavePNG(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, int Width, int Height) +{ + png_structp pPNGStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + + if(pPNGStruct == NULL) + { + dbg_msg("png", "libpng internal failure: png_create_write_struct failed."); + return false; + } + + png_infop pPNGInfo = png_create_info_struct(pPNGStruct); + + if(pPNGInfo == NULL) + { + png_destroy_read_struct(&pPNGStruct, NULL, NULL); + dbg_msg("png", "libpng internal failure: png_create_info_struct failed."); + return false; + } + + WrittenBytes.m_LoadOffset = 0; + WrittenBytes.m_pLoadedImageBytes->clear(); + + png_set_write_fn(pPNGStruct, (png_bytep)&WrittenBytes, WriteDataFromLoadedBytes, FlushPNGWrite); + + int ColorType = PNG_COLOR_TYPE_RGB; + int WriteBytesPerPixel = ImageLoaderHelperFormatToColorChannel(ImageFormat); + if(ImageFormat == IMAGE_FORMAT_R) + { + ColorType = PNG_COLOR_TYPE_GRAY; + } + else if(ImageFormat == IMAGE_FORMAT_RGBA) + { + ColorType = PNG_COLOR_TYPE_RGBA; + } + + png_set_IHDR(pPNGStruct, pPNGInfo, Width, Height, 8, ColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(pPNGStruct, pPNGInfo); + + png_bytepp pRowPointers = new png_bytep[Height]; + int WidthBytes = Width * WriteBytesPerPixel; + ptrdiff_t BufferOffset = 0; + for(int y = 0; y < Height; ++y) + { + pRowPointers[y] = new png_byte[WidthBytes]; + mem_copy(pRowPointers[y], pRawBuffer + BufferOffset, WidthBytes); + BufferOffset += (ptrdiff_t)WidthBytes; + } + png_write_image(pPNGStruct, pRowPointers); + + png_write_end(pPNGStruct, pPNGInfo); + + for(int y = 0; y < Height; ++y) + { + delete[](pRowPointers[y]); + } + delete[](pRowPointers); + + png_destroy_info_struct(pPNGStruct, &pPNGInfo); + png_destroy_write_struct(&pPNGStruct, NULL); + + return true; +} diff --git a/src/engine/shared/image_loader.h b/src/engine/shared/image_loader.h new file mode 100644 index 000000000..0fce77f5d --- /dev/null +++ b/src/engine/shared/image_loader.h @@ -0,0 +1,28 @@ +#ifndef ENGINE_SHARED_IMAGE_LOADER_H +#define ENGINE_SHARED_IMAGE_LOADER_H + +#include +#include +#include + +enum EImageFormat +{ + IMAGE_FORMAT_R = 0, + IMAGE_FORMAT_RGB, + IMAGE_FORMAT_RGBA, +}; + +typedef std::vector TImageByteBuffer; +struct SImageByteBuffer +{ + SImageByteBuffer(TImageByteBuffer *pBuff) : + m_LoadOffset(0), m_pLoadedImageBytes(pBuff), m_Err(0) {} + size_t m_LoadOffset; + TImageByteBuffer *m_pLoadedImageBytes; + int m_Err; +}; + +bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &Width, int &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat); +bool SavePNG(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, int Width, int Height); + +#endif diff --git a/src/tools/dilate.cpp b/src/tools/dilate.cpp index ba3660b60..d3b70bb37 100644 --- a/src/tools/dilate.cpp +++ b/src/tools/dilate.cpp @@ -2,71 +2,74 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include +#include +#include #include -#include int DilateFile(const char *pFilename) { IOHANDLE File = io_open(pFilename, IOFLAG_READ); - if(!File) + if(File) { - dbg_msg("dilate", "failed to open file. filename='%s'", pFilename); - return 0; - } + io_seek(File, 0, IOSEEK_END); + unsigned int FileSize = io_tell(File); + io_seek(File, 0, IOSEEK_START); + TImageByteBuffer ByteBuffer; + SImageByteBuffer ImageByteBuffer(&ByteBuffer); + + ByteBuffer.resize(FileSize); + io_read(File, &ByteBuffer.front(), FileSize); - png_init(0, 0); - png_t Png; - int Error = png_open_read(&Png, 0, File); - if(Error != PNG_NO_ERROR) - { - dbg_msg("dilate", "failed to open image file. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); io_close(File); - return 0; - } - if(Png.color_type != PNG_TRUECOLOR_ALPHA) + CImageInfo Img; + + uint8_t *pImgBuffer = NULL; + EImageFormat ImageFormat; + if(LoadPNG(ImageByteBuffer, pFilename, Img.m_Width, Img.m_Height, pImgBuffer, ImageFormat)) + { + if(ImageFormat != IMAGE_FORMAT_RGBA) + { + free(pImgBuffer); + dbg_msg("dilate", "%s: not an RGBA image", pFilename); + return -1; + } + + Img.m_pData = pImgBuffer; + + unsigned char *pBuffer = (unsigned char *)Img.m_pData; + + int w = Img.m_Width; + int h = Img.m_Height; + + DilateImage(pBuffer, w, h, 4); + + // save here + IOHANDLE SaveFile = io_open(pFilename, IOFLAG_WRITE); + if(SaveFile) + { + TImageByteBuffer ByteBuffer2; + SImageByteBuffer ImageByteBuffer2(&ByteBuffer2); + + if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)pBuffer, ImageByteBuffer2, w, h)) + io_write(SaveFile, &ByteBuffer2.front(), ByteBuffer2.size()); + io_close(SaveFile); + + free(pBuffer); + } + } + else + { + dbg_msg("dilate", "failed unknown image format: %s", pFilename); + return -1; + } + } + else { - dbg_msg("dilate", "%s: not an RGBA image", pFilename); - return 1; + dbg_msg("dilate", "failed to open image file. filename='%s'", pFilename); + return -1; } - unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * sizeof(unsigned char) * 4); - - Error = png_get_data(&Png, pBuffer); - if(Error != PNG_NO_ERROR) - { - dbg_msg("map_convert_07", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); - free(pBuffer); - io_close(File); - return 0; - } - io_close(File); - - int w = Png.width; - int h = Png.height; - - DilateImage(pBuffer, w, h, 4); - - // save here - File = io_open(pFilename, IOFLAG_WRITE); - if(!File) - { - dbg_msg("dilate", "failed to open file. filename='%s'", pFilename); - free(pBuffer); - return 0; - } - Error = png_open_write(&Png, 0, File); - if(Error != PNG_NO_ERROR) - { - dbg_msg("dilate", "failed to open image file. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); - io_close(File); - return 0; - } - png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer); - io_close(File); - - free(pBuffer); - return 0; } diff --git a/src/tools/map_convert_07.cpp b/src/tools/map_convert_07.cpp index c7fd618ae..718f9e6ae 100644 --- a/src/tools/map_convert_07.cpp +++ b/src/tools/map_convert_07.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include /* Usage: map_convert_07 */ @@ -29,43 +29,40 @@ int g_aImageIDs[64]; int LoadPNG(CImageInfo *pImg, const char *pFilename) { IOHANDLE File = io_open(pFilename, IOFLAG_READ); - if(!File) + if(File) { - dbg_msg("map_convert_07", "failed to open file. filename='%s'", pFilename); - return 0; - } + io_seek(File, 0, IOSEEK_END); + unsigned int FileSize = io_tell(File); + io_seek(File, 0, IOSEEK_START); + TImageByteBuffer ByteBuffer; + SImageByteBuffer ImageByteBuffer(&ByteBuffer); + + ByteBuffer.resize(FileSize); + io_read(File, &ByteBuffer.front(), FileSize); - png_t Png; - int Error = png_open_read(&Png, 0, File); - if(Error != PNG_NO_ERROR) - { - dbg_msg("map_convert_07", "failed to open image file. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); io_close(File); - return 0; - } - if(Png.depth != 8 || Png.color_type != PNG_TRUECOLOR_ALPHA || Png.width > (2 << 12) || Png.height > (2 << 12)) - { - dbg_msg("map_convert_07", "invalid image format. filename='%s'", pFilename); - io_close(File); - return 0; - } + uint8_t *pImgBuffer = NULL; + EImageFormat ImageFormat; + if(LoadPNG(ImageByteBuffer, pFilename, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat)) + { + pImg->m_pData = pImgBuffer; - unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * Png.bpp); - Error = png_get_data(&Png, pBuffer); - if(Error != PNG_NO_ERROR) - { - dbg_msg("map_convert_07", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); - free(pBuffer); - io_close(File); - return 0; + if(ImageFormat == IMAGE_FORMAT_RGBA && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13)) + { + pImg->m_Format = CImageInfo::FORMAT_RGBA; + } + else + { + dbg_msg("map_convert_07", "invalid image format. filename='%s'", pFilename); + return 0; + } + } + else + return 0; } - io_close(File); - - pImg->m_Width = Png.width; - pImg->m_Height = Png.height; - pImg->m_Format = CImageInfo::FORMAT_RGBA; - pImg->m_pData = pBuffer; + else + return 0; return 1; } @@ -188,8 +185,6 @@ int main(int argc, const char **argv) return -1; } - png_init(0, 0); - g_NextDataItemID = g_DataReader.NumData(); int i = 0; diff --git a/src/tools/map_extract.cpp b/src/tools/map_extract.cpp index 187cd64ae..c98119bf6 100644 --- a/src/tools/map_extract.cpp +++ b/src/tools/map_extract.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave) { @@ -52,22 +52,15 @@ bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave) // copy image data IOHANDLE File = io_open(aBuf, IOFLAG_WRITE); - if(!File) + if(File) { - dbg_msg("map_extract", "failed to open file. filename='%s'", aBuf); - continue; + TImageByteBuffer ByteBuffer; + SImageByteBuffer ImageByteBuffer(&ByteBuffer); + + if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)Reader.GetData(pItem->m_ImageData), ImageByteBuffer, pItem->m_Width, pItem->m_Height)) + io_write(File, &ByteBuffer.front(), ByteBuffer.size()); + io_close(File); } - png_t Png; - int Error = png_open_write(&Png, 0, File); - if(Error != PNG_NO_ERROR) - { - dbg_msg("map_extract", "failed to write image file. filename='%s', pnglite: %s", aBuf, png_error_string(Error)); - } - else - { - png_set_data(&Png, pItem->m_Width, pItem->m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Reader.GetData(pItem->m_ImageData)); - } - io_close(File); } // load sounds @@ -123,8 +116,6 @@ int main(int argc, const char *argv[]) return -1; } - png_init(0, 0); - int Result = Process(pStorage, argv[1], pDir) ? 0 : 1; return Result; } diff --git a/src/tools/map_replace_image.cpp b/src/tools/map_replace_image.cpp index aa26be6da..c02c3e2bc 100644 --- a/src/tools/map_replace_image.cpp +++ b/src/tools/map_replace_image.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include /* Usage: map_replace_image Notes: map filepath must be relative to user default teeworlds folder @@ -26,52 +26,44 @@ void *g_pNewData = nullptr; int LoadPNG(CImageInfo *pImg, const char *pFilename) { - png_t Png; - IOHANDLE File = io_open(pFilename, IOFLAG_READ); - if(!File) + if(File) { - dbg_msg("map_replace_image", "failed to open file. filename='%s'", pFilename); - return 0; - } - int Error = png_open_read(&Png, 0, File); - if(Error != PNG_NO_ERROR) - { - dbg_msg("map_replace_image", "failed to open image file. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); - io_close(File); - return 0; - } + io_seek(File, 0, IOSEEK_END); + unsigned int FileSize = io_tell(File); + io_seek(File, 0, IOSEEK_START); + TImageByteBuffer ByteBuffer; + SImageByteBuffer ImageByteBuffer(&ByteBuffer); - if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA) || Png.width > (2 << 12) || Png.height > (2 << 12)) - { - dbg_msg("map_replace_image", "invalid image format. filename='%s'", pFilename); - io_close(File); - return 0; - } + ByteBuffer.resize(FileSize); + io_read(File, &ByteBuffer.front(), FileSize); - unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * Png.bpp); - Error = png_get_data(&Png, pBuffer); - if(Error != PNG_NO_ERROR) - { - dbg_msg("map_replace_image", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); - free(pBuffer); io_close(File); - return 0; - } - io_close(File); - pImg->m_Width = Png.width; - pImg->m_Height = Png.height; - if(Png.color_type == PNG_TRUECOLOR) - pImg->m_Format = CImageInfo::FORMAT_RGB; - else if(Png.color_type == PNG_TRUECOLOR_ALPHA) - pImg->m_Format = CImageInfo::FORMAT_RGBA; + uint8_t *pImgBuffer = NULL; + EImageFormat ImageFormat; + if(LoadPNG(ImageByteBuffer, pFilename, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat)) + { + if((ImageFormat == IMAGE_FORMAT_RGBA || ImageFormat == IMAGE_FORMAT_RGB) && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13)) + { + pImg->m_pData = pImgBuffer; + + if(ImageFormat == IMAGE_FORMAT_RGB) // ignore_convention + pImg->m_Format = CImageInfo::FORMAT_RGB; + else if(ImageFormat == IMAGE_FORMAT_RGBA) // ignore_convention + pImg->m_Format = CImageInfo::FORMAT_RGBA; + else + { + free(pImgBuffer); + return 0; + } + } + } + else + return 0; + } else - { - free(pBuffer); return 0; - } - pImg->m_pData = pBuffer; return 1; } @@ -146,8 +138,6 @@ int main(int argc, const char **argv) return -1; } - png_init(0, 0); - // add all items for(int Index = 0; Index < g_DataReader.NumItems(); Index++) {