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:
bors[bot] 2022-06-21 16:21:47 +00:00 committed by GitHub
commit b75289df35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1442 additions and 1501 deletions

View file

@ -58,7 +58,7 @@ jobs:
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get upgrade -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) - name: Prepare Linux (non-fancy)
if: ${{ contains(matrix.os, 'ubuntu') && !matrix.fancy }} if: ${{ contains(matrix.os, 'ubuntu') && !matrix.fancy }}
@ -176,7 +176,8 @@ jobs:
- name: Run integration tests with Valgrind's Memcheck - name: Run integration tests with Valgrind's Memcheck
if: contains(matrix.os, 'ubuntu') if: contains(matrix.os, 'ubuntu')
run: | run: |
scripts/integration_test.sh --valgrind-memcheck headless cd headless
./integration_test.sh --valgrind-memcheck
- name: Package - name: Package
run: | run: |

View file

@ -36,6 +36,7 @@ jobs:
cd san cd san
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=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 "cl_download_skins 0;quit" || true
./DDNet-Server shutdown || true ./DDNet-Server shutdown || true
if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)" if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)"
@ -45,4 +46,5 @@ jobs:
fi fi
- name: Run integration tests with ASan and UBSan - name: Run integration tests with ASan and UBSan
run: | run: |
scripts/integration_test.sh san cd san
make run_integration_tests

View file

