mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
4829: Added map_replace_area tool r=def- a=sctt Motivation: sometimes mappers need to copy a certain area from a map to another (but also to a different position of the same map). that's very time consuming on complex maps, as tiles and quads layers have to be manually copied one by one. map_replace_area is designed to automatically replace all the tiles and quads from a specific area to another. `Usage: map_replace_area <from_map> <from_x> <from_y> <to_map> <to_x> <to_y> <width> <height> <output_map>` PoW screenshots attached below. note: for the moment map_replace_area suppose that you are working on the same map (that's what i needed), that means source and destination maps must have the same layers structure, or else an error is returned. but i understand it might be useful to also work on totally different maps, in that case users must be able specify layers they want to consider by associating source map layers to destination map layers (by using a config file maybe). i'm probably going to make another PR to add this enhancement in the future. PoW: ![Screenshot from 2022-03-16 18-08-12](https://user-images.githubusercontent.com/3328841/158650515-17c31639-28f7-4e19-954a-b5734ee82703.png) ![Screenshot from 2022-03-16 18-08-43](https://user-images.githubusercontent.com/3328841/158650512-6e21f2b4-538f-4974-aaa2-2983551d24b4.png) ![Screenshot from 2022-03-16 18-11-50](https://user-images.githubusercontent.com/3328841/158650510-d00a05a9-a2e8-4df2-8674-0c80fd894f66.png) 5343: Add rcon and chat to integration test r=def- a=ChillerDragon Add more action to the integration test script: chat messages, chat commands, rcon commands. This unlocks more code coverage at runtime to catch asan issues. It adds a very restrictive regex on the log format. But I am happy to maintain that in case changes to the log output happen. The current version is already supporting heinrichs refactor https://github.com/ddnet/ddnet/pull/5036 Also ensure chat messages arrive in the correct format. Would have catched the following issues: https://github.com/ddnet/ddnet/issues/5342 https://github.com/ddnet/ddnet/issues/5340 https://github.com/ddnet/ddnet/issues/5302 https://github.com/ddnet/ddnet/pull/5126 DEPENDS ON: https://github.com/ddnet/ddnet/issues/5342 https://github.com/ddnet/ddnet/issues/5340 5465: Change from pnglite to libpng for PNG reading r=def- a=heinrich5991 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. This also refuses to load images of types not supported by pnglite, which allows us to think about backward compatibility while also introducing libpng. ## Checklist - [x] Tested the change ingame - [ ] Provided screenshots if it is a visual change - [ ] Tested in combination with possibly related configuration options - [ ] Written a unit test if it works standalone, system.c especially - [ ] Considered possible null pointers and out of bounds array indexing - [ ] Changed no physics that affect existing maps - [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: sctt <scottistefano91@gmail.com> Co-authored-by: term <term@term.sinervis.pri> Co-authored-by: f <scottistefano91@gmail.com> Co-authored-by: heinrich5991 <heinrich5991@gmail.com> Co-authored-by: ChillerDragon <ChillerDragon@gmail.com> Co-authored-by: def <dennis@felsin9.de> Co-authored-by: Jupeyy <jupjopjap@gmail.com>
This commit is contained in:
commit
b75289df35
5
.github/workflows/build.yaml
vendored
5
.github/workflows/build.yaml
vendored
|
@ -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 }}
|
||||
|
@ -176,7 +176,8 @@ jobs:
|
|||
- name: Run integration tests with Valgrind's Memcheck
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
scripts/integration_test.sh --valgrind-memcheck headless
|
||||
cd headless
|
||||
./integration_test.sh --valgrind-memcheck
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
|
|
4
.github/workflows/clang-sanitizer.yml
vendored
4
.github/workflows/clang-sanitizer.yml
vendored
|
@ -36,6 +36,7 @@ jobs:
|
|||
cd san
|
||||
export UBSAN_OPTIONS=suppressions=../ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0
|
||||
export ASAN_OPTIONS=log_path=./SAN:print_stacktrace=1:check_initialization_order=1:detect_leaks=1:halt_on_errors=0
|
||||
export LSAN_OPTIONS=suppressions=../lsan.supp
|
||||
./DDNet "cl_download_skins 0;quit" || true
|
||||
./DDNet-Server shutdown || true
|
||||
if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)"
|
||||
|
@ -45,4 +46,5 @@ jobs:
|
|||
fi
|
||||
- name: Run integration tests with ASan and UBSan
|
||||
run: |
|
||||
scripts/integration_test.sh san
|
||||
cd san
|
||||
make run_integration_tests
|
||||
|
|
|
@ -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()
|
||||
|
@ -1368,6 +1371,7 @@ set(EXPECTED_DATA
|
|||
maps/Sunny\ Side\ Up.map
|
||||
maps/Tsunami.map
|
||||
maps/Tutorial.map
|
||||
maps/coverage.map
|
||||
maps/ctf1.map
|
||||
maps/ctf2.map
|
||||
maps/ctf3.map
|
||||
|
@ -1558,10 +1562,18 @@ foreach(datafile ${DATA})
|
|||
file(MAKE_DIRECTORY ${DESTINATION})
|
||||
file(COPY ${datafile} DESTINATION ${DESTINATION})
|
||||
endforeach()
|
||||
set(INTEGRATION_TEST_COPY_FILES
|
||||
lsan.supp
|
||||
memcheck.supp
|
||||
ubsan.supp
|
||||
valgrind.supp
|
||||
scripts/integration_test.sh
|
||||
)
|
||||
set(COPY_FILES
|
||||
${CURL_COPY_FILES}
|
||||
${FREETYPE_COPY_FILES}
|
||||
${OPUSFILE_COPY_FILES}
|
||||
${PNG_COPY_FILES}
|
||||
${SDL2_COPY_FILES}
|
||||
${SQLite3_COPY_FILES}
|
||||
${FFMPEG_COPY_FILES}
|
||||
|
@ -1569,6 +1581,7 @@ set(COPY_FILES
|
|||
${DISCORDSDK_COPY_FILES}
|
||||
${VULKAN_COPY_FILES}
|
||||
${EXCEPTION_HANDLING_COPY_FILES}
|
||||
${INTEGRATION_TEST_COPY_FILES}
|
||||
)
|
||||
file(COPY ${COPY_FILES} DESTINATION .)
|
||||
|
||||
|
@ -1755,8 +1768,6 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
|
|||
http.h
|
||||
huffman.cpp
|
||||
huffman.h
|
||||
image_manipulation.cpp
|
||||
image_manipulation.h
|
||||
jobs.cpp
|
||||
jobs.h
|
||||
json.cpp
|
||||
|
@ -1805,6 +1816,12 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
|
|||
websockets.cpp
|
||||
websockets.h
|
||||
)
|
||||
set_src(ENGINE_GFX GLOB src/engine/gfx
|
||||
image_loader.cpp
|
||||
image_loader.h
|
||||
image_manipulation.cpp
|
||||
image_manipulation.h
|
||||
)
|
||||
set_src(GAME_SHARED GLOB src/game
|
||||
bezier.cpp
|
||||
bezier.h
|
||||
|
@ -1876,9 +1893,11 @@ set(LIBS
|
|||
)
|
||||
|
||||
# Targets
|
||||
add_library(engine-gfx EXCLUDE_FROM_ALL OBJECT ${ENGINE_GFX})
|
||||
target_include_directories(engine-gfx PRIVATE ${PNG_INCLUDE_DIRS})
|
||||
add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_UUID_SHARED} ${BASE})
|
||||
add_library(game-shared EXCLUDE_FROM_ALL OBJECT ${GAME_SHARED} ${GAME_GENERATED_SHARED})
|
||||
list(APPEND TARGETS_OWN engine-shared game-shared)
|
||||
list(APPEND TARGETS_OWN engine-gfx engine-shared game-shared)
|
||||
|
||||
if(DISCORD AND NOT DISCORD_DYNAMIC)
|
||||
add_library(discord-shared SHARED IMPORTED)
|
||||
|
@ -2102,14 +2121,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}
|
||||
|
@ -2160,6 +2179,7 @@ if(CLIENT)
|
|||
${CLIENT_ICON}
|
||||
${CLIENT_MANIFEST}
|
||||
${DEPS_CLIENT}
|
||||
$<TARGET_OBJECTS:engine-gfx>
|
||||
$<TARGET_OBJECTS:engine-shared>
|
||||
$<TARGET_OBJECTS:game-shared>
|
||||
)
|
||||
|
@ -2169,6 +2189,7 @@ if(CLIENT)
|
|||
${CLIENT_ICON}
|
||||
${CLIENT_MANIFEST}
|
||||
${DEPS_CLIENT}
|
||||
$<TARGET_OBJECTS:engine-gfx>
|
||||
$<TARGET_OBJECTS:engine-shared>
|
||||
$<TARGET_OBJECTS:game-shared>
|
||||
)
|
||||
|
@ -2185,7 +2206,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}
|
||||
|
@ -2361,6 +2382,7 @@ if(SERVER)
|
|||
$<TARGET_OBJECTS:game-shared>
|
||||
)
|
||||
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})
|
||||
|
||||
|
@ -2399,6 +2421,7 @@ if(TOOLS)
|
|||
map_diff.cpp
|
||||
map_extract.cpp
|
||||
map_optimize.cpp
|
||||
map_replace_area.cpp
|
||||
map_replace_image.cpp
|
||||
map_resave.cpp
|
||||
packetgen.cpp
|
||||
|
@ -2413,9 +2436,9 @@ 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})
|
||||
list(APPEND TOOL_DEPS $<TARGET_OBJECTS:engine-gfx>)
|
||||
list(APPEND TOOL_LIBS ${PNG_LIBRARIES})
|
||||
endif()
|
||||
if(TOOL MATCHES "^config_")
|
||||
list(APPEND EXTRA_TOOL_SRC "src/tools/config_common.h")
|
||||
|
@ -2540,11 +2563,12 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
add_executable(${TARGET_TESTRUNNER} EXCLUDE_FROM_ALL
|
||||
${TESTS}
|
||||
${TESTS_EXTRA}
|
||||
$<TARGET_OBJECTS:engine-gfx>
|
||||
$<TARGET_OBJECTS:engine-shared>
|
||||
$<TARGET_OBJECTS:game-shared>
|
||||
${DEPS}
|
||||
)
|
||||
target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${MYSQL_LIBRARIES} ${GTEST_LIBRARIES})
|
||||
target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${MYSQL_LIBRARIES} ${PNG_LIBRARIES} ${GTEST_LIBRARIES})
|
||||
target_include_directories(${TARGET_TESTRUNNER} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS})
|
||||
|
||||
list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER})
|
||||
|
@ -2552,12 +2576,19 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
|
||||
add_custom_target(run_tests
|
||||
COMMAND $<TARGET_FILE:${TARGET_TESTRUNNER}> ${TESTRUNNER_ARGS}
|
||||
COMMENT Running tests
|
||||
COMMENT Running unit tests
|
||||
DEPENDS ${TARGET_TESTRUNNER}
|
||||
USES_TERMINAL
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_target(run_integration_tests
|
||||
COMMAND ${PROJECT_BINARY_DIR}/integration_test.sh ${INTEGRATIONTESTRUNNER_ARGS}
|
||||
COMMENT Running integration tests
|
||||
DEPENDS ${TARGET_CLIENT} ${TARGET_SERVER}
|
||||
USES_TERMINAL
|
||||
)
|
||||
|
||||
########################################################################
|
||||
# INSTALLATION
|
||||
########################################################################
|
||||
|
@ -2683,10 +2714,14 @@ set(CPACK_SOURCE_FILES
|
|||
datasrc/
|
||||
ddnet-libs/
|
||||
license.txt
|
||||
lsan.supp
|
||||
memcheck.supp
|
||||
other/
|
||||
scripts/
|
||||
src/
|
||||
storage.cfg
|
||||
ubsan.supp
|
||||
valgrind.supp
|
||||
)
|
||||
set(CPACK_SOURCE_IGNORE_FILES
|
||||
"\\\\.pyc$"
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
@ -167,7 +165,7 @@ make
|
|||
```
|
||||
and run with:
|
||||
```bash
|
||||
UBSAN_OPTIONS=suppressions=./ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0 ASAN_OPTIONS=log_path=./SAN:print_stacktrace=1:check_initialization_order=1:detect_leaks=1:halt_on_errors=0 ./DDNet
|
||||
UBSAN_OPTIONS=suppressions=./ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0 ASAN_OPTIONS=log_path=./SAN:print_stacktrace=1:check_initialization_order=1:detect_leaks=1:halt_on_errors=0 LSAN_OPTIONS=suppressions=./lsan.supp ./DDNet
|
||||
```
|
||||
|
||||
Check the SAN.\* files afterwards. This finds more problems than memcheck, runs faster, but requires a modern GCC/Clang compiler.
|
||||
|
|
46
cmake/FindPNG.cmake
Normal file
46
cmake/FindPNG.cmake
Normal file
|
@ -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()
|
|
@ -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 $<TARGET_OBJECTS:pnglite>)
|
||||
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()
|
|
@ -1 +1 @@
|
|||
Subproject commit 02f8b2e0f3436347820cd36f35455f632ec8c590
|
||||
Subproject commit 8d38bd56b441fbf473d53c65499dd8f1f3071b66
|
|
@ -1,6 +0,0 @@
|
|||
# tests
|
||||
|
||||
Use the test script at the root of the repo to run tests
|
||||
|
||||
./scripts/integration_test.sh
|
||||
|
1
lsan.supp
Normal file
1
lsan.supp
Normal file
|
@ -0,0 +1 @@
|
|||
leak:CCommandProcessorFragment_OpenGL2::Cmd_CreateBufferObject
|
|
@ -1,15 +1,3 @@
|
|||
{
|
||||
Motd
|
||||
Memcheck:Cond
|
||||
fun:_ZN7IServer11SendPackMsgI15CNetMsg_Sv_MotdLi0EEEiPT_ii
|
||||
fun:_ZN12CGameContext8SendMotdEi
|
||||
fun:_ZN12CGameContext25ConchainSpecialMotdupdateEPN8IConsole7IResultEPvPFvS2_S3_ES3_
|
||||
fun:_ZN8CConsole9Con_ChainEPN8IConsole7IResultEPv
|
||||
fun:_ZN8CConsole18ExecuteLineStrokedEiPKcib
|
||||
fun:_ZN8CConsole11ExecuteLineEPKcib
|
||||
fun:_ZN8CConsole11ExecuteFileEPKcibi
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
DemoRecorderWrite
|
||||
Memcheck:Param
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ ! -f scripts/integration_test.sh ] || [ ! -f CMakeLists.txt ]
|
||||
then
|
||||
echo "Error: make sure your are in the root of the repo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arg_build_dir="build"
|
||||
arg_end_args=0
|
||||
arg_verbose=0
|
||||
arg_valgrind_memcheck=0
|
||||
|
||||
for arg in "$@"
|
||||
do
|
||||
if [[ "${arg::1}" == "-" ]] && [[ "$arg_end_args" == "0" ]]
|
||||
then
|
||||
if [ "$arg" == "-h" ] || [ "$arg" == "--help" ]
|
||||
then
|
||||
echo "usage: $(basename "$0") [OPTION..] [build dir]"
|
||||
|
@ -24,43 +14,30 @@ do
|
|||
echo "options:"
|
||||
echo " --help|-h show this help"
|
||||
echo " --verbose|-v verbose output"
|
||||
echo " --valgrind-memcheck use valgrind's memcheck to run server and client"
|
||||
exit 0
|
||||
elif [ "$arg" == "-v" ] || [ "$arg" == "--verbose" ]
|
||||
then
|
||||
arg_verbose=1
|
||||
elif [ "$arg" == "--valgrind-memcheck" ]
|
||||
then
|
||||
arg_valgrind_memcheck=1
|
||||
elif [ "$arg" == "--" ]
|
||||
then
|
||||
arg_end_args=1
|
||||
else
|
||||
echo "Error: unknown arg '$arg'"
|
||||
fi
|
||||
else
|
||||
arg_build_dir="$arg"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d "$arg_build_dir" ]
|
||||
if [ ! -f DDNet ]
|
||||
then
|
||||
echo "Error: build directory '$arg_build_dir' not found"
|
||||
echo "Error: client binary not found DDNet' not found"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$arg_build_dir"/DDNet ]
|
||||
if [ ! -f DDNet-Server ]
|
||||
then
|
||||
echo "Error: client binary not found '$arg_build_dir/DDNet' not found"
|
||||
echo "Error: server binary not found DDNet-Server' not found"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$arg_build_dir"/DDNet-Server ]
|
||||
then
|
||||
echo "Error: server binary not found '$arg_build_dir/DDNet-Server' not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p integration_test
|
||||
cp "$arg_build_dir"/DDNet* integration_test
|
||||
|
||||
cd integration_test || exit 1
|
||||
|
||||
got_killed=0
|
||||
|
||||
|
@ -89,13 +66,6 @@ function cleanup() {
|
|||
|
||||
trap cleanup EXIT
|
||||
|
||||
{
|
||||
echo $'add_path $CURRENTDIR'
|
||||
echo $'add_path $USERDIR'
|
||||
echo $'add_path $DATADIR'
|
||||
echo $'add_path ../data'
|
||||
} > storage.cfg
|
||||
|
||||
function fail()
|
||||
{
|
||||
sleep 1
|
||||
|
@ -104,37 +74,18 @@ function fail()
|
|||
echo "[-] $1 exited with code $2"
|
||||
}
|
||||
|
||||
if test -n "$(find . -maxdepth 1 -name '*.fifo' -print -quit)"
|
||||
then
|
||||
rm ./*.fifo
|
||||
fi
|
||||
if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)"
|
||||
then
|
||||
rm SAN.*
|
||||
fi
|
||||
if test -n "$(find . -maxdepth 1 -name 'fail_*' -print -quit)"
|
||||
then
|
||||
rm fail_*
|
||||
fi
|
||||
if [ -f ddnet-server.sqlite ]
|
||||
then
|
||||
rm ddnet-server.sqlite
|
||||
fi
|
||||
|
||||
# TODO: check for open ports instead
|
||||
port=17822
|
||||
|
||||
cp ../ubsan.supp .
|
||||
cp ../memcheck.supp .
|
||||
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
DETECT_LEAKS=0
|
||||
else
|
||||
DETECT_LEAKS=1
|
||||
fi
|
||||
|
||||
export UBSAN_OPTIONS=suppressions=./ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0
|
||||
export UBSAN_OPTIONS=suppressions=../ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0
|
||||
export ASAN_OPTIONS=log_path=./SAN:print_stacktrace=1:check_initialization_order=1:detect_leaks=$DETECT_LEAKS:halt_on_errors=0
|
||||
export LSAN_OPTIONS=suppressions=../lsan.supp:print_suppressions=0
|
||||
|
||||
function print_results() {
|
||||
if [ "$arg_valgrind_memcheck" == "1" ]; then
|
||||
|
@ -152,21 +103,34 @@ function print_results() {
|
|||
return 0
|
||||
}
|
||||
|
||||
rm -rf integration_test
|
||||
mkdir -p integration_test/data/maps
|
||||
cp data/maps/coverage.map integration_test/data/maps
|
||||
cd integration_test || exit 1
|
||||
|
||||
{
|
||||
echo $'add_path $CURRENTDIR'
|
||||
echo $'add_path $USERDIR'
|
||||
echo $'add_path $DATADIR'
|
||||
echo $'add_path ../data'
|
||||
} > storage.cfg
|
||||
|
||||
if [ "$arg_valgrind_memcheck" == "1" ]; then
|
||||
tool="valgrind --tool=memcheck --gen-suppressions=all --suppressions=memcheck.supp"
|
||||
tool="valgrind --tool=memcheck --gen-suppressions=all --suppressions=../memcheck.supp --track-origins=yes"
|
||||
client_args="cl_menu_map \"\";"
|
||||
else
|
||||
tool=""
|
||||
client_args=""
|
||||
fi
|
||||
|
||||
$tool ./DDNet-Server \
|
||||
$tool ../DDNet-Server \
|
||||
"sv_input_fifo server.fifo;
|
||||
sv_rcon_password rcon;
|
||||
sv_map coverage;
|
||||
sv_sqlite_file ddnet-server.sqlite;
|
||||
sv_port $port" &> server.log || fail server "$?" &
|
||||
|
||||
$tool ./DDNet \
|
||||
$tool ../DDNet \
|
||||
"cl_input_fifo client1.fifo;
|
||||
player_name client1;
|
||||
cl_download_skins 0;
|
||||
|
@ -180,7 +144,7 @@ else
|
|||
sleep 1
|
||||
fi
|
||||
|
||||
$tool ./DDNet \
|
||||
$tool ../DDNet \
|
||||
"cl_input_fifo client2.fifo;
|
||||
player_name client2;
|
||||
cl_download_skins 0;
|
||||
|
@ -192,7 +156,7 @@ fails=0
|
|||
if [ "$arg_valgrind_memcheck" == "1" ]; then
|
||||
tries=120
|
||||
else
|
||||
tries=2
|
||||
tries=50
|
||||
fi
|
||||
# give the client time to launch and create the fifo file
|
||||
# but assume after X secs that the client crashed before
|
||||
|
@ -219,6 +183,63 @@ else
|
|||
sleep 2
|
||||
fi
|
||||
|
||||
echo "[*] test chat and chat commands"
|
||||
echo "say hello world" > client1.fifo
|
||||
echo "rcon_auth rcon" > client1.fifo
|
||||
sleep 1
|
||||
tr -d '\n' > client1.fifo << EOF
|
||||
say "/mc
|
||||
;top5
|
||||
;rank
|
||||
;team 512
|
||||
;emote happy -999
|
||||
;pause
|
||||
;points
|
||||
;mapinfo
|
||||
;list
|
||||
;whisper client2 hi
|
||||
;kill
|
||||
;settings cheats
|
||||
;timeout 123
|
||||
;timer broadcast
|
||||
;cmdlist
|
||||
;saytime"
|
||||
EOF
|
||||
sleep 1
|
||||
echo "[*] test rcon commands"
|
||||
tr -d '\n' > client1.fifo << EOF
|
||||
rcon say hello from admin;
|
||||
rcon broadcast test;
|
||||
rcon status;
|
||||
rcon echo test;
|
||||
muteid 1 900 spam;
|
||||
unban_all;
|
||||
EOF
|
||||
sleep 1
|
||||
|
||||
|
||||
# TODO: remove the first grep after https://github.com/ddnet/ddnet/pull/5036 is merged
|
||||
if ! grep -qE '^\[[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2}\]\[chat\]: 0:-2:client1: hello world$' server.log && \
|
||||
! grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2} D chat: 0:-2:client1: hello world$' server.log
|
||||
then
|
||||
touch fail_chat.txt
|
||||
echo "[-] Error: chat message not found in server log"
|
||||
fi
|
||||
if ! grep -q 'cmdlist' client1.log || \
|
||||
! grep -q 'pause' client1.log || \
|
||||
! grep -q 'rank' client1.log || \
|
||||
! grep -q 'points' client1.log
|
||||
then
|
||||
touch fail_chatcommand.txt
|
||||
echo "[-] Error: did not find output of /cmdlist command"
|
||||
fi
|
||||
|
||||
if ! grep -q "hello from admin" server.log
|
||||
then
|
||||
touch fail_rcon.txt
|
||||
echo "[-] Error: admin message not found in server log"
|
||||
fi
|
||||
|
||||
kill_all
|
||||
wait
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "backend_base.h"
|
||||
#include "engine/shared/image_manipulation.h"
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
|
||||
size_t CCommandProcessorFragment_GLBase::TexFormatToImageColorChannelCount(int TexFormat)
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include <engine/client/backend/glsl_shader_compiler.h>
|
||||
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
|
||||
#ifndef BACKEND_AS_OPENGL_ES
|
||||
#include <GL/glew.h>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <engine/client/backend/opengl/opengl_sl.h>
|
||||
#include <engine/client/backend/opengl/opengl_sl_program.h>
|
||||
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
|
||||
#include <engine/client/backend/glsl_shader_compiler.h>
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
#include <engine/client/backend/backend_base.h>
|
||||
#include <engine/client/backend_sdl.h>
|
||||
#include <engine/client/graphics_threaded.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <base/log.h>
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
#include <base/system.h>
|
||||
|
||||
#include <pnglite.h>
|
||||
|
||||
#include <engine/console.h>
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/storage.h>
|
||||
|
@ -20,8 +20,6 @@
|
|||
#include <game/generated/client_data7.h>
|
||||
#include <game/localization.h>
|
||||
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
|
||||
#if defined(CONF_VIDEORECORDER)
|
||||
#include <engine/shared/video.h>
|
||||
#endif
|
||||
|
@ -135,8 +133,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 +623,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 +797,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);
|
||||
}
|
||||
|
||||
|
|
1
src/engine/external/pnglite/VERSION.txt
vendored
1
src/engine/external/pnglite/VERSION.txt
vendored
|
@ -1 +0,0 @@
|
|||
0.1.17
|
877
src/engine/external/pnglite/pnglite.c
vendored
877
src/engine/external/pnglite/pnglite.c
vendored
|
@ -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 <zlib.h>
|
||||
#else
|
||||
#include "zlite.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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.";
|
||||
};
|
||||
}
|
227
src/engine/external/pnglite/pnglite.h
vendored
227
src/engine/external/pnglite/pnglite.h
vendored
|
@ -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
|
340
src/engine/gfx/image_loader.cpp
Normal file
340
src/engine/gfx/image_loader.cpp
Normal file
|
@ -0,0 +1,340 @@
|
|||
#include "image_loader.h"
|
||||
#include <base/log.h>
|
||||
#include <base/system.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static bool IsUnsupportedByPnglite(png_structp pPNGStruct, png_infop pPNGInfo)
|
||||
{
|
||||
int ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
|
||||
int BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
|
||||
int InterlaceType = png_get_interlace_type(pPNGStruct, pPNGInfo);
|
||||
bool Unsupported = false;
|
||||
switch(ColorType)
|
||||
{
|
||||
case PNG_COLOR_TYPE_GRAY:
|
||||
case PNG_COLOR_TYPE_RGB:
|
||||
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||
break;
|
||||
default:
|
||||
log_error("png", "color type %d unsupported by pnglite", ColorType);
|
||||
Unsupported = true;
|
||||
}
|
||||
|
||||
switch(BitDepth)
|
||||
{
|
||||
case 8:
|
||||
case 16:
|
||||
break;
|
||||
default:
|
||||
log_error("png", "bit depth %d unsupported by pnglite", BitDepth);
|
||||
Unsupported = true;
|
||||
}
|
||||
|
||||
switch(InterlaceType)
|
||||
{
|
||||
case PNG_INTERLACE_NONE:
|
||||
break;
|
||||
default:
|
||||
log_error("png", "interlace type %d unsupported by pnglite", InterlaceType);
|
||||
Unsupported = true;
|
||||
}
|
||||
|
||||
if(png_get_compression_type(pPNGStruct, pPNGInfo) != PNG_COMPRESSION_TYPE_BASE || png_get_filter_type(pPNGStruct, pPNGInfo) != PNG_FILTER_TYPE_BASE)
|
||||
{
|
||||
log_error("png", "non-default compression type or non-default filter type unsupported by pnglite");
|
||||
Unsupported = true;
|
||||
}
|
||||
|
||||
if(Unsupported)
|
||||
{
|
||||
log_error("png", "refusing to load PNG because it would be unsupported by pnglite");
|
||||
}
|
||||
return Unsupported;
|
||||
}
|
||||
|
||||
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 = IsUnsupportedByPnglite(pPNGStruct, pPNGInfo);
|
||||
|
||||
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, height or bit depth 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;
|
||||
}
|
28
src/engine/gfx/image_loader.h
Normal file
28
src/engine/gfx/image_loader.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef ENGINE_GFX_IMAGE_LOADER_H
|
||||
#define ENGINE_GFX_IMAGE_LOADER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
enum EImageFormat
|
||||
{
|
||||
IMAGE_FORMAT_R = 0,
|
||||
IMAGE_FORMAT_RGB,
|
||||
IMAGE_FORMAT_RGBA,
|
||||
};
|
||||
|
||||
typedef std::vector<uint8_t> 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 // ENGINE_GFX_IMAGE_LOADER_H
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef ENGINE_SHARED_IMAGE_MANIPULATION_H
|
||||
#define ENGINE_SHARED_IMAGE_MANIPULATION_H
|
||||
#ifndef ENGINE_GFX_IMAGE_MANIPULATION_H
|
||||
#define ENGINE_GFX_IMAGE_MANIPULATION_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
@ -11,4 +11,4 @@ uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWi
|
|||
|
||||
int HighestBit(int OfVar);
|
||||
|
||||
#endif
|
||||
#endif // ENGINE_GFX_IMAGE_MANIPULATION_H
|
|
@ -415,6 +415,14 @@ CServer::~CServer()
|
|||
free(pCurrentMapData);
|
||||
}
|
||||
|
||||
if(m_RunServer != UNINITIALIZED)
|
||||
{
|
||||
for(auto &Client : m_aClients)
|
||||
{
|
||||
free(Client.m_pPersistentData);
|
||||
}
|
||||
}
|
||||
|
||||
delete m_pRegister;
|
||||
delete m_pConnectionPool;
|
||||
}
|
||||
|
@ -724,7 +732,7 @@ int CServer::Port() const
|
|||
|
||||
int CServer::MaxClients() const
|
||||
{
|
||||
return m_NetServer.MaxClients();
|
||||
return m_RunServer == UNINITIALIZED ? 0 : m_NetServer.MaxClients();
|
||||
}
|
||||
|
||||
int CServer::ClientCount() const
|
||||
|
@ -2908,11 +2916,6 @@ int CServer::Run()
|
|||
m_UPnP.Shutdown();
|
||||
#endif
|
||||
|
||||
for(auto &Client : m_aClients)
|
||||
{
|
||||
free(Client.m_pPersistentData);
|
||||
}
|
||||
|
||||
m_NetServer.Close();
|
||||
|
||||
m_pRegister->OnShutdown();
|
||||
|
|
|
@ -61,9 +61,10 @@ void CFifo::Update()
|
|||
return;
|
||||
|
||||
char aBuf[8192];
|
||||
int Length = read(m_File, aBuf, sizeof(aBuf));
|
||||
int Length = read(m_File, aBuf, sizeof(aBuf) - 1);
|
||||
if(Length <= 0)
|
||||
return;
|
||||
aBuf[Length] = '\0';
|
||||
|
||||
char *pCur = aBuf;
|
||||
for(int i = 0; i < Length; ++i)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <engine/client.h>
|
||||
#include <engine/console.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/input.h>
|
||||
#include <engine/keys.h>
|
||||
|
@ -26,8 +27,6 @@
|
|||
#include <game/generated/client_data.h>
|
||||
#include <game/localization.h>
|
||||
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
|
||||
#include "auto_map.h"
|
||||
#include "editor.h"
|
||||
|
||||
|
|
|
@ -2,70 +2,73 @@
|
|||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
#include <pnglite.h>
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
|
||||
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;
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * sizeof(unsigned char) * 4);
|
||||
Img.m_pData = pImgBuffer;
|
||||
|
||||
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);
|
||||
unsigned char *pBuffer = (unsigned char *)Img.m_pData;
|
||||
|
||||
int w = Png.width;
|
||||
int h = Png.height;
|
||||
int w = Img.m_Width;
|
||||
int h = Img.m_Height;
|
||||
|
||||
DilateImage(pBuffer, w, h, 4);
|
||||
|
||||
// save here
|
||||
File = io_open(pFilename, IOFLAG_WRITE);
|
||||
if(!File)
|
||||
IOHANDLE SaveFile = io_open(pFilename, IOFLAG_WRITE);
|
||||
if(SaveFile)
|
||||
{
|
||||
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);
|
||||
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", "failed to open image file. filename='%s'", pFilename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
#include <game/gamecore.h>
|
||||
#include <game/mapitems.h>
|
||||
|
||||
#include <pnglite.h>
|
||||
/*
|
||||
Usage: map_convert_07 <source map filepath> <dest map filepath>
|
||||
*/
|
||||
|
@ -29,43 +28,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))
|
||||
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_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);
|
||||
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("map_convert_07", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error));
|
||||
free(pBuffer);
|
||||
io_close(File);
|
||||
}
|
||||
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 +184,6 @@ int main(int argc, const char **argv)
|
|||
return -1;
|
||||
}
|
||||
|
||||
png_init(0, 0);
|
||||
|
||||
g_NextDataItemID = g_DataReader.NumData();
|
||||
|
||||
int i = 0;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
// Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
#include <game/mapitems.h>
|
||||
|
||||
#include <pnglite.h>
|
||||
|
||||
bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave)
|
||||
{
|
||||
CDataFileReader Reader;
|
||||
|
@ -52,23 +51,16 @@ 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;
|
||||
}
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// load sounds
|
||||
Reader.GetType(MAPITEMTYPE_SOUND, &Start, &Num);
|
||||
|
@ -123,8 +115,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;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <cstdint>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
#include <engine/storage.h>
|
||||
#include <game/mapitems.h>
|
||||
#include <vector>
|
||||
|
|
668
src/tools/map_replace_area.cpp
Normal file
668
src/tools/map_replace_area.cpp
Normal file
|
@ -0,0 +1,668 @@
|
|||
#include <algorithm>
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
#include <game/gamecore.h>
|
||||
#include <game/mapitems.h>
|
||||
#define INEXISTENT INT_MAX
|
||||
|
||||
// global new layers data (set by ReplaceAreaTiles and ReplaceAreaQuads)
|
||||
void *g_pNewData[1024];
|
||||
void *g_pNewItem[1024];
|
||||
int g_pNewDataSize[1024];
|
||||
|
||||
struct MapObject // quad pivot or tile layer
|
||||
{
|
||||
static constexpr float m_pStandardScreen[2] = {1430 / 2, 1050 / 2};
|
||||
|
||||
float m_pLayerOffset[2];
|
||||
bool m_UseClipping;
|
||||
float m_pClipArea[2][2];
|
||||
float m_pSpeed[2];
|
||||
float m_pScreenOffset[2][2];
|
||||
float m_pBaseArea[2][2]; // adapted to offset
|
||||
float m_pExtendedArea[2][2]; // extended with parallax
|
||||
};
|
||||
|
||||
bool ReplaceArea(IStorage *, const char[][64], const float[][2][2]);
|
||||
bool OpenMaps(IStorage *, const char[][64], CDataFileReader[], CDataFileWriter &);
|
||||
void SaveOutputMap(CDataFileReader &, CDataFileWriter &);
|
||||
bool CompareLayers(const char[][64], CDataFileReader[]);
|
||||
void CompareGroups(const char[][64], CDataFileReader[]);
|
||||
const CMapItemGroup *GetLayerGroup(CDataFileReader &, int);
|
||||
|
||||
void ReplaceAreaTiles(CDataFileReader[], const float[][2][2], const CMapItemGroup *[], CMapItemLayer *[]);
|
||||
void RemoveDestinationTiles(CMapItemLayerTilemap *, CTile *, float[][2]);
|
||||
void ReplaceDestinationTiles(CMapItemLayerTilemap *[], CTile *[], float[][2][2]);
|
||||
bool AdaptVisibleAreas(const float[][2][2], const MapObject[], float[][2][2]);
|
||||
bool AdaptReplaceableAreas(const float[][2][2], const float[][2][2], const MapObject[], float[][2][2]);
|
||||
|
||||
void ReplaceAreaQuads(CDataFileReader[], const float[][2][2], const CMapItemGroup *[], CMapItemLayer *[], int);
|
||||
bool RemoveDestinationQuads(const float[][2], const CQuad *, int, const CMapItemGroup *, CQuad *, int &);
|
||||
bool InsertDestinationQuads(const float[][2][2], const CQuad *, int, const CMapItemGroup *[], CQuad *, int &);
|
||||
bool AdaptVisiblePoint(const float[][2][2], const float[][2], const MapObject[], float[]);
|
||||
|
||||
MapObject CreateMapObject(const CMapItemGroup *, int, int, int, int);
|
||||
void SetExtendedArea(MapObject &);
|
||||
bool GetVisibleArea(const float[][2], MapObject, float[][2] = 0x0);
|
||||
bool GetReplaceableArea(const float[][2], MapObject, float[][2]);
|
||||
|
||||
void GetGameAreaDistance(const float[][2][2], const MapObject[], const float[][2][2], float[]);
|
||||
void GetGameAreaDistance(const float[][2][2], const MapObject[], const float[][2], float[]);
|
||||
void GetSignificantScreenPos(MapObject, const float[][2], const float[][2], float[]);
|
||||
void ConvertToTiles(const float[][2], int[][2]);
|
||||
|
||||
bool GetLineIntersection(const float[], const float[], float[] = 0x0);
|
||||
bool GetLineIntersection(const float[], float);
|
||||
void SetInexistent(float *, int);
|
||||
bool IsInexistent(const float *, int);
|
||||
bool IsInexistent(float);
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
CCmdlineFix CmdlineFix(&argc, &argv);
|
||||
log_set_global_logger_default();
|
||||
|
||||
if(argc != 10)
|
||||
{
|
||||
dbg_msg("map_replace_area", "Invalid arguments");
|
||||
dbg_msg("map_replace_area", "Usage: %s <from_map> <from_x> <from_y> <to_map> <to_x> <to_y> <width> <height> <output_map>", argv[0]);
|
||||
dbg_msg("map_replace_area", "Note: use game layer tiles as a reference for both coordinates and sizes");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
char pMapNames[3][64];
|
||||
snprintf(pMapNames[0], 64, "%s", argv[1]); //from_map
|
||||
snprintf(pMapNames[1], 64, "%s", argv[4]); //to_map
|
||||
snprintf(pMapNames[2], 64, "%s", argv[9]); //output_map
|
||||
|
||||
float pGameAreas[2][2][2];
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
pGameAreas[i][0][0] = atof(argv[2 + i * 3]) * 32; //x
|
||||
pGameAreas[i][1][0] = atof(argv[3 + i * 3]) * 32; //y
|
||||
pGameAreas[i][0][1] = pGameAreas[i][0][0] + atof(argv[7]) * 32; //x + width
|
||||
pGameAreas[i][1][1] = pGameAreas[i][1][0] + atof(argv[8]) * 32; //y + height
|
||||
}
|
||||
|
||||
cmdline_free(argc, argv);
|
||||
|
||||
dbg_msg("map_replace_area", "from_map='%s'; to_map='%s'; from_area='%fx,%fy'; to_area='%fx,%fy'; area_width='%fpx'; area_heigth='%fpx'; output_map='%s'",
|
||||
pMapNames[0], pMapNames[1], pGameAreas[0][0][0], pGameAreas[0][1][0], pGameAreas[1][0][0], pGameAreas[1][1][0],
|
||||
pGameAreas[0][0][1] - pGameAreas[0][0][0], pGameAreas[0][1][1] - pGameAreas[0][1][0], pMapNames[2]);
|
||||
|
||||
IStorage *pStorage = CreateLocalStorage();
|
||||
for(int i = 0; i < 1024; i++)
|
||||
{
|
||||
g_pNewData[i] = g_pNewItem[i] = 0;
|
||||
g_pNewDataSize[i] = 0;
|
||||
}
|
||||
|
||||
return ReplaceArea(pStorage, pMapNames, pGameAreas) ? 0 : 1;
|
||||
}
|
||||
|
||||
bool ReplaceArea(IStorage *pStorage, const char pMapNames[3][64], const float pGameAreas[][2][2])
|
||||
{
|
||||
CDataFileReader InputMaps[2];
|
||||
CDataFileWriter OutputMap;
|
||||
|
||||
if(!OpenMaps(pStorage, pMapNames, InputMaps, OutputMap))
|
||||
return false;
|
||||
if(!CompareLayers(pMapNames, InputMaps))
|
||||
return false;
|
||||
CompareGroups(pMapNames, InputMaps);
|
||||
|
||||
int LayersStart[2], LayersCount;
|
||||
for(int i = 0; i < 2; i++)
|
||||
InputMaps[i].GetType(MAPITEMTYPE_LAYER, &LayersStart[i], &LayersCount);
|
||||
|
||||
for(int i = 0; i < LayersCount; i++)
|
||||
{
|
||||
const CMapItemGroup *pLayerGroups[2];
|
||||
CMapItemLayer *pItem[2];
|
||||
for(int j = 0; j < 2; j++)
|
||||
{
|
||||
pLayerGroups[j] = GetLayerGroup(InputMaps[j], i + 1);
|
||||
pItem[j] = (CMapItemLayer *)InputMaps[j].GetItem(LayersStart[j] + i, 0, 0);
|
||||
}
|
||||
|
||||
if(!pLayerGroups[0] || !pLayerGroups[1])
|
||||
continue;
|
||||
|
||||
if(pItem[0]->m_Type == LAYERTYPE_TILES)
|
||||
ReplaceAreaTiles(InputMaps, pGameAreas, pLayerGroups, pItem);
|
||||
else if(pItem[0]->m_Type == LAYERTYPE_QUADS)
|
||||
ReplaceAreaQuads(InputMaps, pGameAreas, pLayerGroups, pItem, LayersStart[1] + i);
|
||||
}
|
||||
|
||||
SaveOutputMap(InputMaps[1], OutputMap);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenMaps(IStorage *pStorage, const char pMapNames[3][64], CDataFileReader InputMaps[2], CDataFileWriter &OutputMap)
|
||||
{
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(!InputMaps[i].Open(pStorage, pMapNames[i], IStorage::TYPE_ABSOLUTE))
|
||||
{
|
||||
dbg_msg("map_replace_area", "ERROR: unable to open map '%s'", pMapNames[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!OutputMap.Open(pStorage, pMapNames[2], IStorage::TYPE_ABSOLUTE))
|
||||
{
|
||||
dbg_msg("map_replace_area", "ERROR: unable to open map '%s'", pMapNames[2]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SaveOutputMap(CDataFileReader &InputMap, CDataFileWriter &OutputMap)
|
||||
{
|
||||
for(int i = 0; i < InputMap.NumItems(); i++)
|
||||
{
|
||||
int ID, Type;
|
||||
void *pItem = InputMap.GetItem(i, &Type, &ID);
|
||||
|
||||
if(Type == ITEMTYPE_EX)
|
||||
continue;
|
||||
if(g_pNewItem[i])
|
||||
pItem = g_pNewItem[i];
|
||||
|
||||
int Size = InputMap.GetItemSize(i);
|
||||
OutputMap.AddItem(Type, ID, Size, pItem);
|
||||
}
|
||||
|
||||
for(int i = 0; i < InputMap.NumData(); i++)
|
||||
{
|
||||
void *pData = g_pNewData[i] ? g_pNewData[i] : InputMap.GetData(i);
|
||||
int Size = g_pNewDataSize[i] ? g_pNewDataSize[i] : InputMap.GetDataSize(i);
|
||||
OutputMap.AddData(Size, pData);
|
||||
}
|
||||
|
||||
OutputMap.Finish();
|
||||
}
|
||||
|
||||
bool CompareLayers(const char pMapNames[3][64], CDataFileReader InputMaps[2])
|
||||
{
|
||||
int Start[2], Num[2];
|
||||
for(int i = 0; i < 2; i++)
|
||||
InputMaps[i].GetType(MAPITEMTYPE_LAYER, &Start[i], &Num[i]);
|
||||
|
||||
if(Num[0] != Num[1])
|
||||
{
|
||||
dbg_msg("map_replace_area", "ERROR: different layers quantity");
|
||||
for(int i = 0; i < 2; i++)
|
||||
dbg_msg("map_replace_area", " \"%s\": %d layers", pMapNames[i], Num[i]);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < Num[0]; i++)
|
||||
{
|
||||
CMapItemLayer *pItem[2];
|
||||
for(int j = 0; j < 2; j++)
|
||||
pItem[j] = (CMapItemLayer *)InputMaps[j].GetItem(Start[j] + i, 0, 0);
|
||||
|
||||
if(pItem[0]->m_Type != pItem[1]->m_Type)
|
||||
{
|
||||
dbg_msg("map_replace_area", "ERROR: different types on layer #%d", i);
|
||||
for(int j = 0; j < 2; j++)
|
||||
dbg_msg("map_replace_area", " \"%s\": %s", pMapNames[j], pItem[j]->m_Type == LAYERTYPE_TILES ? "tiles layer" : "quad layer");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompareGroups(const char pMapNames[3][64], CDataFileReader InputMaps[2])
|
||||
{
|
||||
int Start[2], Num[2];
|
||||
for(int i = 0; i < 2; i++)
|
||||
InputMaps[i].GetType(MAPITEMTYPE_GROUP, &Start[i], &Num[i]);
|
||||
|
||||
for(int i = 0; i < std::max(Num[0], Num[1]); i++)
|
||||
{
|
||||
CMapItemGroup *pItem[2];
|
||||
for(int j = 0; j < 2; j++)
|
||||
pItem[j] = (CMapItemGroup *)InputMaps[j].GetItem(Start[j] + i, 0, 0);
|
||||
|
||||
bool bSameConfig = pItem[0]->m_ParallaxX == pItem[1]->m_ParallaxX && pItem[0]->m_ParallaxY == pItem[1]->m_ParallaxY && pItem[0]->m_OffsetX == pItem[1]->m_OffsetX && pItem[0]->m_OffsetY == pItem[1]->m_OffsetY && pItem[0]->m_UseClipping == pItem[1]->m_UseClipping && pItem[0]->m_ClipX == pItem[1]->m_ClipX && pItem[0]->m_ClipY == pItem[1]->m_ClipY && pItem[0]->m_ClipW == pItem[1]->m_ClipW && pItem[0]->m_ClipH == pItem[1]->m_ClipH;
|
||||
|
||||
if(!bSameConfig)
|
||||
dbg_msg("map_replace_area", "WARNING: different configuration on layergroup #%d, this might lead to unexpected results", i);
|
||||
}
|
||||
}
|
||||
|
||||
const CMapItemGroup *GetLayerGroup(CDataFileReader &InputMap, const int LayerNumber)
|
||||
{
|
||||
int Start, Num;
|
||||
InputMap.GetType(MAPITEMTYPE_GROUP, &Start, &Num);
|
||||
|
||||
for(int i = 0; i < Num; i++)
|
||||
{
|
||||
CMapItemGroup *pItem = (CMapItemGroup *)InputMap.GetItem(Start + i, 0, 0);
|
||||
if(LayerNumber >= pItem->m_StartLayer && LayerNumber <= pItem->m_StartLayer + pItem->m_NumLayers)
|
||||
return pItem;
|
||||
}
|
||||
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
void ReplaceAreaTiles(CDataFileReader InputMaps[2], const float pGameAreas[][2][2], const CMapItemGroup *pLayerGroups[2], CMapItemLayer *pItem[2])
|
||||
{
|
||||
CMapItemLayerTilemap *pTilemap[2];
|
||||
CTile *pTile[2];
|
||||
float pVisibleAreas[2][2][2], pReplaceableAreas[2][2][2];
|
||||
MapObject Obs[2];
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
pTilemap[i] = (CMapItemLayerTilemap *)pItem[i];
|
||||
pTile[i] = (CTile *)InputMaps[i].GetData(pTilemap[i]->m_Data);
|
||||
Obs[i] = CreateMapObject(pLayerGroups[i], 0, 0, pTilemap[i]->m_Width * 32, pTilemap[i]->m_Height * 32);
|
||||
}
|
||||
|
||||
if(!GetVisibleArea(pGameAreas[1], Obs[1], pVisibleAreas[1]))
|
||||
return;
|
||||
|
||||
GetReplaceableArea(pVisibleAreas[1], Obs[1], pReplaceableAreas[1]);
|
||||
RemoveDestinationTiles(pTilemap[1], pTile[1], pReplaceableAreas[1]);
|
||||
|
||||
if(GetVisibleArea(pGameAreas[0], Obs[0], pVisibleAreas[0]) && AdaptVisibleAreas(pGameAreas, Obs, pVisibleAreas))
|
||||
{
|
||||
for(int i = 0; i < 2; i++)
|
||||
GetReplaceableArea(pVisibleAreas[i], Obs[i], pReplaceableAreas[i]);
|
||||
|
||||
if(AdaptReplaceableAreas(pGameAreas, pVisibleAreas, Obs, pReplaceableAreas))
|
||||
ReplaceDestinationTiles(pTilemap, pTile, pReplaceableAreas);
|
||||
}
|
||||
|
||||
g_pNewData[pTilemap[1]->m_Data] = pTile[1];
|
||||
}
|
||||
|
||||
void RemoveDestinationTiles(CMapItemLayerTilemap *pTilemap, CTile *pTile, float pReplaceableArea[2][2])
|
||||
{
|
||||
int pRange[2][2];
|
||||
ConvertToTiles(pReplaceableArea, pRange);
|
||||
|
||||
CTile EmptyTile;
|
||||
EmptyTile.m_Index = EmptyTile.m_Flags = EmptyTile.m_Skip = EmptyTile.m_Reserved = 0;
|
||||
|
||||
for(int y = pRange[1][0]; y < pRange[1][1]; y++)
|
||||
for(int x = pRange[0][0]; x < pRange[0][1]; x++)
|
||||
pTile[x + (y * pTilemap->m_Width)] = EmptyTile;
|
||||
}
|
||||
|
||||
void ReplaceDestinationTiles(CMapItemLayerTilemap *pTilemap[2], CTile *pTile[2], float pReplaceableAreas[2][2][2])
|
||||
{
|
||||
int pRanges[2][2][2];
|
||||
for(int i = 0; i < 2; i++)
|
||||
ConvertToTiles(pReplaceableAreas[i], pRanges[i]);
|
||||
|
||||
for(int y0 = pRanges[0][1][0], y1 = pRanges[1][1][0]; y0 < pRanges[0][1][1] && y1 < pRanges[1][1][1]; y0++, y1++)
|
||||
for(int x0 = pRanges[0][0][0], x1 = pRanges[1][0][0]; x0 < pRanges[0][0][1] && x1 < pRanges[1][0][1]; x0++, x1++)
|
||||
pTile[1][x1 + (y1 * pTilemap[1]->m_Width)] = pTile[0][x0 + (y0 * pTilemap[0]->m_Width)];
|
||||
}
|
||||
|
||||
bool AdaptVisibleAreas(const float pGameAreas[2][2][2], const MapObject Obs[2], float pVisibleAreas[2][2][2])
|
||||
{
|
||||
float pDistance[2];
|
||||
GetGameAreaDistance(pGameAreas, Obs, pVisibleAreas, pDistance);
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(Obs[0].m_pSpeed[i] == 1 || Obs[1].m_pSpeed[i] == 1)
|
||||
continue;
|
||||
|
||||
for(int j = 0; j < 2; j++)
|
||||
pVisibleAreas[1][i][j] -= pDistance[i];
|
||||
|
||||
if(!GetLineIntersection(pVisibleAreas[0][i], pVisibleAreas[1][i], pVisibleAreas[0][i]))
|
||||
return false;
|
||||
|
||||
for(int j = 0; j < 2; j++)
|
||||
pVisibleAreas[1][i][j] = pVisibleAreas[0][i][j] + pDistance[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdaptReplaceableAreas(const float pGameAreas[2][2][2], const float pVisibleAreas[2][2][2], const MapObject Obs[2], float pReplaceableAreas[2][2][2])
|
||||
{
|
||||
float pDistance[2], pScreenPos[2];
|
||||
GetGameAreaDistance(pGameAreas, Obs, pVisibleAreas, pDistance);
|
||||
GetSignificantScreenPos(Obs[0], pVisibleAreas[0], pReplaceableAreas[0], pScreenPos);
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
float pDestLine[2], pSourceLine[2], pVisibleLine[2];
|
||||
|
||||
pDestLine[0] = Obs[1].m_pBaseArea[i][0] + (pScreenPos[i] + pDistance[i]) * Obs[1].m_pSpeed[i];
|
||||
pDestLine[1] = pDestLine[0] + (Obs[1].m_pBaseArea[i][1] - Obs[1].m_pBaseArea[i][0]);
|
||||
|
||||
if(!GetLineIntersection(pDestLine, pVisibleAreas[1][i], pVisibleLine))
|
||||
return false;
|
||||
|
||||
pSourceLine[0] = pVisibleAreas[0][i][0] + pDistance[i] - pReplaceableAreas[0][i][0];
|
||||
pSourceLine[1] = pVisibleAreas[0][i][1] + pDistance[i] + pReplaceableAreas[0][i][1] - pReplaceableAreas[0][i][0];
|
||||
|
||||
if(!GetLineIntersection(pSourceLine, pVisibleLine, pVisibleLine))
|
||||
return false;
|
||||
|
||||
pReplaceableAreas[0][i][0] = pVisibleLine[0] - pSourceLine[0];
|
||||
pReplaceableAreas[1][i][0] = pVisibleLine[0] - pDestLine[0];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReplaceAreaQuads(CDataFileReader InputMaps[2], const float pGameAreas[][2][2], const CMapItemGroup *pLayerGroups[2], CMapItemLayer *pItem[2], const int ItemNumber)
|
||||
{
|
||||
CMapItemLayerQuads *pQuadLayer[2];
|
||||
for(int i = 0; i < 2; i++)
|
||||
pQuadLayer[i] = (CMapItemLayerQuads *)pItem[i];
|
||||
|
||||
CQuad *pQuads[3];
|
||||
for(int i = 0; i < 2; i++)
|
||||
pQuads[i] = (CQuad *)InputMaps[i].GetDataSwapped(pQuadLayer[i]->m_Data);
|
||||
|
||||
pQuads[2] = new CQuad[pQuadLayer[0]->m_NumQuads + pQuadLayer[1]->m_NumQuads];
|
||||
int QuadsCounter = 0;
|
||||
|
||||
bool bDataChanged = RemoveDestinationQuads(pGameAreas[1], pQuads[1], pQuadLayer[1]->m_NumQuads, pLayerGroups[1], pQuads[2], QuadsCounter);
|
||||
bDataChanged |= InsertDestinationQuads(pGameAreas, pQuads[0], pQuadLayer[0]->m_NumQuads, pLayerGroups, pQuads[2], QuadsCounter);
|
||||
|
||||
if(bDataChanged)
|
||||
{
|
||||
g_pNewData[pQuadLayer[1]->m_Data] = pQuads[2];
|
||||
g_pNewDataSize[pQuadLayer[1]->m_Data] = ((int)sizeof(CQuad)) * QuadsCounter;
|
||||
pQuadLayer[1]->m_NumQuads = QuadsCounter;
|
||||
g_pNewItem[ItemNumber] = pItem[1];
|
||||
}
|
||||
else
|
||||
delete[] pQuads[2];
|
||||
}
|
||||
|
||||
bool RemoveDestinationQuads(const float pGameArea[2][2], const CQuad *pQuads, const int NumQuads, const CMapItemGroup *pLayerGroup, CQuad *pDestQuads, int &QuadsCounter)
|
||||
{
|
||||
bool bDataChanged = false;
|
||||
|
||||
for(int i = 0; i < NumQuads; i++)
|
||||
{
|
||||
MapObject Ob = CreateMapObject(pLayerGroup, fx2f(pQuads[i].m_aPoints[4].x), fx2f(pQuads[i].m_aPoints[4].y), 0, 0);
|
||||
|
||||
if(GetVisibleArea(pGameArea, Ob))
|
||||
{
|
||||
bDataChanged = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pDestQuads[QuadsCounter] = pQuads[i];
|
||||
QuadsCounter++;
|
||||
}
|
||||
|
||||
return bDataChanged;
|
||||
}
|
||||
|
||||
bool InsertDestinationQuads(const float pGameAreas[2][2][2], const CQuad *pQuads, const int NumQuads, const CMapItemGroup *pLayerGroups[2], CQuad *pDestQuads, int &QuadsCounter)
|
||||
{
|
||||
bool bDataChanged = false;
|
||||
|
||||
for(int i = 0; i < NumQuads; i++)
|
||||
{
|
||||
MapObject Obs[2];
|
||||
Obs[0] = CreateMapObject(pLayerGroups[0], fx2f(pQuads[i].m_aPoints[4].x), fx2f(pQuads[i].m_aPoints[4].y), 0, 0);
|
||||
float pVisibleArea[2][2];
|
||||
|
||||
if(GetVisibleArea(pGameAreas[0], Obs[0], pVisibleArea))
|
||||
{
|
||||
float pQuadPos[2];
|
||||
Obs[1] = CreateMapObject(pLayerGroups[1], 0, 0, 0, 0);
|
||||
|
||||
if(!AdaptVisiblePoint(pGameAreas, pVisibleArea, Obs, pQuadPos))
|
||||
continue;
|
||||
|
||||
pDestQuads[QuadsCounter] = pQuads[i];
|
||||
for(auto &m_aPoint : pDestQuads[QuadsCounter].m_aPoints)
|
||||
{
|
||||
m_aPoint.x += f2fx(pQuadPos[0]) - pDestQuads[QuadsCounter].m_aPoints[4].x;
|
||||
m_aPoint.y += f2fx(pQuadPos[1]) - pDestQuads[QuadsCounter].m_aPoints[4].y;
|
||||
}
|
||||
|
||||
QuadsCounter++;
|
||||
bDataChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bDataChanged;
|
||||
}
|
||||
|
||||
bool AdaptVisiblePoint(const float pGameAreas[2][2][2], const float pVisibleArea[2][2], const MapObject Obs[2], float pPos[2])
|
||||
{
|
||||
float pDistance[2], pScreenPos[2];
|
||||
GetGameAreaDistance(pGameAreas, Obs, pVisibleArea, pDistance);
|
||||
GetSignificantScreenPos(Obs[0], pVisibleArea, 0x0, pScreenPos);
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
pPos[i] = pVisibleArea[i][0] + pDistance[i] + Obs[1].m_pLayerOffset[i] - (pScreenPos[i] + pDistance[i]) * Obs[1].m_pSpeed[i];
|
||||
|
||||
MapObject FinalOb = Obs[1];
|
||||
for(int i = 0; i < 2; i++)
|
||||
FinalOb.m_pBaseArea[i][0] = FinalOb.m_pBaseArea[i][1] += pPos[i];
|
||||
SetExtendedArea(FinalOb);
|
||||
|
||||
return GetVisibleArea(pGameAreas[1], FinalOb);
|
||||
}
|
||||
|
||||
MapObject CreateMapObject(const CMapItemGroup *pLayerGroup, const int PosX, const int PosY, const int Width, const int Height)
|
||||
{
|
||||
MapObject Ob;
|
||||
|
||||
Ob.m_pBaseArea[0][0] = PosX - pLayerGroup->m_OffsetX;
|
||||
Ob.m_pBaseArea[1][0] = PosY - pLayerGroup->m_OffsetY;
|
||||
Ob.m_pBaseArea[0][1] = Ob.m_pBaseArea[0][0] + Width;
|
||||
Ob.m_pBaseArea[1][1] = Ob.m_pBaseArea[1][0] + Height;
|
||||
Ob.m_pLayerOffset[0] = pLayerGroup->m_OffsetX;
|
||||
Ob.m_pLayerOffset[1] = pLayerGroup->m_OffsetY;
|
||||
Ob.m_UseClipping = pLayerGroup->m_UseClipping;
|
||||
Ob.m_pClipArea[0][0] = pLayerGroup->m_ClipX;
|
||||
Ob.m_pClipArea[1][0] = pLayerGroup->m_ClipY;
|
||||
Ob.m_pClipArea[0][1] = pLayerGroup->m_ClipX + pLayerGroup->m_ClipW;
|
||||
Ob.m_pClipArea[1][1] = pLayerGroup->m_ClipY + pLayerGroup->m_ClipH;
|
||||
Ob.m_pSpeed[0] = 1 - (pLayerGroup->m_ParallaxX / 100.0f);
|
||||
Ob.m_pSpeed[1] = 1 - (pLayerGroup->m_ParallaxY / 100.0f);
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
Ob.m_pScreenOffset[i][0] = -Ob.m_pStandardScreen[i];
|
||||
Ob.m_pScreenOffset[i][1] = Ob.m_pStandardScreen[i];
|
||||
if(Ob.m_pSpeed[i] < 0)
|
||||
std::swap(Ob.m_pScreenOffset[i][0], Ob.m_pScreenOffset[i][1]);
|
||||
}
|
||||
|
||||
SetExtendedArea(Ob);
|
||||
return Ob;
|
||||
}
|
||||
|
||||
void SetExtendedArea(MapObject &Ob)
|
||||
{
|
||||
SetInexistent((float *)Ob.m_pExtendedArea, 4);
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(Ob.m_pSpeed[i] == 1)
|
||||
{
|
||||
float pInspectedArea[2];
|
||||
if(GetLineIntersection(Ob.m_pBaseArea[i], Ob.m_pScreenOffset[i], pInspectedArea))
|
||||
memcpy(Ob.m_pExtendedArea[i], pInspectedArea, sizeof(float[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int j = 0; j < 2; j++)
|
||||
Ob.m_pExtendedArea[i][j] = (Ob.m_pBaseArea[i][j] + Ob.m_pScreenOffset[i][j] * Ob.m_pSpeed[i]) / (1 - Ob.m_pSpeed[i]);
|
||||
|
||||
if(Ob.m_pExtendedArea[i][0] > Ob.m_pExtendedArea[i][1])
|
||||
std::swap(Ob.m_pExtendedArea[i][0], Ob.m_pExtendedArea[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetVisibleArea(const float pGameArea[2][2], const MapObject Ob, float pVisibleArea[2][2])
|
||||
{
|
||||
if(IsInexistent((float *)Ob.m_pExtendedArea, 4))
|
||||
return false;
|
||||
|
||||
if(pVisibleArea)
|
||||
SetInexistent((float *)pVisibleArea, 4);
|
||||
|
||||
float pInspectedArea[2][2];
|
||||
memcpy(pInspectedArea, pGameArea, sizeof(float[2][2]));
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(Ob.m_pSpeed[i] == 1)
|
||||
{
|
||||
memcpy(pInspectedArea[i], Ob.m_pExtendedArea[i], sizeof(float[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Ob.m_UseClipping && !GetLineIntersection(pInspectedArea[i], Ob.m_pClipArea[i], pInspectedArea[i]))
|
||||
return false;
|
||||
|
||||
if(!GetLineIntersection(pInspectedArea[i], Ob.m_pExtendedArea[i], pInspectedArea[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
if(pVisibleArea)
|
||||
memcpy(pVisibleArea, pInspectedArea, sizeof(float[2][2]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetReplaceableArea(const float pVisibleArea[2][2], const MapObject Ob, float pReplaceableArea[2][2])
|
||||
{
|
||||
SetInexistent((float *)pReplaceableArea, 4);
|
||||
if(IsInexistent((float *)pVisibleArea, 4))
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(Ob.m_pSpeed[i] == 1)
|
||||
{
|
||||
pReplaceableArea[i][0] = pVisibleArea[i][0] - Ob.m_pBaseArea[i][0];
|
||||
pReplaceableArea[i][1] = pVisibleArea[i][1] - Ob.m_pBaseArea[i][0];
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int j = 0; j < 2; j++)
|
||||
{
|
||||
float pVisibleLine[2], pReplaceableLine[2];
|
||||
int k = Ob.m_pSpeed[i] > 1 ? !j : j;
|
||||
|
||||
pVisibleLine[0] = Ob.m_pBaseArea[i][0] + (pVisibleArea[i][j] - Ob.m_pScreenOffset[i][k]) * Ob.m_pSpeed[i];
|
||||
pVisibleLine[1] = pVisibleLine[0] + Ob.m_pBaseArea[i][1] - Ob.m_pBaseArea[i][0];
|
||||
|
||||
if(GetLineIntersection(pVisibleArea[i], pVisibleLine, pReplaceableLine))
|
||||
pReplaceableArea[i][k] = pReplaceableLine[j] - pVisibleLine[0];
|
||||
else
|
||||
pReplaceableArea[i][k] = k * (Ob.m_pBaseArea[i][1] - Ob.m_pBaseArea[i][0]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetGameAreaDistance(const float pGameAreas[2][2][2], const MapObject Obs[2], const float pVisibleAreas[2][2][2], float pDistance[2])
|
||||
{
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(Obs[0].m_pSpeed[i] == 1 && Obs[1].m_pSpeed[i] == 1)
|
||||
pDistance[i] = 0;
|
||||
else if(Obs[0].m_pSpeed[i] == 1 && Obs[1].m_pSpeed[i] != 1)
|
||||
pDistance[i] = pGameAreas[1][i][0] - pVisibleAreas[0][i][0];
|
||||
else if(Obs[0].m_pSpeed[i] != 1 && Obs[1].m_pSpeed[i] == 1)
|
||||
pDistance[i] = pVisibleAreas[1][i][0] - pGameAreas[0][i][0];
|
||||
else
|
||||
pDistance[i] = pGameAreas[1][i][0] - pGameAreas[0][i][0];
|
||||
}
|
||||
}
|
||||
|
||||
void GetGameAreaDistance(const float pGameAreas[2][2][2], const MapObject Obs[2], const float pVisibleArea[2][2], float pDistance[2])
|
||||
{
|
||||
float pVisibleAreas[2][2][2];
|
||||
memcpy(pVisibleAreas[0], pVisibleArea[0], sizeof(float[2][2]));
|
||||
memcpy(pVisibleAreas[1], pVisibleArea[0], sizeof(float[2][2]));
|
||||
GetGameAreaDistance(pGameAreas, Obs, pVisibleAreas, pDistance);
|
||||
}
|
||||
|
||||
void GetSignificantScreenPos(const MapObject Ob, const float pVisibleArea[2][2], const float pReplaceableArea[2][2], float pScreen[2])
|
||||
{
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
if(!Ob.m_pSpeed[i])
|
||||
{
|
||||
pScreen[i] = pVisibleArea[i][0] + Ob.m_pScreenOffset[i][1];
|
||||
continue;
|
||||
}
|
||||
|
||||
float BaseOffset = pReplaceableArea ? pReplaceableArea[i][0] : 0;
|
||||
pScreen[i] = (pVisibleArea[i][0] - Ob.m_pBaseArea[i][0] - BaseOffset) / Ob.m_pSpeed[i];
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertToTiles(const float pArea[2][2], int pTiles[2][2])
|
||||
{
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
pTiles[i][0] = floor((floor(pArea[i][0] * 100.0f) / 100.0f) / 32.0f);
|
||||
pTiles[i][1] = ceil((floor(pArea[i][1] * 100.0f) / 100.0f) / 32.0f);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetLineIntersection(const float pLine1[2], const float pLine2[2], float pIntersection[2])
|
||||
{
|
||||
float pBorders[2] = {
|
||||
std::max(pLine1[0], pLine2[0]),
|
||||
std::min(pLine1[1], pLine2[1])};
|
||||
|
||||
if(pIntersection)
|
||||
SetInexistent((float *)pIntersection, 2);
|
||||
|
||||
if(pBorders[0] - pBorders[1] > 0.01f)
|
||||
return false;
|
||||
|
||||
if(pIntersection)
|
||||
memcpy(pIntersection, pBorders, sizeof(float[2]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetLineIntersection(const float pLine[2], const float Point)
|
||||
{
|
||||
return pLine[0] - Point <= 0.01f && pLine[1] - Point >= 0.01f;
|
||||
}
|
||||
|
||||
void SetInexistent(float *pArray, const int Count)
|
||||
{
|
||||
for(int i = 0; i < Count; i++)
|
||||
pArray[i] = (float)INEXISTENT;
|
||||
}
|
||||
|
||||
bool IsInexistent(const float *pArray, const int Count)
|
||||
{
|
||||
for(int i = 0; i < Count; i++)
|
||||
if((float)pArray[i] == (float)INEXISTENT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsInexistent(const float Value)
|
||||
{
|
||||
return Value == INEXISTENT;
|
||||
}
|
|
@ -3,12 +3,11 @@
|
|||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
#include <game/mapitems.h>
|
||||
|
||||
#include <pnglite.h>
|
||||
/*
|
||||
Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>
|
||||
Notes: map filepath must be relative to user default teeworlds folder
|
||||
|
@ -26,52 +25,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)
|
||||
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(Png.color_type == PNG_TRUECOLOR_ALPHA)
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA) // ignore_convention
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
else
|
||||
{
|
||||
free(pBuffer);
|
||||
free(pImgBuffer);
|
||||
return 0;
|
||||
}
|
||||
pImg->m_pData = pBuffer;
|
||||
}
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -146,8 +137,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++)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue