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++) {