@ -545,7 +545,7 @@ endif()
find_package(Ogg) find_package(Ogg)
find_package(Opus) find_package(Opus)
find_package(Opusfile) find_package(Opusfile)
find_package(Pnglite) find_package(PNG)
find_package(PythonInterp 3) find_package(PythonInterp 3)
find_package(SDL2) find_package(SDL2)
find_package(SQLite3) find_package(SQLite3)
@ -631,7 +631,7 @@ show_dependency_status("Ogg" OGG)
show_dependency_status("OpenSSL Crypto" CRYPTO) show_dependency_status("OpenSSL Crypto" CRYPTO)
show_dependency_status("Opus" OPUS) show_dependency_status("Opus" OPUS)
show_dependency_status("Opusfile" OPUSFILE) show_dependency_status("Opusfile" OPUSFILE)
show_dependency_status("Pnglite" PNGLITE) show_dependency_status("PNG" PNG)
show_dependency_status("PythonInterp" PYTHONINTERP) show_dependency_status("PythonInterp" PYTHONINTERP)
show_dependency_status("SDL2" SDL2) show_dependency_status("SDL2" SDL2)
show_dependency_status("SQLite3" SQLite3) 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") message(SEND_ERROR "You must enable the DISCORD flag if you want to link the Discord SDK")
endif() endif()
endif() endif()
if(NOT(PNG_FOUND))
message(SEND_ERROR "You must install libpng to compile DDNet")
endif()
if(CLIENT AND NOT(FREETYPE_FOUND)) if(CLIENT AND NOT(FREETYPE_FOUND))
message(SEND_ERROR "You must install Freetype to compile the ${CMAKE_PROJECT_NAME} client") message(SEND_ERROR "You must install Freetype to compile the ${CMAKE_PROJECT_NAME} client")
endif() endif()
@ -1368,6 +1371,7 @@ set(EXPECTED_DATA
maps/Sunny\ Side\ Up.map maps/Sunny\ Side\ Up.map
maps/Tsunami.map maps/Tsunami.map
maps/Tutorial.map maps/Tutorial.map
maps/coverage.map
maps/ctf1.map maps/ctf1.map
maps/ctf2.map maps/ctf2.map
maps/ctf3.map maps/ctf3.map
@ -1558,10 +1562,18 @@ foreach(datafile ${DATA})
file(MAKE_DIRECTORY ${DESTINATION}) file(MAKE_DIRECTORY ${DESTINATION})
file(COPY ${datafile} DESTINATION ${DESTINATION}) file(COPY ${datafile} DESTINATION ${DESTINATION})
endforeach() endforeach()
set(INTEGRATION_TEST_COPY_FILES
lsan.supp
memcheck.supp
ubsan.supp
valgrind.supp
scripts/integration_test.sh
)
set(COPY_FILES set(COPY_FILES
${CURL_COPY_FILES} ${CURL_COPY_FILES}
${FREETYPE_COPY_FILES} ${FREETYPE_COPY_FILES}
${OPUSFILE_COPY_FILES} ${OPUSFILE_COPY_FILES}
${PNG_COPY_FILES}
${SDL2_COPY_FILES} ${SDL2_COPY_FILES}
${SQLite3_COPY_FILES} ${SQLite3_COPY_FILES}
${FFMPEG_COPY_FILES} ${FFMPEG_COPY_FILES}
@ -1569,6 +1581,7 @@ set(COPY_FILES
${DISCORDSDK_COPY_FILES} ${DISCORDSDK_COPY_FILES}
${VULKAN_COPY_FILES} ${VULKAN_COPY_FILES}
${EXCEPTION_HANDLING_COPY_FILES} ${EXCEPTION_HANDLING_COPY_FILES}
${INTEGRATION_TEST_COPY_FILES}
) )
file(COPY ${COPY_FILES} DESTINATION .) file(COPY ${COPY_FILES} DESTINATION .)
@ -1755,8 +1768,6 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
http.h http.h
huffman.cpp huffman.cpp
huffman.h huffman.h
image_manipulation.cpp
image_manipulation.h
jobs.cpp jobs.cpp
jobs.h jobs.h
json.cpp json.cpp
@ -1805,6 +1816,12 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
websockets.cpp websockets.cpp
websockets.h 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 set_src(GAME_SHARED GLOB src/game
bezier.cpp bezier.cpp
bezier.h bezier.h
@ -1876,9 +1893,11 @@ set(LIBS
) )
# Targets # 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(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}) 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) if(DISCORD AND NOT DISCORD_DYNAMIC)
add_library(discord-shared SHARED IMPORTED) 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(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 # Libraries
set(LIBS_CLIENT set(LIBS_CLIENT
${LIBS} ${LIBS}
${FREETYPE_LIBRARIES} ${FREETYPE_LIBRARIES}
${GLEW_LIBRARIES} ${GLEW_LIBRARIES}
${PNGLITE_LIBRARIES} ${PNG_LIBRARIES}
${SDL2_LIBRARIES} ${SDL2_LIBRARIES}
${WAVPACK_LIBRARIES} ${WAVPACK_LIBRARIES}
${FFMPEG_LIBRARIES} ${FFMPEG_LIBRARIES}
@ -2160,6 +2179,7 @@ if(CLIENT)
${CLIENT_ICON} ${CLIENT_ICON}
${CLIENT_MANIFEST} ${CLIENT_MANIFEST}
${DEPS_CLIENT} ${DEPS_CLIENT}
$<TARGET_OBJECTS:engine-gfx>
$<TARGET_OBJECTS:engine-shared> $<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared> $<TARGET_OBJECTS:game-shared>
) )
@ -2169,6 +2189,7 @@ if(CLIENT)
${CLIENT_ICON} ${CLIENT_ICON}
${CLIENT_MANIFEST} ${CLIENT_MANIFEST}
${DEPS_CLIENT} ${DEPS_CLIENT}
$<TARGET_OBJECTS:engine-gfx>
$<TARGET_OBJECTS:engine-shared> $<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared> $<TARGET_OBJECTS:game-shared>
) )
@ -2185,7 +2206,7 @@ if(CLIENT)
${OGG_INCLUDE_DIRS} ${OGG_INCLUDE_DIRS}
${OPUSFILE_INCLUDE_DIRS} ${OPUSFILE_INCLUDE_DIRS}
${OPUS_INCLUDE_DIRS} ${OPUS_INCLUDE_DIRS}
${PNGLITE_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS}
${WAVPACK_INCLUDE_DIRS} ${WAVPACK_INCLUDE_DIRS}
${FFMPEG_INCLUDE_DIRS} ${FFMPEG_INCLUDE_DIRS}
@ -2218,15 +2239,15 @@ if(CLIENT)
endif() endif()
if(WAVPACK_CLOSE_FILE) if(WAVPACK_CLOSE_FILE)
target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_WAVPACK_CLOSE_FILE) target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_WAVPACK_CLOSE_FILE)
endif() endif()
if(GLEW_BUNDLED) if(GLEW_BUNDLED)
target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_GLEW_HAS_CONTEXT_INIT) target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_GLEW_HAS_CONTEXT_INIT)
endif() endif()
if(VULKAN) if(VULKAN)
target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_BACKEND_VULKAN) target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_BACKEND_VULKAN)
endif() endif()
list(APPEND TARGETS_OWN ${TARGET_CLIENT}) list(APPEND TARGETS_OWN ${TARGET_CLIENT})
@ -2361,6 +2382,7 @@ if(SERVER)
$<TARGET_OBJECTS:game-shared> $<TARGET_OBJECTS:game-shared>
) )
target_link_libraries(${TARGET_SERVER} ${LIBS_SERVER}) target_link_libraries(${TARGET_SERVER} ${LIBS_SERVER})
target_include_directories(${TARGET_SERVER} PRIVATE ${PNG_INCLUDE_DIRS})
list(APPEND TARGETS_OWN ${TARGET_SERVER}) list(APPEND TARGETS_OWN ${TARGET_SERVER})
list(APPEND TARGETS_LINK ${TARGET_SERVER}) list(APPEND TARGETS_LINK ${TARGET_SERVER})
@ -2399,6 +2421,7 @@ if(TOOLS)
map_diff.cpp map_diff.cpp
map_extract.cpp map_extract.cpp
map_optimize.cpp map_optimize.cpp
map_replace_area.cpp
map_replace_image.cpp map_replace_image.cpp
map_resave.cpp map_resave.cpp
packetgen.cpp packetgen.cpp
@ -2413,9 +2436,9 @@ if(TOOLS)
set(TOOL_DEPS ${DEPS}) set(TOOL_DEPS ${DEPS})
set(TOOL_LIBS ${LIBS}) set(TOOL_LIBS ${LIBS})
if(TOOL MATCHES "^(dilate|map_convert_07|map_optimize|map_extract|map_replace_image)$") if(TOOL MATCHES "^(dilate|map_convert_07|map_optimize|map_extract|map_replace_image)$")
list(APPEND TOOL_DEPS ${PNGLITE_DEP}) list(APPEND TOOL_INCLUDE_DIRS ${PNG_INCLUDE_DIRS})
list(APPEND TOOL_LIBS ${PNGLITE_LIBRARIES}) list(APPEND TOOL_DEPS $<TARGET_OBJECTS:engine-gfx>)
list(APPEND TOOL_INCLUDE_DIRS ${PNGLITE_INCLUDE_DIRS}) list(APPEND TOOL_LIBS ${PNG_LIBRARIES})
endif() endif()
if(TOOL MATCHES "^config_") if(TOOL MATCHES "^config_")
list(APPEND EXTRA_TOOL_SRC "src/tools/config_common.h") 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 add_executable(${TARGET_TESTRUNNER} EXCLUDE_FROM_ALL
${TESTS} ${TESTS}
${TESTS_EXTRA} ${TESTS_EXTRA}
$<TARGET_OBJECTS:engine-gfx>
$<TARGET_OBJECTS:engine-shared> $<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared> $<TARGET_OBJECTS:game-shared>
${DEPS} ${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}) target_include_directories(${TARGET_TESTRUNNER} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS})
list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER}) list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER})
@ -2552,12 +2576,19 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
add_custom_target(run_tests add_custom_target(run_tests
COMMAND $<TARGET_FILE:${TARGET_TESTRUNNER}> ${TESTRUNNER_ARGS} COMMAND $<TARGET_FILE:${TARGET_TESTRUNNER}> ${TESTRUNNER_ARGS}
COMMENT Running tests COMMENT Running unit tests
DEPENDS ${TARGET_TESTRUNNER} DEPENDS ${TARGET_TESTRUNNER}
USES_TERMINAL USES_TERMINAL
) )
endif() 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 # INSTALLATION
######################################################################## ########################################################################
@ -2683,10 +2714,14 @@ set(CPACK_SOURCE_FILES
datasrc/ datasrc/
ddnet-libs/ ddnet-libs/
license.txt license.txt
lsan.supp
memcheck.supp
other/ other/
scripts/ scripts/
src/ src/
storage.cfg storage.cfg
ubsan.supp
valgrind.supp
) )
set(CPACK_SOURCE_IGNORE_FILES set(CPACK_SOURCE_IGNORE_FILES
"\\\\.pyc$" "\\\\.pyc$"

View file

@ -11,7 +11,7 @@ RUN apt-get update && apt-get install -y mingw-w64 \
libglew-dev \ libglew-dev \
libogg-dev \ libogg-dev \
libopus-dev \ libopus-dev \
libpnglite-dev \ libpng-dev \
libwavpack-dev \ libwavpack-dev \
libopusfile-dev \ libopusfile-dev \
libsdl2-dev \ libsdl2-dev \

View file

@ -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: 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. 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: 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 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
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).
On macOS you can use [homebrew](https://brew.sh/) to install build dependencies like this: On macOS you can use [homebrew](https://brew.sh/) to install build dependencies like this:
@ -167,7 +165,7 @@ make
``` ```
and run with: and run with:
```bash ```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. 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
View 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()

View file

@ -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

View file

@ -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
View file

@ -0,0 +1 @@
leak:CCommandProcessorFragment_OpenGL2::Cmd_CreateBufferObject

View file

@ -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 DemoRecorderWrite
Memcheck:Param Memcheck:Param

View file

@ -1,66 +1,43 @@
#!/bin/bash #!/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_verbose=0
arg_valgrind_memcheck=0 arg_valgrind_memcheck=0
for arg in "$@" for arg in "$@"
do do
if [[ "${arg::1}" == "-" ]] && [[ "$arg_end_args" == "0" ]] if [ "$arg" == "-h" ] || [ "$arg" == "--help" ]
then then
if [ "$arg" == "-h" ] || [ "$arg" == "--help" ] echo "usage: $(basename "$0") [OPTION..] [build dir]"
then echo "description:"
echo "usage: $(basename "$0") [OPTION..] [build dir]" echo " Runs a simple integration test of the client and server"
echo "description:" echo " binaries from the given build dir"
echo " Runs a simple integration test of the client and server" echo "options:"
echo " binaries from the given build dir" echo " --help|-h show this help"
echo "options:" echo " --verbose|-v verbose output"
echo " --help|-h show this help" echo " --valgrind-memcheck use valgrind's memcheck to run server and client"
echo " --verbose|-v verbose output" exit 0
elif [ "$arg" == "-v" ] || [ "$arg" == "--verbose" ] elif [ "$arg" == "-v" ] || [ "$arg" == "--verbose" ]
then then
arg_verbose=1 arg_verbose=1
elif [ "$arg" == "--valgrind-memcheck" ] elif [ "$arg" == "--valgrind-memcheck" ]
then then
arg_valgrind_memcheck=1 arg_valgrind_memcheck=1
elif [ "$arg" == "--" ]
then
arg_end_args=1
else
echo "Error: unknown arg '$arg'"
fi
else else
arg_build_dir="$arg" echo "Error: unknown arg '$arg'"
exit 1
fi fi
done done
if [ ! -d "$arg_build_dir" ] if [ ! -f DDNet ]
then then
echo "Error: build directory '$arg_build_dir' not found" echo "Error: client binary not found DDNet' not found"
exit 1 exit 1
fi fi
if [ ! -f "$arg_build_dir"/DDNet ] if [ ! -f DDNet-Server ]
then 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 exit 1
fi 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 got_killed=0
@ -89,13 +66,6 @@ function cleanup() {
trap cleanup EXIT trap cleanup EXIT
{
echo $'add_path $CURRENTDIR'
echo $'add_path $USERDIR'
echo $'add_path $DATADIR'
echo $'add_path ../data'
} > storage.cfg
function fail() function fail()
{ {
sleep 1 sleep 1
@ -104,37 +74,18 @@ function fail()
echo "[-] $1 exited with code $2" 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 # TODO: check for open ports instead
port=17822 port=17822
cp ../ubsan.supp .
cp ../memcheck.supp .
if [[ $OSTYPE == 'darwin'* ]]; then if [[ $OSTYPE == 'darwin'* ]]; then
DETECT_LEAKS=0 DETECT_LEAKS=0
else else
DETECT_LEAKS=1 DETECT_LEAKS=1
fi 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 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() { function print_results() {
if [ "$arg_valgrind_memcheck" == "1" ]; then if [ "$arg_valgrind_memcheck" == "1" ]; then
@ -152,21 +103,34 @@ function print_results() {
return 0 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 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 \"\";" client_args="cl_menu_map \"\";"
else else
tool="" tool=""
client_args="" client_args=""
fi fi
$tool ./DDNet-Server \ $tool ../DDNet-Server \
"sv_input_fifo server.fifo; "sv_input_fifo server.fifo;
sv_rcon_password rcon;
sv_map coverage; sv_map coverage;
sv_sqlite_file ddnet-server.sqlite; sv_sqlite_file ddnet-server.sqlite;
sv_port $port" &> server.log || fail server "$?" & sv_port $port" &> server.log || fail server "$?" &
$tool ./DDNet \ $tool ../DDNet \
"cl_input_fifo client1.fifo; "cl_input_fifo client1.fifo;
player_name client1; player_name client1;
cl_download_skins 0; cl_download_skins 0;
@ -175,12 +139,12 @@ $tool ./DDNet \
connect localhost:$port" &> client1.log || fail client1 "$?" & connect localhost:$port" &> client1.log || fail client1 "$?" &
if [ "$arg_valgrind_memcheck" == "1" ]; then if [ "$arg_valgrind_memcheck" == "1" ]; then
sleep 10 sleep 10
else else
sleep 1 sleep 1
fi fi
$tool ./DDNet \ $tool ../DDNet \
"cl_input_fifo client2.fifo; "cl_input_fifo client2.fifo;
player_name client2; player_name client2;
cl_download_skins 0; cl_download_skins 0;
@ -192,7 +156,7 @@ fails=0
if [ "$arg_valgrind_memcheck" == "1" ]; then if [ "$arg_valgrind_memcheck" == "1" ]; then
tries=120 tries=120
else else
tries=2 tries=50
fi fi
# give the client time to launch and create the fifo file # give the client time to launch and create the fifo file
# but assume after X secs that the client crashed before # but assume after X secs that the client crashed before
@ -214,9 +178,66 @@ do
done done
if [ "$arg_valgrind_memcheck" == "1" ]; then if [ "$arg_valgrind_memcheck" == "1" ]; then
sleep 20 sleep 20
else else
sleep 2 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 fi
kill_all kill_all

View file

@ -1,5 +1,5 @@
#include "backend_base.h" #include "backend_base.h"
#include "engine/shared/image_manipulation.h" #include <engine/gfx/image_manipulation.h>
size_t CCommandProcessorFragment_GLBase::TexFormatToImageColorChannelCount(int TexFormat) size_t CCommandProcessorFragment_GLBase::TexFormatToImageColorChannelCount(int TexFormat)
{ {

View file

@ -14,7 +14,7 @@
#include <engine/client/backend/glsl_shader_compiler.h> #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 #ifndef BACKEND_AS_OPENGL_ES
#include <GL/glew.h> #include <GL/glew.h>

View file

@ -15,7 +15,7 @@
#include <engine/client/backend/opengl/opengl_sl.h> #include <engine/client/backend/opengl/opengl_sl.h>
#include <engine/client/backend/opengl/opengl_sl_program.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> #include <engine/client/backend/glsl_shader_compiler.h>

View file

@ -5,9 +5,9 @@
#include <engine/client/backend/backend_base.h> #include <engine/client/backend/backend_base.h>
#include <engine/client/backend_sdl.h> #include <engine/client/backend_sdl.h>
#include <engine/client/graphics_threaded.h> #include <engine/client/graphics_threaded.h>
#include <engine/gfx/image_manipulation.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <engine/shared/image_manipulation.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <base/log.h> #include <base/log.h>

View file

@ -10,9 +10,9 @@
#include <base/system.h> #include <base/system.h>
#include <pnglite.h>
#include <engine/console.h> #include <engine/console.h>
#include <engine/gfx/image_loader.h>
#include <engine/gfx/image_manipulation.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <engine/storage.h> #include <engine/storage.h>
@ -20,8 +20,6 @@
#include <game/generated/client_data7.h> #include <game/generated/client_data7.h>
#include <game/localization.h> #include <game/localization.h>
#include <engine/shared/image_manipulation.h>
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
#include <engine/shared/video.h> #include <engine/shared/video.h>
#endif #endif
@ -135,8 +133,6 @@ CGraphics_Threaded::CGraphics_Threaded()
m_RenderEnable = true; m_RenderEnable = true;
m_DoScreenshot = false; m_DoScreenshot = false;
png_init(0, 0);
} }
void CGraphics_Threaded::ClipEnable(int x, int y, int w, int h) 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) int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType)
{ {
char aCompleteFilename[IO_MAX_PATH_LENGTH]; char aCompleteFilename[IO_MAX_PATH_LENGTH];
// open file for reading
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename)); 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); dbg_msg("game/png", "failed to open file. filename='%s'", pFilename);
return 0; 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; return 1;
} }
@ -806,21 +797,21 @@ bool CGraphics_Threaded::ScreenshotDirect()
{ {
// find filename // find filename
char aWholePath[1024]; char aWholePath[1024];
png_t Png;
IOHANDLE File = m_pStorage->OpenFile(m_aScreenshotName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath)); 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]; char aBuf[256];
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath); 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); // save png
png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Image.m_pData); 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); io_close(File);
} }

View file

@ -1 +0,0 @@
0.1.17

View file

@ -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.";
};
}

View file

@ -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

View 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;
}

View 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

View file

@ -1,5 +1,5 @@
#ifndef ENGINE_SHARED_IMAGE_MANIPULATION_H #ifndef ENGINE_GFX_IMAGE_MANIPULATION_H
#define ENGINE_SHARED_IMAGE_MANIPULATION_H #define ENGINE_GFX_IMAGE_MANIPULATION_H
#include <stdint.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); int HighestBit(int OfVar);
#endif #endif // ENGINE_GFX_IMAGE_MANIPULATION_H

View file

@ -415,6 +415,14 @@ CServer::~CServer()
free(pCurrentMapData); free(pCurrentMapData);
} }
if(m_RunServer != UNINITIALIZED)
{
for(auto &Client : m_aClients)
{
free(Client.m_pPersistentData);
}
}
delete m_pRegister; delete m_pRegister;
delete m_pConnectionPool; delete m_pConnectionPool;
} }
@ -724,7 +732,7 @@ int CServer::Port() const
int CServer::MaxClients() const int CServer::MaxClients() const
{ {
return m_NetServer.MaxClients(); return m_RunServer == UNINITIALIZED ? 0 : m_NetServer.MaxClients();
} }
int CServer::ClientCount() const int CServer::ClientCount() const
@ -2908,11 +2916,6 @@ int CServer::Run()
m_UPnP.Shutdown(); m_UPnP.Shutdown();
#endif #endif
for(auto &Client : m_aClients)
{
free(Client.m_pPersistentData);
}
m_NetServer.Close(); m_NetServer.Close();
m_pRegister->OnShutdown(); m_pRegister->OnShutdown();

View file

@ -61,9 +61,10 @@ void CFifo::Update()
return; return;
char aBuf[8192]; char aBuf[8192];
int Length = read(m_File, aBuf, sizeof(aBuf)); int Length = read(m_File, aBuf, sizeof(aBuf) - 1);
if(Length <= 0) if(Length <= 0)
return; return;
aBuf[Length] = '\0';
char *pCur = aBuf; char *pCur = aBuf;
for(int i = 0; i < Length; ++i) for(int i = 0; i < Length; ++i)

View file

@ -12,6 +12,7 @@
#include <engine/client.h> #include <engine/client.h>
#include <engine/console.h> #include <engine/console.h>
#include <engine/gfx/image_manipulation.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/input.h> #include <engine/input.h>
#include <engine/keys.h> #include <engine/keys.h>
@ -26,8 +27,6 @@
#include <game/generated/client_data.h> #include <game/generated/client_data.h>
#include <game/localization.h> #include <game/localization.h>
#include <engine/shared/image_manipulation.h>
#include "auto_map.h" #include "auto_map.h"
#include "editor.h" #include "editor.h"

View file

@ -2,71 +2,74 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/logger.h> #include <base/logger.h>
#include <base/system.h> #include <base/system.h>
#include <engine/shared/image_manipulation.h> #include <engine/gfx/image_loader.h>
#include <pnglite.h> #include <engine/gfx/image_manipulation.h>
#include <engine/graphics.h>
int DilateFile(const char *pFilename) int DilateFile(const char *pFilename)
{ {
IOHANDLE File = io_open(pFilename, IOFLAG_READ); IOHANDLE File = io_open(pFilename, IOFLAG_READ);
if(!File) if(File)
{ {
dbg_msg("dilate", "failed to open file. filename='%s'", pFilename); io_seek(File, 0, IOSEEK_END);
return 0; 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); io_close(File);
return 0;
}
if(Png.color_type != PNG_TRUECOLOR_ALPHA) CImageInfo Img;
uint8_t *pImgBuffer = NULL;
EImageFormat ImageFormat;
if(LoadPNG(ImageByteBuffer, pFilename, Img.m_Width, Img.m_Height, pImgBuffer, ImageFormat))
{
if(ImageFormat != IMAGE_FORMAT_RGBA)
{
free(pImgBuffer);
dbg_msg("dilate", "%s: not an RGBA image", pFilename);
return -1;
}
Img.m_pData = pImgBuffer;
unsigned char *pBuffer = (unsigned char *)Img.m_pData;
int w = Img.m_Width;
int h = Img.m_Height;
DilateImage(pBuffer, w, h, 4);
// save here
IOHANDLE SaveFile = io_open(pFilename, IOFLAG_WRITE);
if(SaveFile)
{
TImageByteBuffer ByteBuffer2;
SImageByteBuffer ImageByteBuffer2(&ByteBuffer2);
if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)pBuffer, ImageByteBuffer2, w, h))
io_write(SaveFile, &ByteBuffer2.front(), ByteBuffer2.size());
io_close(SaveFile);
free(pBuffer);
}
}
else
{
dbg_msg("dilate", "failed unknown image format: %s", pFilename);
return -1;
}
}
else
{ {
dbg_msg("dilate", "%s: not an RGBA image", pFilename); dbg_msg("dilate", "failed to open image file. filename='%s'", pFilename);
return 1; return -1;
} }
unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * sizeof(unsigned char) * 4);
Error = png_get_data(&Png, pBuffer);
if(Error != PNG_NO_ERROR)
{
dbg_msg("map_convert_07", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error));
free(pBuffer);
io_close(File);
return 0;
}
io_close(File);
int w = Png.width;
int h = Png.height;
DilateImage(pBuffer, w, h, 4);
// save here
File = io_open(pFilename, IOFLAG_WRITE);
if(!File)
{
dbg_msg("dilate", "failed to open file. filename='%s'", pFilename);
free(pBuffer);
return 0;
}
Error = png_open_write(&Png, 0, File);
if(Error != PNG_NO_ERROR)
{
dbg_msg("dilate", "failed to open image file. filename='%s', pnglite: %s", pFilename, png_error_string(Error));
io_close(File);
return 0;
}
png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer);
io_close(File);
free(pBuffer);
return 0; return 0;
} }

View file

@ -3,13 +3,12 @@
#include <base/logger.h> #include <base/logger.h>
#include <base/system.h> #include <base/system.h>
#include <engine/gfx/image_loader.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/shared/datafile.h> #include <engine/shared/datafile.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <game/gamecore.h> #include <game/gamecore.h>
#include <game/mapitems.h> #include <game/mapitems.h>
#include <pnglite.h>
/* /*
Usage: map_convert_07 <source map filepath> <dest map filepath> 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) int LoadPNG(CImageInfo *pImg, const char *pFilename)
{ {
IOHANDLE File = io_open(pFilename, IOFLAG_READ); IOHANDLE File = io_open(pFilename, IOFLAG_READ);
if(!File) if(File)
{ {
dbg_msg("map_convert_07", "failed to open file. filename='%s'", pFilename); io_seek(File, 0, IOSEEK_END);
return 0; 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); 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;
dbg_msg("map_convert_07", "invalid image format. filename='%s'", pFilename); if(LoadPNG(ImageByteBuffer, pFilename, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
io_close(File); {
return 0; pImg->m_pData = pImgBuffer;
}
unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * Png.bpp); if(ImageFormat == IMAGE_FORMAT_RGBA && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
Error = png_get_data(&Png, pBuffer); {
if(Error != PNG_NO_ERROR) pImg->m_Format = CImageInfo::FORMAT_RGBA;
{ }
dbg_msg("map_convert_07", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error)); else
free(pBuffer); {
io_close(File); dbg_msg("map_convert_07", "invalid image format. filename='%s'", pFilename);
return 0; return 0;
}
}
else
return 0;
} }
io_close(File); else
return 0;
pImg->m_Width = Png.width;
pImg->m_Height = Png.height;
pImg->m_Format = CImageInfo::FORMAT_RGBA;
pImg->m_pData = pBuffer;
return 1; return 1;
} }
@ -188,8 +184,6 @@ int main(int argc, const char **argv)
return -1; return -1;
} }
png_init(0, 0);
g_NextDataItemID = g_DataReader.NumData(); g_NextDataItemID = g_DataReader.NumData();
int i = 0; int i = 0;

View file

@ -1,12 +1,11 @@
// Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery // Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery
#include <base/logger.h> #include <base/logger.h>
#include <base/system.h> #include <base/system.h>
#include <engine/gfx/image_loader.h>
#include <engine/shared/datafile.h> #include <engine/shared/datafile.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <game/mapitems.h> #include <game/mapitems.h>
#include <pnglite.h>
bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave) bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave)
{ {
CDataFileReader Reader; CDataFileReader Reader;
@ -52,22 +51,15 @@ bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave)
// copy image data // copy image data
IOHANDLE File = io_open(aBuf, IOFLAG_WRITE); IOHANDLE File = io_open(aBuf, IOFLAG_WRITE);
if(!File) if(File)
{ {
dbg_msg("map_extract", "failed to open file. filename='%s'", aBuf); TImageByteBuffer ByteBuffer;
continue; SImageByteBuffer ImageByteBuffer(&ByteBuffer);
if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)Reader.GetData(pItem->m_ImageData), ImageByteBuffer, pItem->m_Width, pItem->m_Height))
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
io_close(File);
} }
png_t Png;
int Error = png_open_write(&Png, 0, File);
if(Error != PNG_NO_ERROR)
{
dbg_msg("map_extract", "failed to write image file. filename='%s', pnglite: %s", aBuf, png_error_string(Error));
}
else
{
png_set_data(&Png, pItem->m_Width, pItem->m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Reader.GetData(pItem->m_ImageData));
}
io_close(File);
} }
// load sounds // load sounds
@ -123,8 +115,6 @@ int main(int argc, const char *argv[])
return -1; return -1;
} }
png_init(0, 0);
int Result = Process(pStorage, argv[1], pDir) ? 0 : 1; int Result = Process(pStorage, argv[1], pDir) ? 0 : 1;
return Result; return Result;
} }

View file

@ -2,8 +2,8 @@
#include <base/logger.h> #include <base/logger.h>
#include <base/system.h> #include <base/system.h>
#include <cstdint> #include <cstdint>
#include <engine/gfx/image_manipulation.h>
#include <engine/shared/datafile.h> #include <engine/shared/datafile.h>
#include <engine/shared/image_manipulation.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <game/mapitems.h> #include <game/mapitems.h>
#include <vector> #include <vector>

View 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;
}

View file

@ -3,12 +3,11 @@
#include <base/logger.h> #include <base/logger.h>
#include <base/system.h> #include <base/system.h>
#include <engine/gfx/image_loader.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/shared/datafile.h> #include <engine/shared/datafile.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <game/mapitems.h> #include <game/mapitems.h>
#include <pnglite.h>
/* /*
Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath> 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 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) int LoadPNG(CImageInfo *pImg, const char *pFilename)
{ {
png_t Png;
IOHANDLE File = io_open(pFilename, IOFLAG_READ); IOHANDLE File = io_open(pFilename, IOFLAG_READ);
if(!File) if(File)
{ {
dbg_msg("map_replace_image", "failed to open file. filename='%s'", pFilename); io_seek(File, 0, IOSEEK_END);
return 0; unsigned int FileSize = io_tell(File);
} io_seek(File, 0, IOSEEK_START);
int Error = png_open_read(&Png, 0, File); TImageByteBuffer ByteBuffer;
if(Error != PNG_NO_ERROR) SImageByteBuffer ImageByteBuffer(&ByteBuffer);
{
dbg_msg("map_replace_image", "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 && Png.color_type != PNG_TRUECOLOR_ALPHA) || Png.width > (2 << 12) || Png.height > (2 << 12)) ByteBuffer.resize(FileSize);
{ io_read(File, &ByteBuffer.front(), FileSize);
dbg_msg("map_replace_image", "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_replace_image", "failed to read image. filename='%s', pnglite: %s", pFilename, png_error_string(Error));
free(pBuffer);
io_close(File); io_close(File);
return 0;
}
io_close(File);
pImg->m_Width = Png.width; uint8_t *pImgBuffer = NULL;
pImg->m_Height = Png.height; EImageFormat ImageFormat;
if(Png.color_type == PNG_TRUECOLOR) if(LoadPNG(ImageByteBuffer, pFilename, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
pImg->m_Format = CImageInfo::FORMAT_RGB; {
else if(Png.color_type == PNG_TRUECOLOR_ALPHA) if((ImageFormat == IMAGE_FORMAT_RGBA || ImageFormat == IMAGE_FORMAT_RGB) && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
pImg->m_Format = CImageInfo::FORMAT_RGBA; {
pImg->m_pData = pImgBuffer;
if(ImageFormat == IMAGE_FORMAT_RGB) // ignore_convention
pImg->m_Format = CImageInfo::FORMAT_RGB;
else if(ImageFormat == IMAGE_FORMAT_RGBA) // ignore_convention
pImg->m_Format = CImageInfo::FORMAT_RGBA;
else
{
free(pImgBuffer);
return 0;
}
}
}
else
return 0;
}
else else
{
free(pBuffer);
return 0; return 0;
}
pImg->m_pData = pBuffer;
return 1; return 1;
} }
@ -146,8 +137,6 @@ int main(int argc, const char **argv)
return -1; return -1;
} }
png_init(0, 0);
// add all items // add all items
for(int Index = 0; Index < g_DataReader.NumItems(); Index++) for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
{ {