diff --git a/cmake/DrawdanceDependencies.cmake b/cmake/DrawdanceDependencies.cmake index fc30925b57..97beed30f1 100644 --- a/cmake/DrawdanceDependencies.cmake +++ b/cmake/DrawdanceDependencies.cmake @@ -2,63 +2,65 @@ dp_find_package(ZLIB MODULE REQUIRED) -if(NOT EMSCRIPTEN) - find_package(PkgConfig QUIET) - if(PKGCONFIG_FOUND) - pkg_check_modules(LIBAV IMPORTED_TARGET GLOBAL - libavcodec - libavformat - libavutil - libswscale - ) - if(TARGET PkgConfig::LIBAV) - include(CMakePrintHelpers) - cmake_print_properties(TARGETS PkgConfig::LIBAV PROPERTIES - INTERFACE_COMPILE_DEFINITIONS - INTERFACE_COMPILE_FEATURES - INTERFACE_COMPILE_OPTIONS - INTERFACE_INCLUDE_DIRECTORIES - INTERFACE_LINK_DEPENDS - INTERFACE_LINK_DIRECTORIES - INTERFACE_LINK_LIBRARIES - INTERFACE_LINK_LIBRARIES_DIRECT - INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE - INTERFACE_LINK_OPTIONS - INTERFACE_SYSTEM_INCLUDE_DIRECTORIES +if(NOT WIN32) + dp_find_package(Threads REQUIRED) +endif() + +if(CLIENT OR TOOLS) + if(NOT EMSCRIPTEN) + find_package(PkgConfig QUIET) + if(PKGCONFIG_FOUND) + pkg_check_modules(LIBAV IMPORTED_TARGET GLOBAL + libavcodec + libavformat + libavutil + libswscale ) - add_library(LIBAV::LIBAV ALIAS PkgConfig::LIBAV) + if(TARGET PkgConfig::LIBAV) + include(CMakePrintHelpers) + cmake_print_properties(TARGETS PkgConfig::LIBAV PROPERTIES + INTERFACE_COMPILE_DEFINITIONS + INTERFACE_COMPILE_FEATURES + INTERFACE_COMPILE_OPTIONS + INTERFACE_INCLUDE_DIRECTORIES + INTERFACE_LINK_DEPENDS + INTERFACE_LINK_DIRECTORIES + INTERFACE_LINK_LIBRARIES + INTERFACE_LINK_LIBRARIES_DIRECT + INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE + INTERFACE_LINK_OPTIONS + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES + ) + add_library(LIBAV::LIBAV ALIAS PkgConfig::LIBAV) + endif() + else() + message(WARNING "PkgConfig NOT FOUND") endif() - else() - message(WARNING "PkgConfig NOT FOUND") + add_feature_info("Video export via libav" "TARGET LIBAV::LIBAV" "") endif() - add_feature_info("Video export via libav" "TARGET LIBAV::LIBAV" "") -endif() -if(IMAGE_IMPL STREQUAL "LIBS") - dp_find_package(PNG REQUIRED) - dp_find_package(JPEG REQUIRED) -elseif(IMAGE_IMPL STREQUAL "QT") - dp_find_package("Qt${QT_VERSION_MAJOR}" COMPONENTS Gui REQUIRED) -endif() - -dp_find_package(dpwebp REQUIRED) + if(IMAGE_IMPL STREQUAL "LIBS") + dp_find_package(PNG REQUIRED) + dp_find_package(JPEG REQUIRED) + elseif(IMAGE_IMPL STREQUAL "QT") + dp_find_package("Qt${QT_VERSION_MAJOR}" COMPONENTS Gui REQUIRED) + endif() -if(NOT WIN32) - dp_find_package(Threads REQUIRED) -endif() + dp_find_package(dpwebp REQUIRED) -dp_find_package("Qt${QT_VERSION_MAJOR}" COMPONENTS Xml REQUIRED) -if(ZIP_IMPL STREQUAL "LIBZIP") - dp_find_package(libzip QUIET) - if(NOT TARGET libzip::zip) - # libzip 1.5 has only pkg-config - if(PKGCONFIG_FOUND) - pkg_check_modules(libzip REQUIRED IMPORTED_TARGET libzip) - add_library(libzip::zip ALIAS PkgConfig::libzip) + dp_find_package("Qt${QT_VERSION_MAJOR}" COMPONENTS Xml REQUIRED) + if(ZIP_IMPL STREQUAL "LIBZIP") + dp_find_package(libzip QUIET) + if(NOT TARGET libzip::zip) + # libzip 1.5 has only pkg-config + if(PKGCONFIG_FOUND) + pkg_check_modules(libzip REQUIRED IMPORTED_TARGET libzip) + add_library(libzip::zip ALIAS PkgConfig::libzip) + endif() endif() + elseif(ZIP_IMPL STREQUAL "KARCHIVE") + dp_find_package("KF${QT_VERSION_MAJOR}Archive" REQUIRED) endif() -elseif(ZIP_IMPL STREQUAL "KARCHIVE") - dp_find_package("KF${QT_VERSION_MAJOR}Archive" REQUIRED) endif() if(USE_GENERATORS) diff --git a/src/desktop/CMakeLists.txt b/src/desktop/CMakeLists.txt index 03ba4ff7ef..da623da80d 100644 --- a/src/desktop/CMakeLists.txt +++ b/src/desktop/CMakeLists.txt @@ -408,7 +408,7 @@ target_link_libraries(drawpile PRIVATE cmake-config dpshared dpclient - drawdance + drawdance_client ${QT_PACKAGE_NAME}::Core ${QT_PACKAGE_NAME}::Gui ${QT_PACKAGE_NAME}::OpenGL diff --git a/src/desktop/dialogs/sessionsettings.cpp b/src/desktop/dialogs/sessionsettings.cpp index 27d71bbf3d..fc47da8204 100644 --- a/src/desktop/dialogs/sessionsettings.cpp +++ b/src/desktop/dialogs/sessionsettings.cpp @@ -8,6 +8,7 @@ #include "libclient/net/banlistmodel.h" #include "libclient/parentalcontrols/parentalcontrols.h" #include "libclient/utils/listservermodel.h" +#include "libshared/util/qtcompat.h" #include "ui_sessionsettings.h" #include #include diff --git a/src/desktop/filewrangler.h b/src/desktop/filewrangler.h index 783dd59f15..52505bdb7f 100644 --- a/src/desktop/filewrangler.h +++ b/src/desktop/filewrangler.h @@ -2,7 +2,7 @@ #ifndef DESKTOP_FILEWRANGLER_H #define DESKTOP_FILEWRANGLER_H extern "C" { -#include +#include } #include "libclient/utils/images.h" #include diff --git a/src/desktop/mainwindow.h b/src/desktop/mainwindow.h index c79ee4c577..239ea08ba8 100644 --- a/src/desktop/mainwindow.h +++ b/src/desktop/mainwindow.h @@ -2,7 +2,7 @@ #ifndef DESKTOP_MAINWINDOW_H #define DESKTOP_MAINWINDOW_H extern "C" { -#include +#include } #include "desktop/dialogs/flipbook.h" #include "libclient/canvas/acl.h" diff --git a/src/drawdance/CMakeLists.txt b/src/drawdance/CMakeLists.txt index 2c65cccb77..059f19e593 100644 --- a/src/drawdance/CMakeLists.txt +++ b/src/drawdance/CMakeLists.txt @@ -20,6 +20,13 @@ add_subdirectory(libcommon) add_subdirectory(libmsg) add_subdirectory(libengine) -add_library(drawdance INTERFACE) -set(drawdance_libraries dpengine dpmsg dpcommon cmake-config) -target_link_libraries(drawdance INTERFACE dpengine dpmsg dpcommon cmake-config) +add_library(drawdance_server INTERFACE) +target_link_libraries( + drawdance_server INTERFACE dpengine dpmsg dpcommon cmake-config) + +if(CLIENT OR TOOLS) + add_subdirectory(libimpex) + add_library(drawdance_client INTERFACE) + target_link_libraries( + drawdance_client INTERFACE dpimpex dpengine dpmsg dpcommon cmake-config) +endif() diff --git a/src/drawdance/bundled/CMakeLists.txt b/src/drawdance/bundled/CMakeLists.txt index 0e802a8af5..85ac824c72 100644 --- a/src/drawdance/bundled/CMakeLists.txt +++ b/src/drawdance/bundled/CMakeLists.txt @@ -36,71 +36,73 @@ target_include_directories( add_library(uthash INTERFACE) target_include_directories(uthash SYSTEM INTERFACE "uthash") -add_library(jo_gifx OBJECT jo_gifx/jo_gifx.h jo_gifx/jo_gifx.c) -target_include_directories( - jo_gifx SYSTEM PUBLIC ${CMAKE_CURRENT_LIST_DIR}/jo_gifx) +if(CLIENT OR TOOLS) + add_library(jo_gifx OBJECT jo_gifx/jo_gifx.h jo_gifx/jo_gifx.c) + target_include_directories( + jo_gifx SYSTEM PUBLIC ${CMAKE_CURRENT_LIST_DIR}/jo_gifx) -set(psd_sdk_sources - psd_sdk/Psd.h - psd_sdk/PsdAllocator.cpp - psd_sdk/PsdAllocator.h - psd_sdk/PsdAssert.h - psd_sdk/PsdBitUtil.h - psd_sdk/PsdBitUtil.inl - psd_sdk/PsdBlendMode.cpp - psd_sdk/PsdBlendMode.h - psd_sdk/PsdChannel.h - psd_sdk/PsdColorMode.cpp - psd_sdk/PsdColorMode.h - psd_sdk/PsdCompilerMacros.h - psd_sdk/PsdCompressionType.h - psd_sdk/PsdDecompressRle.cpp - psd_sdk/PsdDecompressRle.h - psd_sdk/PsdDocument.h - psd_sdk/PsdEndianConversion.h - psd_sdk/PsdEndianConversion.inl - psd_sdk/PsdFile.cpp - psd_sdk/PsdFile.h - psd_sdk/PsdFixedSizeString.cpp - psd_sdk/PsdFixedSizeString.h - psd_sdk/PsdKey.h - psd_sdk/PsdLayer.h - psd_sdk/PsdLayerMask.h - psd_sdk/PsdLayerMaskSection.h - psd_sdk/PsdLayerType.h - psd_sdk/PsdLog.h - psd_sdk/PsdMallocAllocator.cpp - psd_sdk/PsdMallocAllocator.h - psd_sdk/PsdMemoryUtil.h - psd_sdk/PsdMemoryUtil.inl - psd_sdk/PsdNamespace.h - psd_sdk/PsdParseDocument.cpp - psd_sdk/PsdParseDocument.h - psd_sdk/PsdParseLayerMaskSection.cpp - psd_sdk/PsdParseLayerMaskSection.h - psd_sdk/PsdPch.cpp - psd_sdk/PsdPch.h - psd_sdk/PsdPlatform.h - psd_sdk/PsdSection.h - psd_sdk/PsdSyncFileReader.cpp - psd_sdk/PsdSyncFileReader.h - psd_sdk/PsdSyncFileUtil.h - psd_sdk/PsdSyncFileUtil.inl - psd_sdk/PsdTypes.h - psd_sdk/PsdUnionCast.h - psd_sdk/PsdUnionCast.inl - psd_sdk/PsdVectorMask.h - psd_sdk/Psdinttypes.h - psd_sdk/Psdispod.h - psd_sdk/Psdisunsigned.h - psd_sdk/Psdminiz.c - psd_sdk/Psdminiz.h - psd_sdk/Psdstdint.h -) + set(psd_sdk_sources + psd_sdk/Psd.h + psd_sdk/PsdAllocator.cpp + psd_sdk/PsdAllocator.h + psd_sdk/PsdAssert.h + psd_sdk/PsdBitUtil.h + psd_sdk/PsdBitUtil.inl + psd_sdk/PsdBlendMode.cpp + psd_sdk/PsdBlendMode.h + psd_sdk/PsdChannel.h + psd_sdk/PsdColorMode.cpp + psd_sdk/PsdColorMode.h + psd_sdk/PsdCompilerMacros.h + psd_sdk/PsdCompressionType.h + psd_sdk/PsdDecompressRle.cpp + psd_sdk/PsdDecompressRle.h + psd_sdk/PsdDocument.h + psd_sdk/PsdEndianConversion.h + psd_sdk/PsdEndianConversion.inl + psd_sdk/PsdFile.cpp + psd_sdk/PsdFile.h + psd_sdk/PsdFixedSizeString.cpp + psd_sdk/PsdFixedSizeString.h + psd_sdk/PsdKey.h + psd_sdk/PsdLayer.h + psd_sdk/PsdLayerMask.h + psd_sdk/PsdLayerMaskSection.h + psd_sdk/PsdLayerType.h + psd_sdk/PsdLog.h + psd_sdk/PsdMallocAllocator.cpp + psd_sdk/PsdMallocAllocator.h + psd_sdk/PsdMemoryUtil.h + psd_sdk/PsdMemoryUtil.inl + psd_sdk/PsdNamespace.h + psd_sdk/PsdParseDocument.cpp + psd_sdk/PsdParseDocument.h + psd_sdk/PsdParseLayerMaskSection.cpp + psd_sdk/PsdParseLayerMaskSection.h + psd_sdk/PsdPch.cpp + psd_sdk/PsdPch.h + psd_sdk/PsdPlatform.h + psd_sdk/PsdSection.h + psd_sdk/PsdSyncFileReader.cpp + psd_sdk/PsdSyncFileReader.h + psd_sdk/PsdSyncFileUtil.h + psd_sdk/PsdSyncFileUtil.inl + psd_sdk/PsdTypes.h + psd_sdk/PsdUnionCast.h + psd_sdk/PsdUnionCast.inl + psd_sdk/PsdVectorMask.h + psd_sdk/Psdinttypes.h + psd_sdk/Psdispod.h + psd_sdk/Psdisunsigned.h + psd_sdk/Psdminiz.c + psd_sdk/Psdminiz.h + psd_sdk/Psdstdint.h + ) -add_library(psd_sdk STATIC ${psd_sdk_sources}) -target_include_directories( - psd_sdk SYSTEM PUBLIC ${CMAKE_CURRENT_LIST_DIR}/psd_sdk) + add_library(psd_sdk STATIC ${psd_sdk_sources}) + target_include_directories( + psd_sdk SYSTEM PUBLIC ${CMAKE_CURRENT_LIST_DIR}/psd_sdk) +endif() # The project root adds compiler warnings and tidy to all targets, but # third-party code does not follow the same code hygiene rules, so turn those diff --git a/src/drawdance/generators/bindgen.bash b/src/drawdance/generators/bindgen.bash index 927ee9f980..91970bc64d 100755 --- a/src/drawdance/generators/bindgen.bash +++ b/src/drawdance/generators/bindgen.bash @@ -26,4 +26,5 @@ bindgen \ -I../bundled \ -I../libcommon \ -I../libmsg \ - -I../libengine + -I../libengine \ + -I../libimpex diff --git a/src/drawdance/generators/qt_image_resize.cpp b/src/drawdance/generators/qt_image_resize.cpp index b5555da24c..6e22a5f62c 100644 --- a/src/drawdance/generators/qt_image_resize.cpp +++ b/src/drawdance/generators/qt_image_resize.cpp @@ -21,7 +21,7 @@ */ #include #include -#include "../libengine/test/resize_image.h" +#include "../libimpex/test/resize_image.h" static QImage generate_base_image() { diff --git a/src/drawdance/libengine/CMakeLists.txt b/src/drawdance/libengine/CMakeLists.txt index 6a45983ec1..755a525bfc 100644 --- a/src/drawdance/libengine/CMakeLists.txt +++ b/src/drawdance/libengine/CMakeLists.txt @@ -19,7 +19,6 @@ dp_target_sources(dpengine dpengine/flood_fill.c dpengine/image.c dpengine/image_transform.c - dpengine/image_webp.c dpengine/key_frame.c dpengine/layer_content.c dpengine/layer_group.c @@ -27,9 +26,6 @@ dp_target_sources(dpengine dpengine/layer_props.c dpengine/layer_props_list.c dpengine/layer_routes.c - dpengine/load.c - dpengine/load_animation.c - dpengine/load_psd.cpp dpengine/local_state.c dpengine/ops.c dpengine/paint.c @@ -39,8 +35,6 @@ dp_target_sources(dpengine dpengine/preview.c dpengine/recorder.c dpengine/renderer.c - dpengine/save.c - dpengine/save_psd.c dpengine/selection.c dpengine/selection_set.c dpengine/snapshots.c @@ -50,9 +44,7 @@ dp_target_sources(dpengine dpengine/timeline.c dpengine/track.c dpengine/user_cursors.c - dpengine/utf16be_qt.cpp dpengine/view_mode.c - dpengine/xml_stream_qt.cpp dpengine/affected_area.h dpengine/annotation.h dpengine/annotation_list.h @@ -69,10 +61,7 @@ dp_target_sources(dpengine dpengine/dump_reader.h dpengine/flood_fill.h dpengine/image.h - dpengine/image_jpeg.h - dpengine/image_png.h dpengine/image_transform.h - dpengine/image_webp.h dpengine/key_frame.h dpengine/layer_content.h dpengine/layer_group.h @@ -80,6 +69,7 @@ dp_target_sources(dpengine dpengine/layer_props.h dpengine/layer_props_list.h dpengine/layer_routes.h + dpengine/load_enums.h dpengine/local_state.h dpengine/ops.h dpengine/paint.h @@ -89,6 +79,7 @@ dp_target_sources(dpengine dpengine/preview.h dpengine/recorder.h dpengine/renderer.h + dpengine/save_enums.h dpengine/selection.h dpengine/selection_set.h dpengine/snapshots.h @@ -99,73 +90,23 @@ dp_target_sources(dpengine dpengine/track.h dpengine/user_cursors.h dpengine/view_mode.h - dpengine/load.h - dpengine/load_animation.h - dpengine/save.h - dpengine/utf16be.h - dpengine/xml_stream.h - dpengine/zip_archive.h ) target_include_directories(dpengine PUBLIC ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(dpengine PUBLIC - mypaint uthash qgrayraster jo_gifx parson dpmsg dpcommon dpwebp::dpwebp - ZLIB::ZLIB psd_sdk "Qt${QT_VERSION_MAJOR}::Core" "Qt${QT_VERSION_MAJOR}::Xml" + mypaint uthash qgrayraster parson dpmsg dpcommon ZLIB::ZLIB + "Qt${QT_VERSION_MAJOR}::Core" ) -if(ZIP_IMPL STREQUAL "LIBZIP") - target_sources(dpengine PRIVATE dpengine/zip_archive_libzip.c) - target_link_libraries(dpengine PUBLIC libzip::zip) -elseif(ZIP_IMPL STREQUAL "KARCHIVE") - target_sources(dpengine PRIVATE dpengine/zip_archive_karchive.cpp) - target_link_libraries(dpengine PUBLIC KF${QT_VERSION_MAJOR}::Archive) -else() - message(SEND_ERROR "Unknown ZIP_IMPL value '${ZIP_IMPL}'") -endif() - -if(IMAGE_IMPL STREQUAL "LIBS") - target_sources(dpengine PRIVATE - dpengine/image_jpeg.c - dpengine/image_png.c - ) - target_link_libraries(dpengine PUBLIC - PNG::PNG - JPEG::JPEG - ) -elseif(IMAGE_IMPL STREQUAL "QT") - target_sources(dpengine PRIVATE dpengine/image_qt.cpp) - target_link_libraries(dpengine PUBLIC "Qt${QT_VERSION_MAJOR}::Gui") -else() - message(SEND_ERROR "Unknown IMAGE_IMPL value '${IMAGE_IMPL}'") -endif() - -if(TARGET LIBAV::LIBAV) - target_sources(dpengine PRIVATE - dpengine/save_video.c - dpengine/save_video.h - ) - target_link_libraries(dpengine PUBLIC LIBAV::LIBAV) - target_compile_definitions(dpengine PUBLIC DP_LIBAV=1) -endif() - if(TESTS) - add_library(dptest_engine) - target_sources(dptest_engine PRIVATE - test/dptest/dptest_engine.c - test/dptest/dptest_engine.h - ) - target_include_directories(dptest_engine PUBLIC - "${CMAKE_CURRENT_LIST_DIR}/test/dptest" - ) - target_link_libraries(dptest_engine PUBLIC dptest dpengine) + add_library(dptest_engine INTERFACE) + target_link_libraries(dptest_engine INTERFACE dptest dpengine) add_dptest_targets(engine dptest_engine test/handle_annotations.c test/handle_layers.c test/handle_metadata.c test/handle_timeline.c - test/image_thumbnail.c test/pixel_conversion.c - test/resize_image.c ) endif() diff --git a/src/drawdance/libengine/dpengine/image.c b/src/drawdance/libengine/dpengine/image.c index 740455dd60..b4373a2ee5 100644 --- a/src/drawdance/libengine/dpengine/image.c +++ b/src/drawdance/libengine/dpengine/image.c @@ -21,10 +21,7 @@ */ #include "image.h" #include "compress.h" -#include "image_jpeg.h" -#include "image_png.h" #include "image_transform.h" -#include "image_webp.h" #include "paint.h" #include #include @@ -52,13 +49,6 @@ DP_Image *DP_image_new(int width, int height) return img; } -static void assign_type(DP_ImageFileType *out_type, DP_ImageFileType type) -{ - if (out_type) { - *out_type = type; - } -} - static bool guess_png(const unsigned char *buf, size_t size) { unsigned char sig[] = {0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa}; @@ -95,64 +85,6 @@ DP_ImageFileType DP_image_guess(const unsigned char *buf, size_t size) } } -static DP_Image *read_image_guess(DP_Input *input, DP_ImageFileType *out_type) -{ - unsigned char buf[12]; - bool error; - size_t read = DP_input_read(input, buf, sizeof(buf), &error); - if (error) { - return NULL; - } - - DP_ImageFileType type = DP_image_guess(buf, read); - assign_type(out_type, type); - - DP_Image *(*read_fn)(DP_Input *); - switch (type) { - case DP_IMAGE_FILE_TYPE_PNG: - read_fn = DP_image_png_read; - break; - case DP_IMAGE_FILE_TYPE_JPEG: - read_fn = DP_image_jpeg_read; - break; - case DP_IMAGE_FILE_TYPE_WEBP: - read_fn = DP_image_webp_read; - break; - default: - DP_error_set("Could not guess image format"); - return NULL; - } - - if (!DP_input_rewind(input)) { - return NULL; - } - - return read_fn(input); -} - -DP_Image *DP_image_new_from_file(DP_Input *input, DP_ImageFileType type, - DP_ImageFileType *out_type) -{ - DP_ASSERT(input); - switch (type) { - case DP_IMAGE_FILE_TYPE_GUESS: - return read_image_guess(input, out_type); - case DP_IMAGE_FILE_TYPE_PNG: - assign_type(out_type, DP_IMAGE_FILE_TYPE_PNG); - return DP_image_png_read(input); - case DP_IMAGE_FILE_TYPE_JPEG: - assign_type(out_type, DP_IMAGE_FILE_TYPE_JPEG); - return DP_image_jpeg_read(input); - case DP_IMAGE_FILE_TYPE_WEBP: - assign_type(out_type, DP_IMAGE_FILE_TYPE_WEBP); - return DP_image_webp_read(input); - default: - assign_type(out_type, DP_IMAGE_FILE_TYPE_UNKNOWN); - DP_error_set("Unknown image file type %d", (int)type); - return NULL; - } -} - struct DP_ImageInflateArgs { int width, height; @@ -586,46 +518,3 @@ DP_UPixelFloat DP_image_sample_color_at_with(int width, int height, return sample_dab_color(width, height, get_pixel, user, stamp, opaque); } } - - -DP_Image *DP_image_read_png(DP_Input *input) -{ - DP_ASSERT(input); - return DP_image_png_read(input); -} - -DP_Image *DP_image_read_jpeg(DP_Input *input) -{ - DP_ASSERT(input); - return DP_image_jpeg_read(input); -} - -DP_Image *DP_image_read_webp(DP_Input *input) -{ - DP_ASSERT(input); - return DP_image_webp_read(input); -} - -bool DP_image_write_png(DP_Image *img, DP_Output *output) -{ - DP_ASSERT(img); - DP_ASSERT(output); - return DP_image_png_write(output, DP_image_width(img), DP_image_height(img), - DP_image_pixels(img)); -} - -bool DP_image_write_jpeg(DP_Image *img, DP_Output *output) -{ - DP_ASSERT(img); - DP_ASSERT(output); - return DP_image_jpeg_write(output, DP_image_width(img), - DP_image_height(img), DP_image_pixels(img)); -} - -bool DP_image_write_webp(DP_Image *img, DP_Output *output) -{ - DP_ASSERT(img); - DP_ASSERT(output); - return DP_image_webp_write(output, DP_image_width(img), - DP_image_height(img), DP_image_pixels(img)); -} diff --git a/src/drawdance/libengine/dpengine/image.h b/src/drawdance/libengine/dpengine/image.h index 27b66c45ef..9b73785656 100644 --- a/src/drawdance/libengine/dpengine/image.h +++ b/src/drawdance/libengine/dpengine/image.h @@ -48,9 +48,6 @@ DP_Image *DP_image_new(int width, int height); DP_ImageFileType DP_image_guess(const unsigned char *buf, size_t size); -DP_Image *DP_image_new_from_file(DP_Input *input, DP_ImageFileType type, - DP_ImageFileType *out_type); - DP_Image *DP_image_new_from_compressed(int width, int height, const unsigned char *in, size_t in_size); @@ -110,13 +107,4 @@ DP_UPixelFloat DP_image_sample_color_at_with(int width, int height, int *in_out_last_diameter); -DP_Image *DP_image_read_png(DP_Input *input); -DP_Image *DP_image_read_jpeg(DP_Input *input); -DP_Image *DP_image_read_jpeg(DP_Input *input); - -bool DP_image_write_png(DP_Image *img, DP_Output *output) DP_MUST_CHECK; -bool DP_image_write_jpeg(DP_Image *img, DP_Output *output) DP_MUST_CHECK; -bool DP_image_write_webp(DP_Image *img, DP_Output *output) DP_MUST_CHECK; - - #endif diff --git a/src/drawdance/libengine/dpengine/image_jpeg.h b/src/drawdance/libengine/dpengine/image_jpeg.h deleted file mode 100644 index 74acb0fffc..0000000000 --- a/src/drawdance/libengine/dpengine/image_jpeg.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef DP_IMAGE_JPEG_H -#define DP_IMAGE_JPEG_H -#include - -typedef struct DP_Image DP_Image; -typedef struct DP_Input DP_Input; -typedef struct DP_Output DP_Output; -typedef union DP_Pixel8 DP_Pixel8; - - -DP_Image *DP_image_jpeg_read(DP_Input *input); - -bool DP_image_jpeg_write(DP_Output *output, int width, int height, - DP_Pixel8 *pixels); - - -#endif diff --git a/src/drawdance/libengine/dpengine/image_png.h b/src/drawdance/libengine/dpengine/image_png.h deleted file mode 100644 index 8a3051e622..0000000000 --- a/src/drawdance/libengine/dpengine/image_png.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef DP_IMAGE_PNG_H -#define DP_IMAGE_PNG_H -#include - -typedef struct DP_Image DP_Image; -typedef struct DP_Input DP_Input; -typedef struct DP_Output DP_Output; -typedef union DP_Pixel8 DP_Pixel8; -typedef union DP_UPixel8 DP_UPixel8; - - -DP_Image *DP_image_png_read(DP_Input *input); - -bool DP_image_png_write(DP_Output *output, int width, int height, - DP_Pixel8 *pixels); - -bool DP_image_png_write_unpremultiplied(DP_Output *output, int width, int height, - DP_UPixel8 *pixels); - - -#endif diff --git a/src/drawdance/libengine/dpengine/load.h b/src/drawdance/libengine/dpengine/load.h deleted file mode 100644 index 4a6c4ada61..0000000000 --- a/src/drawdance/libengine/dpengine/load.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2022 - 2023 askmeaboutloom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * -------------------------------------------------------------------- - * - * This code is based on Drawpile, using it under the GNU General Public - * License, version 3. See 3rdparty/licenses/drawpile/COPYING for details. - */ -#ifndef DPENGINE_LOAD_H -#define DPENGINE_LOAD_H -#include "save.h" -#include - -typedef struct DP_CanvasState DP_CanvasState; -typedef struct DP_DrawContext DP_DrawContext; -typedef struct DP_Input DP_Input; -typedef struct DP_Player DP_Player; - -#define DP_LOAD_FLAG_NONE 0u -#define DP_LOAD_FLAG_SINGLE_THREAD (1u << 0u) - - -typedef struct DP_LoadFormat { - const char *title; - const char **extensions; // Terminated by a NULL element. -} DP_LoadFormat; - -// Returns supported formats for loading, terminated by a {NULL, NULL} element. -const DP_LoadFormat *DP_load_supported_formats(void); - - -typedef enum DP_LoadResult { - DP_LOAD_RESULT_SUCCESS, - DP_LOAD_RESULT_BAD_ARGUMENTS, - DP_LOAD_RESULT_UNKNOWN_FORMAT, - DP_LOAD_RESULT_OPEN_ERROR, - DP_LOAD_RESULT_READ_ERROR, - DP_LOAD_RESULT_BAD_MIMETYPE, - DP_LOAD_RESULT_RECORDING_INCOMPATIBLE, - DP_LOAD_RESULT_UNSUPPORTED_PSD_BITS_PER_CHANNEL, - DP_LOAD_RESULT_UNSUPPORTED_PSD_COLOR_MODE, - DP_LOAD_RESULT_IMAGE_TOO_LARGE, - DP_LOAD_RESULT_INTERNAL_ERROR, -} DP_LoadResult; - -typedef void (*DP_LoadFixedLayerFn)(void *user, int layer_id); - -DP_SaveImageType DP_load_guess(const unsigned char *buf, size_t size); - -DP_CanvasState *DP_load(DP_DrawContext *dc, const char *path, - const char *flat_image_layer_title, unsigned int flags, - DP_LoadResult *out_result, DP_SaveImageType *out_type); - -DP_CanvasState *DP_load_ora(DP_DrawContext *dc, const char *path, - unsigned int flags, - DP_LoadFixedLayerFn on_fixed_layer, void *user, - DP_LoadResult *out_result); - -DP_CanvasState *DP_load_psd(DP_DrawContext *dc, DP_Input *input, - DP_LoadResult *out_result); - -DP_Player *DP_load_recording(const char *path, DP_LoadResult *out_result); - -DP_Player *DP_load_debug_dump(const char *path, DP_LoadResult *out_result); - - -#endif diff --git a/src/drawdance/libengine/dpengine/load_enums.h b/src/drawdance/libengine/dpengine/load_enums.h new file mode 100644 index 0000000000..c7b729344d --- /dev/null +++ b/src/drawdance/libengine/dpengine/load_enums.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPENGINE_LOAD_ENUMS_H +#define DPENGINE_LOAD_ENUMS_H + +typedef enum DP_LoadResult { + DP_LOAD_RESULT_SUCCESS, + DP_LOAD_RESULT_BAD_ARGUMENTS, + DP_LOAD_RESULT_UNKNOWN_FORMAT, + DP_LOAD_RESULT_OPEN_ERROR, + DP_LOAD_RESULT_READ_ERROR, + DP_LOAD_RESULT_BAD_MIMETYPE, + DP_LOAD_RESULT_RECORDING_INCOMPATIBLE, + DP_LOAD_RESULT_UNSUPPORTED_PSD_BITS_PER_CHANNEL, + DP_LOAD_RESULT_UNSUPPORTED_PSD_COLOR_MODE, + DP_LOAD_RESULT_IMAGE_TOO_LARGE, + DP_LOAD_RESULT_INTERNAL_ERROR, +} DP_LoadResult; + +#endif diff --git a/src/drawdance/libengine/dpengine/paint_engine.c b/src/drawdance/libengine/dpengine/paint_engine.c index e092a955f2..935ee769f0 100644 --- a/src/drawdance/libengine/dpengine/paint_engine.c +++ b/src/drawdance/libengine/dpengine/paint_engine.c @@ -73,10 +73,6 @@ #define RECORDER_STARTED 1 #define RECORDER_STOPPED 2 -#define PLAYBACK_STEP_MESSAGES 0 -#define PLAYBACK_STEP_UNDO_POINTS 1 -#define PLAYBACK_STEP_MSECS 2 - #define NO_PUSH 0 #define PUSH_MESSAGE 1 #define PUSH_CLEAR_LOCAL_FORK 2 @@ -156,14 +152,7 @@ struct DP_PaintEngine { DP_RecorderGetTimeMsFn get_time_ms_fn; void *get_time_ms_user; } record; - struct { - DP_Player *player; - long long msecs; - bool next_has_time; - DP_PaintEnginePlaybackFn fn; - DP_PaintEngineDumpPlaybackFn dump_fn; - void *user; - } playback; + DP_PaintEnginePlayback playback; }; @@ -1077,609 +1066,10 @@ bool DP_paint_engine_recorder_is_recording(DP_PaintEngine *pe) } -static void push_playback(DP_PaintEnginePushMessageFn push_message, void *user, - long long position) -{ - push_message(user, DP_msg_internal_playback_new(0, position)); -} - -static long long guess_message_msecs(DP_Message *msg, DP_MessageType type, - bool *out_next_has_time) -{ - // The recording format doesn't contain proper timing information, so we - // just make some wild guesses as to how long each message takes to try to - // get some vaguely okayly timed playback out of it. - switch (type) { - case DP_MSG_DRAW_DABS_CLASSIC: - case DP_MSG_DRAW_DABS_PIXEL: - case DP_MSG_DRAW_DABS_PIXEL_SQUARE: - case DP_MSG_DRAW_DABS_MYPAINT: - return 5; - case DP_MSG_PUT_IMAGE: - case DP_MSG_MOVE_REGION: - case DP_MSG_MOVE_RECT: - case DP_MSG_MOVE_POINTER: - return 10; - case DP_MSG_LAYER_CREATE: - case DP_MSG_LAYER_ATTRIBUTES: - case DP_MSG_LAYER_RETITLE: - case DP_MSG_LAYER_ORDER: - case DP_MSG_LAYER_DELETE: - case DP_MSG_LAYER_VISIBILITY: - case DP_MSG_ANNOTATION_RESHAPE: - case DP_MSG_ANNOTATION_EDIT: - return 20; - case DP_MSG_CANVAS_RESIZE: - return 60; - case DP_MSG_UNDO: - // An undo for user 0 means that the next message is actually an undone - // entry, which happens at the start of recordings. We treat those - // messages as having a length of 0, since they just get appended to the - // history and aren't visible. - if (DP_message_context_id(msg) == 0 - && DP_msg_undo_override_user(DP_message_internal(msg)) == 0) { - *out_next_has_time = false; - return 0; - } - else { - return 20; - } - default: - return 0; - } -} - -static DP_PlayerResult -skip_playback_forward(DP_PaintEngine *pe, long long steps, int what, - DP_PaintEngineFilterMessageFn filter_message_or_null, - DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_ASSERT(steps >= 0); - DP_ASSERT(push_message); - DP_ASSERT(what == PLAYBACK_STEP_MESSAGES - || what == PLAYBACK_STEP_UNDO_POINTS - || what == PLAYBACK_STEP_MSECS); - DP_debug("Skip playback forward by %lld %s", steps, - what == PLAYBACK_STEP_MESSAGES ? "step(s)" - : what == PLAYBACK_STEP_UNDO_POINTS ? "undo point(s)" - : "millisecond(s)"); - - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - long long done = 0; - if (what == PLAYBACK_STEP_MSECS) { - done += pe->playback.msecs; - } - - while (done < steps) { - DP_Message *msg; - DP_PlayerResult result = DP_player_step(player, &msg); - if (result == DP_PLAYER_SUCCESS) { - bool should_time = true; - if (filter_message_or_null) { - unsigned int flags = filter_message_or_null(user, msg); - if (flags & DP_PAINT_ENGINE_FILTER_MESSAGE_FLAG_NO_TIME) { - should_time = false; - } - } - - DP_MessageType type = DP_message_type(msg); - if (type == DP_MSG_INTERVAL) { - if (what == PLAYBACK_STEP_MESSAGES) { - ++done; - } - else if (what == PLAYBACK_STEP_MSECS && should_time) { - DP_MsgInterval *mi = DP_message_internal(msg); - // Really no point in delaying playback for ages, it just - // makes the user wonder if there's something broken. - done += DP_min_int(1000, DP_msg_interval_msecs(mi)); - } - DP_message_decref(msg); - } - else { - if (what == PLAYBACK_STEP_MESSAGES - || (what == PLAYBACK_STEP_UNDO_POINTS - && type == DP_MSG_UNDO_POINT)) { - ++done; - } - else if (what == PLAYBACK_STEP_MSECS) { - if (pe->playback.next_has_time) { - long long msecs = guess_message_msecs( - msg, type, &pe->playback.next_has_time); - if (should_time) { - done += msecs; - } - } - else { - pe->playback.next_has_time = true; - } - } - push_message(user, msg); - } - } - else if (result == DP_PLAYER_ERROR_PARSE) { - if (what == PLAYBACK_STEP_MESSAGES) { - ++done; - } - DP_warn("Can't play back message: %s", DP_error()); - } - else { - // We're either at the end of the recording or encountered an input - // error. In either case, we're done playing this recording, report - // a negative value as the position to indicate that. - push_playback(push_message, user, -1); - return result; - } - } - - if (what == PLAYBACK_STEP_MSECS) { - pe->playback.msecs = done - steps; - } - - // If we don't have an index, report the position as a percentage - // completion based on the amount of bytes read from the recording. - long long position = - DP_player_index_loaded(player) - ? DP_player_position(player) - : DP_double_to_llong(DP_player_progress(player) * 100.0 + 0.5); - push_playback(push_message, user, position); - return DP_PLAYER_SUCCESS; -} - -DP_PlayerResult -DP_paint_engine_playback_step(DP_PaintEngine *pe, long long steps, - DP_PaintEnginePushMessageFn push_message, - void *user) -{ - DP_ASSERT(pe); - DP_ASSERT(steps >= 0); - DP_ASSERT(push_message); - return skip_playback_forward(pe, steps, PLAYBACK_STEP_MESSAGES, NULL, - push_message, user); -} - -static DP_PlayerResult rewind_playback(DP_PaintEngine *pe, - DP_PaintEnginePushMessageFn push_message, - void *user) -{ - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - if (DP_player_rewind(player)) { - push_message(user, DP_msg_internal_reset_new(0)); - push_playback(push_message, user, 0); - return DP_PLAYER_SUCCESS; - } - else { - push_playback(push_message, user, -1); - return DP_PLAYER_ERROR_INPUT; - } -} - -static DP_PlayerResult -jump_playback_to(DP_PaintEngine *pe, DP_DrawContext *dc, long long to, - bool relative, bool exact, - DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - if (!DP_player_index_loaded(player)) { - DP_error_set("No index loaded"); - push_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - long long player_position = DP_player_position(player); - long long target_position = relative ? player_position + to : to; - DP_debug("Jump playback from %lld to %s %lld", player_position, - exact ? "exactly" : "snapshot nearest", target_position); - - long long message_count = - DP_uint_to_llong(DP_player_index_message_count(player)); - if (message_count == 0) { - DP_error_set("Recording contains no messages"); - push_playback(push_message, user, player_position); - return DP_PLAYER_ERROR_INPUT; - } - else if (target_position < 0) { - target_position = 0; - } - else if (target_position >= message_count) { - target_position = message_count - 1; - } - DP_debug("Clamped position to %lld (message count %lld)", target_position, - message_count); - - DP_PlayerIndexEntry entry = DP_player_index_entry_search( - player, target_position, relative && !exact && to > 0); - DP_debug("Loaded entry with message index %lld, message offset %zu, " - "snapshot offset %zu, thumbnail offset %zu", - entry.message_index, entry.message_offset, entry.snapshot_offset, - entry.thumbnail_offset); - - bool inside_snapshot = player_position >= entry.message_index - && player_position < target_position; - if (inside_snapshot) { - long long steps = target_position - player_position; - DP_debug("Already inside snapshot, stepping forward by %lld", steps); - DP_PlayerResult result = skip_playback_forward( - pe, steps, PLAYBACK_STEP_MESSAGES, NULL, push_message, user); - // If skipping playback forward doesn't work, e.g. because we're - // in an input error state, punt to reloading the snapshot. - if (result == DP_PLAYER_SUCCESS) { - return result; - } - else { - DP_warn("Skipping inside snapshot failed with result %d: %s", - (int)result, DP_error()); - } - } - - DP_PlayerIndexEntrySnapshot *snapshot = - DP_player_index_entry_load(player, dc, entry); - if (!snapshot) { - DP_debug("Reading snapshot failed: %s", DP_error()); - push_playback(push_message, user, player_position); - return DP_PLAYER_ERROR_INPUT; - } - - if (!DP_player_seek(player, entry.message_index, entry.message_offset)) { - DP_player_index_entry_snapshot_free(snapshot); - push_playback(push_message, user, player_position); - return DP_PLAYER_ERROR_INPUT; - } - - DP_CanvasState *cs = - DP_player_index_entry_snapshot_canvas_state_inc(snapshot); - push_message(user, DP_msg_internal_reset_to_state_new(0, cs)); - - int count = DP_player_index_entry_snapshot_message_count(snapshot); - for (int i = 0; i < count; ++i) { - DP_Message *msg = - DP_player_index_entry_snapshot_message_at_inc(snapshot, i); - if (msg) { - push_message(user, msg); - } - } - - DP_player_index_entry_snapshot_free(snapshot); - return skip_playback_forward( - pe, exact ? target_position - entry.message_index : 0, - PLAYBACK_STEP_MESSAGES, NULL, push_message, user); -} - -DP_PlayerResult DP_paint_engine_playback_skip_by( - DP_PaintEngine *pe, DP_DrawContext *dc, long long steps, bool by_snapshots, - DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_ASSERT(pe); - if (steps < 0 || by_snapshots) { - return jump_playback_to(pe, dc, steps, true, !by_snapshots, - push_message, user); - } - else { - return skip_playback_forward(pe, steps, PLAYBACK_STEP_UNDO_POINTS, NULL, - push_message, user); - } -} - -DP_PlayerResult DP_paint_engine_playback_jump_to( - DP_PaintEngine *pe, DP_DrawContext *dc, long long position, - DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_ASSERT(pe); - if (position <= 0) { - return rewind_playback(pe, push_message, user); - } - else { - return jump_playback_to(pe, dc, position, false, true, push_message, - user); - } -} - -DP_PlayerResult DP_paint_engine_playback_begin(DP_PaintEngine *pe) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - pe->playback.msecs = 0; - return DP_PLAYER_SUCCESS; - } - else { - DP_error_set("No player set"); - return DP_PLAYER_ERROR_OPERATION; - } -} - -DP_PlayerResult DP_paint_engine_playback_play( - DP_PaintEngine *pe, long long msecs, - DP_PaintEngineFilterMessageFn filter_message_or_null, - DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_ASSERT(pe); - return skip_playback_forward(pe, msecs, PLAYBACK_STEP_MSECS, - filter_message_or_null, push_message, user); -} - -bool DP_paint_engine_playback_index_build( - DP_PaintEngine *pe, DP_DrawContext *dc, - DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, - DP_PlayerIndexProgressFn progress_fn, void *user) -{ - DP_ASSERT(pe); - DP_ASSERT(dc); - DP_Player *player = pe->playback.player; - if (player) { - return DP_player_index_build(player, dc, should_snapshot_fn, - progress_fn, user); - } - else { - DP_error_set("No player set"); - return false; - } -} - -bool DP_paint_engine_playback_index_load(DP_PaintEngine *pe) +DP_PaintEnginePlayback *DP_paint_engine_playback(DP_PaintEngine *pe) { DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - return DP_player_index_load(player); - } - else { - DP_error_set("No player set"); - return false; - } -} - -unsigned int DP_paint_engine_playback_index_message_count(DP_PaintEngine *pe) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - return DP_player_index_message_count(player); - } - else { - DP_error_set("No player set"); - return 0; - } -} - -size_t DP_paint_engine_playback_index_entry_count(DP_PaintEngine *pe) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - return DP_player_index_entry_count(player); - } - else { - DP_error_set("No player set"); - return 0; - } -} - -DP_Image *DP_paint_engine_playback_index_thumbnail_at(DP_PaintEngine *pe, - size_t index, - bool *out_error) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - return DP_player_index_thumbnail_at(player, index, out_error); - } - else { - DP_error_set("No player set"); - if (out_error) { - *out_error = true; - } - return NULL; - } -} - -static DP_PlayerResult step_dump(DP_Player *player, - DP_PaintEnginePushMessageFn push_message, - void *user) -{ - DP_DumpType type; - int count; - DP_Message **msgs; - DP_PlayerResult result = DP_player_step_dump(player, &type, &count, &msgs); - if (result == DP_PLAYER_SUCCESS) { - push_message(user, DP_msg_internal_dump_command_new_inc(0, (int)type, - count, msgs)); - } - return result; -} - -static void push_dump_playback(DP_PaintEnginePushMessageFn push_message, - void *user, long long position) -{ - push_message(user, DP_msg_internal_dump_playback_new(0, position)); -} - -DP_PlayerResult DP_paint_engine_playback_dump_step( - DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_dump_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - DP_PlayerResult result = step_dump(player, push_message, user); - long long position_to_report = - result == DP_PLAYER_SUCCESS ? DP_player_position(player) : -1; - push_dump_playback(push_message, user, position_to_report); - return result; -} - -DP_PlayerResult DP_paint_engine_playback_dump_jump_previous_reset( - DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_dump_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - if (!DP_player_seek_dump(player, DP_player_position(player) - 1)) { - push_dump_playback(push_message, user, -1); - return DP_PLAYER_ERROR_INPUT; - } - - push_message(user, DP_msg_internal_reset_new(0)); - push_dump_playback(push_message, user, DP_player_position(player)); - return DP_PLAYER_SUCCESS; -} - -static int step_toward_next_reset(DP_Player *player, bool stop_at_reset, - DP_PaintEnginePushMessageFn push_message, - void *user) -{ - long long position = DP_player_position(player); - size_t offset = DP_player_tell(player); - DP_DumpType type; - int count; - DP_Message **msgs; - DP_PlayerResult result = DP_player_step_dump(player, &type, &count, &msgs); - if (result == DP_PLAYER_SUCCESS) { - if (type == DP_DUMP_RESET && stop_at_reset) { - if (DP_player_seek(player, position, offset)) { - return -1; - } - else { - return DP_PLAYER_ERROR_INPUT; - } - } - else { - push_message(user, DP_msg_internal_dump_command_new_inc( - 0, (int)type, count, msgs)); - } - } - return (int)result; -} - -DP_PlayerResult DP_paint_engine_playback_dump_jump_next_reset( - DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_dump_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - // We want to jump up to, but excluding the next reset. So we keep walking - // through the dump until we hit a reset and then seek to before it. If we - // hit a reset as our very first message, we push through it, since - // otherwise we'd be doing nothing at all until manually stepping forward. - int result = step_toward_next_reset(player, false, push_message, user); - while (result == DP_PLAYER_SUCCESS) { - result = step_toward_next_reset(player, true, push_message, user); - } - - push_dump_playback(push_message, user, DP_player_position(player)); - return result == -1 ? DP_PLAYER_SUCCESS : (DP_PlayerResult)result; -} - -DP_PlayerResult -DP_paint_engine_playback_dump_jump(DP_PaintEngine *pe, long long position, - DP_PaintEnginePushMessageFn push_message, - void *user) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (!player) { - DP_error_set("No player set"); - push_dump_playback(push_message, user, -1); - return DP_PLAYER_ERROR_OPERATION; - } - - if (!DP_player_seek_dump(player, position)) { - push_dump_playback(push_message, user, -1); - return DP_PLAYER_ERROR_INPUT; - } - - if (DP_player_position(player) == 0) { - push_message(user, DP_msg_internal_reset_new(0)); - } - - DP_PlayerResult result = DP_PLAYER_SUCCESS; - while (DP_player_position(player) < position) { - result = step_dump(player, push_message, user); - if (result != DP_PLAYER_SUCCESS) { - break; - } - } - push_dump_playback(push_message, user, DP_player_position(player)); - return result; -} - -bool DP_paint_engine_playback_flush(DP_PaintEngine *pe, - DP_PaintEnginePushMessageFn push_message, - void *user) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - while (true) { - DP_Message *msg; - DP_PlayerResult result = DP_player_step(player, &msg); - if (result == DP_PLAYER_SUCCESS) { - DP_MessageType type = DP_message_type(msg); - if (type == DP_MSG_INTERVAL) { - DP_message_decref(msg); - } - else { - push_message(user, msg); - } - } - else if (result == DP_PLAYER_ERROR_PARSE) { - DP_warn("Can't play back message: %s", DP_error()); - } - else if (result == DP_PLAYER_RECORDING_END) { - return true; - } - else { - return false; // Some kind of input error. - } - } - } - else { - return false; - } -} - -bool DP_paint_engine_playback_close(DP_PaintEngine *pe) -{ - DP_ASSERT(pe); - DP_Player *player = pe->playback.player; - if (player) { - DP_player_free(player); - pe->playback.player = NULL; - return true; - } - else { - return false; - } + return &pe->playback; } diff --git a/src/drawdance/libengine/dpengine/paint_engine.h b/src/drawdance/libengine/dpengine/paint_engine.h index 73dbfb0227..aad6d7d670 100644 --- a/src/drawdance/libengine/dpengine/paint_engine.h +++ b/src/drawdance/libengine/dpengine/paint_engine.h @@ -86,6 +86,15 @@ typedef void (*DP_PaintEnginePushMessageFn)(void *user, DP_Message *msg); typedef struct DP_PaintEngine DP_PaintEngine; +typedef struct DP_PaintEnginePlayback { + DP_Player *player; + long long msecs; + bool next_has_time; + DP_PaintEnginePlaybackFn fn; + DP_PaintEngineDumpPlaybackFn dump_fn; + void *user; +} DP_PaintEnginePlayback; + DP_PaintEngine *DP_paint_engine_new_inc( DP_DrawContext *paint_dc, DP_DrawContext *main_dc, DP_DrawContext *preview_dc, DP_AclState *acls, DP_CanvasState *cs_or_null, @@ -154,60 +163,7 @@ bool DP_paint_engine_recorder_stop(DP_PaintEngine *pe); bool DP_paint_engine_recorder_is_recording(DP_PaintEngine *pe); -DP_PlayerResult -DP_paint_engine_playback_step(DP_PaintEngine *pe, long long steps, - DP_PaintEnginePushMessageFn push_message, - void *user); - -DP_PlayerResult DP_paint_engine_playback_skip_by( - DP_PaintEngine *pe, DP_DrawContext *dc, long long steps, bool by_snapshots, - DP_PaintEnginePushMessageFn push_message, void *user); - -DP_PlayerResult DP_paint_engine_playback_jump_to( - DP_PaintEngine *pe, DP_DrawContext *dc, long long position, - DP_PaintEnginePushMessageFn push_message, void *user); - -DP_PlayerResult DP_paint_engine_playback_begin(DP_PaintEngine *pe); - -DP_PlayerResult DP_paint_engine_playback_play( - DP_PaintEngine *pe, long long msecs, - DP_PaintEngineFilterMessageFn filter_message_or_null, - DP_PaintEnginePushMessageFn push_message, void *user); - -bool DP_paint_engine_playback_index_build( - DP_PaintEngine *pe, DP_DrawContext *dc, - DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, - DP_PlayerIndexProgressFn progress_fn, void *user); - -bool DP_paint_engine_playback_index_load(DP_PaintEngine *pe); - -unsigned int DP_paint_engine_playback_index_message_count(DP_PaintEngine *pe); - -size_t DP_paint_engine_playback_index_entry_count(DP_PaintEngine *pe); - -DP_Image *DP_paint_engine_playback_index_thumbnail_at(DP_PaintEngine *pe, - size_t index, - bool *out_error); - -DP_PlayerResult DP_paint_engine_playback_dump_step( - DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user); - -DP_PlayerResult DP_paint_engine_playback_dump_jump_previous_reset( - DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user); - -DP_PlayerResult DP_paint_engine_playback_dump_jump_next_reset( - DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user); - -DP_PlayerResult -DP_paint_engine_playback_dump_jump(DP_PaintEngine *pe, long long position, - DP_PaintEnginePushMessageFn push_message, - void *user); - -bool DP_paint_engine_playback_flush(DP_PaintEngine *pe, - DP_PaintEnginePushMessageFn push_message, - void *user); - -bool DP_paint_engine_playback_close(DP_PaintEngine *pe); +DP_PaintEnginePlayback *DP_paint_engine_playback(DP_PaintEngine *pe); // Returns the number of drawing commands actually pushed to the paint engine. int DP_paint_engine_handle_inc(DP_PaintEngine *pe, bool local, diff --git a/src/drawdance/libengine/dpengine/player.c b/src/drawdance/libengine/dpengine/player.c index b38f5e4e56..9fe6f4fcdf 100644 --- a/src/drawdance/libengine/dpengine/player.c +++ b/src/drawdance/libengine/dpengine/player.c @@ -20,25 +20,10 @@ * License, version 3. See 3rdparty/licenses/drawpile/COPYING for details. */ #include "player.h" -#include "annotation.h" -#include "annotation_list.h" #include "canvas_history.h" -#include "document_metadata.h" -#include "draw_context.h" #include "dump_reader.h" #include "image.h" -#include "key_frame.h" -#include "layer_content.h" -#include "layer_group.h" -#include "layer_list.h" -#include "layer_props.h" -#include "layer_props_list.h" -#include "limits.h" -#include "load.h" #include "local_state.h" -#include "tile.h" -#include "timeline.h" -#include "track.h" #include #include #include @@ -53,29 +38,8 @@ #include #include #include -#include -#define DP_PERF_CONTEXT "player" - - -#define INDEX_EXTENSION "dpidx" -#define INDEX_MAGIC "DPIDX" -#define INDEX_MAGIC_LENGTH 6 -#define INDEX_VERSION 12 -#define INDEX_VERSION_LENGTH 2 -#define INDEX_HEADER_LENGTH (INDEX_MAGIC_LENGTH + INDEX_VERSION_LENGTH + 12) -#define INITAL_ENTRY_CAPACITY 64 - -static_assert(INDEX_MAGIC_LENGTH < sizeof(DP_OutputBinaryEntry), - "index header fits into output binary entry"); - - -typedef struct DP_PlayerIndex { - DP_BufferedInput input; - unsigned int message_count; - DP_PlayerIndexEntry *entries; - size_t entry_count; -} DP_PlayerIndex; +#define INDEX_EXTENSION "dpidx" typedef union DP_PlayerReader { DP_BinaryReader *binary; @@ -98,12 +62,6 @@ struct DP_Player { DP_PlayerIndex index; }; -struct DP_PlayerIndexEntrySnapshot { - DP_CanvasState *cs; - int message_count; - DP_Message *messages[]; -}; - static void player_index_dispose(DP_PlayerIndex *pi) { @@ -433,12 +391,37 @@ void DP_player_acl_override_set(DP_Player *player, bool override) player->acl_override = override; } +const char *DP_player_recording_path(DP_Player *player) +{ + DP_ASSERT(player); + return player->recording_path; +} + +const char *DP_player_index_path(DP_Player *player) +{ + DP_ASSERT(player); + return player->index_path; +} + bool DP_player_index_loaded(DP_Player *player) { DP_ASSERT(player); return player->index.entries; } +DP_PlayerIndex *DP_player_index(DP_Player *player) +{ + DP_ASSERT(player); + return &player->index; +} + +void DP_player_index_set(DP_Player *player, DP_PlayerIndex index) +{ + DP_ASSERT(player); + player_index_dispose(&player->index); + player->index = index; +} + size_t DP_player_tell(DP_Player *player) { DP_ASSERT(player); @@ -690,7 +673,7 @@ bool DP_player_seek(DP_Player *player, long long position, size_t offset) } } -static size_t player_body_offset(DP_Player *player) +size_t DP_player_body_offset(DP_Player *player) { switch (player->type) { case DP_PLAYER_TYPE_BINARY: @@ -707,7 +690,7 @@ static size_t player_body_offset(DP_Player *player) bool DP_player_rewind(DP_Player *player) { DP_ASSERT(player); - return DP_player_seek(player, 0, player_body_offset(player)); + return DP_player_seek(player, 0, DP_player_body_offset(player)); } bool DP_player_seek_dump(DP_Player *player, long long position) @@ -731,1993 +714,3 @@ bool DP_player_seek_dump(DP_Player *player, long long position) return false; } } - - -typedef struct DP_BuildIndexTileMap { - DP_Tile *t; - size_t offset; - UT_hash_handle hh; -} DP_BuildIndexTileMap; - -typedef struct DP_BuildIndexLayerKey { - union { - DP_LayerContent *lc; - DP_LayerGroup *lg; - }; - DP_LayerProps *lp; -} DP_BuildIndexLayerKey; - -typedef struct DP_BuildIndexLayerMap { - DP_BuildIndexLayerKey key; - size_t offset; - UT_hash_handle hh; -} DP_BuildIndexLayerMap; - -typedef struct DP_BuildIndexAnnotationMap { - DP_Annotation *a; - size_t offset; - UT_hash_handle hh; -} DP_BuildIndexAnnotationMap; - -typedef struct DP_BuildIndexMaps { - DP_BuildIndexTileMap *tiles; - DP_BuildIndexLayerMap *layers; - DP_BuildIndexAnnotationMap *annotations; - struct { - DP_DocumentMetadata *dm; - size_t offset; - } metadata; - struct { - DP_Timeline *tl; - size_t offset; - } timeline; -} DP_BuildIndexMaps; - -typedef struct DP_BuildIndexEntryContext { - DP_Output *output; - DP_AclState *acls; - DP_LocalState *local_state; - DP_CanvasHistory *ch; - DP_CanvasState *cs; - DP_DrawContext *dc; - DP_BuildIndexMaps current; - DP_BuildIndexMaps *last; - int message_count; - struct { - size_t history; - size_t layers; - size_t background_tile; - size_t snapshot; - size_t thumbnail; - } offset; - struct { - unsigned char *buffer; - size_t size; - } annotation; -} DP_BuildIndexEntryContext; - -typedef struct DP_BuildIndexContext { - DP_Player *player; - DP_Output *output; - DP_AclState *acls; - DP_LocalState *local_state; - DP_CanvasHistory *ch; - DP_DrawContext *dc; - long long message_count; - DP_Vector entries; - DP_BuildIndexMaps last; - DP_PlayerIndexShouldSnapshotFn should_snapshot_fn; - DP_PlayerIndexProgressFn progress_fn; - void *user; -} DP_BuildIndexContext; - -struct DP_BuildIndexLayerProps { - uint16_t layer_id; - uint16_t title_length; - union { - const char *title; - char *buffer; - }; - uint8_t opacity; - uint8_t blend_mode; - uint8_t hidden; - uint8_t censored; - uint8_t isolated; - uint8_t group; -}; - -static bool write_index_header(DP_BuildIndexContext *c) -{ - return DP_OUTPUT_WRITE_LITTLEENDIAN( - c->output, DP_OUTPUT_BYTES(INDEX_MAGIC, INDEX_MAGIC_LENGTH), - DP_OUTPUT_UINT16(INDEX_VERSION), DP_OUTPUT_UINT32(0), - DP_OUTPUT_UINT64(0)); -} - -unsigned char *get_message_buffer(void *user, size_t length) -{ - return DP_draw_context_pool_require(user, length); -} - -static bool write_index_history_message_dec(DP_BuildIndexEntryContext *e, - DP_Message *msg) -{ - size_t length = DP_message_serialize(msg, false, get_message_buffer, e->dc); - DP_message_decref(msg); - if (length == 0) { - DP_error_set("Error serializing history message %d", - e->message_count + 1); - return true; // Not a fatal error. - } - else { - DP_Output *output = e->output; - bool ok = DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT16(length)) - && DP_output_write(output, DP_draw_context_pool(e->dc), length); - if (ok) { - ++e->message_count; - return true; - } - else { - return false; - } - } -} - -static bool init_reset_image(void *user, DP_CanvasState *cs) -{ - DP_BuildIndexEntryContext *e = user; - e->cs = cs; - DP_Message *msg = DP_acl_state_msg_feature_access_all_new(0); - return write_index_history_message_dec(e, msg); -} - -static bool write_reset_image_message(void *user, DP_Message *msg) -{ - return write_index_history_message_dec(user, msg); -} - -static bool write_index_history(DP_BuildIndexEntryContext *e) -{ - bool error; - size_t offset = DP_output_tell(e->output, &error); - if (error) { - return false; - } - - if (!DP_canvas_history_reset_image_new(e->ch, init_reset_image, - write_reset_image_message, e)) { - return false; - } - - // The state of the permissions at this point. - if (!DP_acl_state_reset_image_build( - e->acls, 0, DP_ACL_STATE_RESET_IMAGE_RECORDING_FLAGS, - write_reset_image_message, e)) { - return false; - } - // Local changes (hidden layers, local canvas background). - if (!DP_local_state_reset_image_build(e->local_state, e->dc, - write_reset_image_message, e)) { - return false; - } - - e->offset.history = offset; - return true; -} - -static DP_BuildIndexTileMap *search_tile(DP_BuildIndexTileMap *tiles, - DP_Tile *t) -{ - DP_BuildIndexTileMap *entry; - HASH_FIND_PTR(tiles, &t, entry); - return entry; -} - -static void move_tile_offset(DP_BuildIndexEntryContext *e, - DP_BuildIndexTileMap *entry) -{ - HASH_DEL(e->last->tiles, entry); - HASH_ADD_PTR(e->current.tiles, t, entry); -} - -static void move_tile_offsets(DP_BuildIndexEntryContext *e, DP_LayerContent *lc) -{ - DP_TileCounts tile_counts = DP_tile_counts_round( - DP_layer_content_width(lc), DP_layer_content_height(lc)); - for (int y = 0; y < tile_counts.y; ++y) { - for (int x = 0; x < tile_counts.x; ++x) { - DP_Tile *t = DP_layer_content_tile_at_noinc(lc, x, y); - DP_BuildIndexTileMap *entry; - bool should_move = t && !search_tile(e->current.tiles, t) - && (entry = search_tile(e->last->tiles, t)) != NULL; - if (should_move) { - move_tile_offset(e, entry); - } - } - } -} - -static unsigned char *get_compression_buffer(size_t size, void *user) -{ - size_t required_capacity = sizeof(uint16_t) + size; - unsigned char *pool = DP_draw_context_pool_require(user, required_capacity); - return pool + sizeof(uint16_t); -} - -static size_t write_index_tile(DP_BuildIndexEntryContext *e, DP_Tile *t) -{ - size_t size = DP_tile_compress(t, DP_draw_context_tile8_buffer(e->dc), - get_compression_buffer, e->dc); - if (size == 0) { - return 0; - } - - bool error; - size_t offset = DP_output_tell(e->output, &error); - if (error) { - return 0; - } - - unsigned char *buffer = DP_draw_context_pool(e->dc); - DP_write_littleendian_uint16(DP_size_to_uint16(size), buffer); - if (!DP_output_write(e->output, buffer, size + sizeof(uint16_t))) { - return 0; - } - - return offset; -} - -static bool maybe_write_index_tile(DP_BuildIndexEntryContext *e, DP_Tile *t, - size_t *out_offset) -{ - if (t) { - DP_BuildIndexTileMap *entry; - if ((entry = search_tile(e->current.tiles, t)) != NULL) { - *out_offset = entry->offset; - } - else if ((entry = search_tile(e->last->tiles, t)) != NULL) { - move_tile_offset(e, entry); - *out_offset = entry->offset; - } - else { - size_t offset = write_index_tile(e, t); - if (offset != 0) { - *out_offset = offset; - } - else { - return false; - } - } - } - else { - *out_offset = 0; - } - return true; -} - -static size_t search_existing_layer(DP_BuildIndexEntryContext *e, - DP_BuildIndexLayerKey *key) -{ - DP_BuildIndexLayerMap *entry; - // If we already wrote the layer this round, it might be here. Shouldn't - // happen in practice, since layer ids are supposed to be unique. - HASH_FIND(hh, e->current.layers, key, sizeof(*key), entry); - if (entry) { - return entry->offset; - } - // If we wrote the layer last snapshot, move it to the current set. - HASH_FIND(hh, e->last->layers, key, sizeof(*key), entry); - if (entry) { - HASH_DEL(e->last->layers, entry); - HASH_ADD(hh, e->current.layers, key, sizeof(entry->key), entry); - if (!DP_layer_props_children_noinc(entry->key.lp)) { - move_tile_offsets(e, entry->key.lc); - } - return entry->offset; - } - else { - return 0; - } -} - -static size_t write_index_layer_list(DP_BuildIndexEntryContext *e, - struct DP_BuildIndexLayerProps *bilp, - DP_LayerList *ll, DP_LayerPropsList *lpl); - -static struct DP_BuildIndexLayerProps to_index_layer_props(DP_LayerProps *lp) -{ - size_t title_length; - const char *title = DP_layer_props_title(lp, &title_length); - return (struct DP_BuildIndexLayerProps){ - DP_int_to_uint16(DP_layer_props_id(lp)), - DP_size_to_uint16(title_length), - {.title = title}, - DP_channel15_to_8(DP_layer_props_opacity(lp)), - DP_int_to_uint8(DP_layer_props_blend_mode(lp)), - DP_layer_props_hidden(lp) ? 1 : 0, - DP_layer_props_censored(lp) ? 1 : 0, - DP_layer_props_isolated(lp) ? 1 : 0, - DP_layer_props_children_noinc(lp) ? 1 : 0}; -} - -static bool write_index_layer_props(DP_BuildIndexEntryContext *e, - struct DP_BuildIndexLayerProps *bilp) -{ - DP_Output *output = e->output; - return DP_OUTPUT_WRITE_LITTLEENDIAN(output, - DP_OUTPUT_UINT16(bilp->layer_id), - DP_OUTPUT_UINT16(bilp->title_length)) - && DP_output_write(output, bilp->title, bilp->title_length) - && DP_OUTPUT_WRITE_LITTLEENDIAN( - output, DP_OUTPUT_UINT8(bilp->opacity), - DP_OUTPUT_UINT8(bilp->blend_mode), DP_OUTPUT_UINT8(bilp->hidden), - DP_OUTPUT_UINT8(bilp->censored), DP_OUTPUT_UINT8(bilp->isolated), - DP_OUTPUT_UINT8(bilp->group)); -} - -static bool is_relevant_sublayer(DP_LayerProps *sub_lp) -{ - int sub_id = DP_layer_props_id(sub_lp); - return sub_id >= 0 && sub_id <= UINT8_MAX; -} - -static size_t write_index_layer_content(DP_BuildIndexEntryContext *e, - DP_LayerContent *lc, DP_LayerProps *lp, - bool sublayer) -{ - if (!sublayer) { // Sublayers are ephemeral, don't try re-using them. - DP_BuildIndexLayerKey key; // There's not gonna be padding here, but - memset(&key, 0, sizeof(key)); // we'll zero it anyway just to make sure. - key.lc = lc; - key.lp = lp; - size_t existing_offset = search_existing_layer(e, &key); - if (existing_offset != 0) { - return existing_offset; - } - } - - DP_LayerPropsList *sub_lpl = DP_layer_content_sub_props_noinc(lc); - int sub_count = DP_layer_props_list_count(sub_lpl); - size_t relevant_sub_count = 0; - for (int i = 0; i < sub_count; ++i) { - DP_LayerProps *sub_lp = DP_layer_props_list_at_noinc(sub_lpl, i); - if (is_relevant_sublayer(sub_lp)) { - ++relevant_sub_count; - } - } - - size_t sub_buffer_size = - sizeof(uint16_t) + sizeof(uint64_t) * relevant_sub_count; - unsigned char *sub_buffer = DP_malloc(sub_buffer_size); - DP_write_littleendian_uint16(DP_size_to_uint16(relevant_sub_count), - sub_buffer); - - size_t sub_index = 0; - DP_LayerList *sub_ll = DP_layer_content_sub_contents_noinc(lc); - for (int i = sub_count - 1; i >= 0; --i) { // Top to bottom. - DP_LayerProps *sub_lp = DP_layer_props_list_at_noinc(sub_lpl, i); - if (is_relevant_sublayer(sub_lp)) { - DP_LayerContent *sub_lc = DP_layer_list_content_at_noinc(sub_ll, i); - size_t sub_offset = - write_index_layer_content(e, sub_lc, sub_lp, true); - - if (sub_offset != 0) { - size_t ii = relevant_sub_count - sub_index - 1; - unsigned char *out = - sub_buffer + sizeof(uint16_t) + ii * sizeof(uint64_t); - DP_write_littleendian_uint64(sub_offset, out); - ++sub_index; - } - else { - DP_free(sub_buffer); - return 0; - } - } - } - DP_ASSERT(sub_index == relevant_sub_count); - - DP_TileCounts tile_counts = DP_tile_counts_round( - DP_layer_content_width(lc), DP_layer_content_height(lc)); - int tile_total = tile_counts.x * tile_counts.y; - size_t tile_buffer_size = DP_int_to_size(tile_total) * sizeof(uint64_t); - unsigned char *tile_buffer = DP_malloc(tile_buffer_size); - size_t written = 0; - for (int y = 0; y < tile_counts.y; ++y) { - for (int x = 0; x < tile_counts.x; ++x) { - DP_Tile *t = DP_layer_content_tile_at_noinc(lc, x, y); - size_t tile_offset; - if (!maybe_write_index_tile(e, t, &tile_offset)) { - DP_free(sub_buffer); - DP_free(tile_buffer); - return 0; - } - written += DP_write_littleendian_uint64(tile_offset, - tile_buffer + written); - } - } - DP_ASSERT(written == tile_buffer_size); - - bool error; - size_t offset = DP_output_tell(e->output, &error); - struct DP_BuildIndexLayerProps bilp = to_index_layer_props(lp); - bool ok = !error && write_index_layer_props(e, &bilp) - && DP_output_write(e->output, sub_buffer, sub_buffer_size) - && DP_output_write(e->output, tile_buffer, tile_buffer_size); - DP_free(sub_buffer); - DP_free(tile_buffer); - return ok ? offset : 0; -} - -static size_t write_index_layer_group(DP_BuildIndexEntryContext *e, - DP_LayerGroup *lg, DP_LayerProps *lp) -{ - DP_BuildIndexLayerKey key; // There's not gonna be padding here, but - memset(&key, 0, sizeof(key)); // we'll zero it anyway just to make sure. - key.lg = lg; - key.lp = lp; - size_t existing_offset = search_existing_layer(e, &key); - if (existing_offset != 0) { - return existing_offset; - } - - struct DP_BuildIndexLayerProps bilp = to_index_layer_props(lp); - size_t offset = - write_index_layer_list(e, &bilp, DP_layer_group_children_noinc(lg), - DP_layer_props_children_noinc(lp)); - - if (offset != 0) { - DP_BuildIndexLayerMap *entry = DP_malloc_zeroed(sizeof(*entry)); - entry->key.lg = DP_layer_group_incref(lg); - entry->key.lp = DP_layer_props_incref(lp); - entry->offset = offset; - HASH_ADD(hh, e->current.layers, key, sizeof(entry->key), entry); - } - - return offset; -} - -static size_t write_index_layer_list(DP_BuildIndexEntryContext *e, - struct DP_BuildIndexLayerProps *bilp, - DP_LayerList *ll, DP_LayerPropsList *lpl) -{ - int count = DP_layer_list_count(ll); - DP_ASSERT(DP_layer_props_list_count(lpl) == count); - // A buffer to hold the layer count plus each layer's offset. - size_t size = sizeof(uint16_t) + DP_int_to_size(count) * sizeof(uint64_t); - unsigned char *buffer = DP_malloc(size); - DP_write_littleendian_uint16(DP_int_to_uint16(count), buffer); - - for (int i = count - 1; i >= 0; --i) { // Top to bottom. - DP_LayerListEntry *lle = DP_layer_list_at_noinc(ll, i); - DP_LayerProps *lp = DP_layer_props_list_at_noinc(lpl, i); - size_t child_offset; - if (DP_layer_props_children_noinc(lp)) { - DP_LayerGroup *lg = DP_layer_list_entry_group_noinc(lle); - child_offset = write_index_layer_group(e, lg, lp); - } - else { - DP_LayerContent *lc = DP_layer_list_entry_content_noinc(lle); - child_offset = write_index_layer_content(e, lc, lp, false); - } - - if (child_offset != 0) { - size_t ii = DP_int_to_size(count - i - 1); - unsigned char *out = - buffer + sizeof(uint16_t) + ii * sizeof(uint64_t); - DP_write_littleendian_uint64(child_offset, out); - } - else { - DP_free(buffer); - return 0; - } - } - - bool error; - size_t offset = DP_output_tell(e->output, &error); - bool ok = !error && write_index_layer_props(e, bilp) - && DP_output_write(e->output, buffer, size); - DP_free(buffer); - return ok ? offset : 0; -} - -static bool write_index_layers(DP_BuildIndexEntryContext *e) -{ - struct DP_BuildIndexLayerProps bilp = { - 0, 0, {.title = ""}, UINT8_MAX, DP_BLEND_MODE_NORMAL, 0, 0, 0, 1}; - size_t offset = - write_index_layer_list(e, &bilp, DP_canvas_state_layers_noinc(e->cs), - DP_canvas_state_layer_props_noinc(e->cs)); - if (offset != 0) { - e->offset.layers = offset; - return true; - } - else { - return false; - } -} - -static size_t write_index_annotation(DP_BuildIndexEntryContext *e, - DP_Annotation *a) -{ - DP_BuildIndexAnnotationMap *entry; - - HASH_FIND_PTR(e->current.annotations, &a, entry); - if (entry) { - return entry->offset; - } - - HASH_FIND_PTR(e->last->annotations, &a, entry); - if (entry) { - HASH_DEL(e->last->annotations, entry); - HASH_ADD_PTR(e->current.annotations, a, entry); - return entry->offset; - } - - DP_Output *output = e->output; - bool error; - size_t offset = DP_output_tell(output, &error); - if (error) { - return 0; - } - - size_t text_length; - const char *text = DP_annotation_text(a, &text_length); - bool ok = DP_OUTPUT_WRITE_LITTLEENDIAN( - output, DP_OUTPUT_UINT16(DP_annotation_id(a)), - DP_OUTPUT_INT32(DP_annotation_x(a)), - DP_OUTPUT_INT32(DP_annotation_y(a)), - DP_OUTPUT_INT32(DP_annotation_width(a)), - DP_OUTPUT_INT32(DP_annotation_height(a)), - DP_OUTPUT_UINT32(DP_annotation_background_color(a)), - DP_OUTPUT_UINT8(DP_annotation_valign(a)), - DP_OUTPUT_UINT16(text_length)) - && DP_output_write(output, text, text_length); - if (!ok) { - return 0; - } - - entry = DP_malloc(sizeof(*entry)); - entry->a = DP_annotation_incref(a); - entry->offset = offset; - HASH_ADD_PTR(e->current.annotations, a, entry); - return offset; -} - -static bool write_index_annotations(DP_BuildIndexEntryContext *e) -{ - DP_AnnotationList *al = DP_canvas_state_annotations_noinc(e->cs); - int count = DP_annotation_list_count(al); - size_t size = sizeof(uint16_t) + DP_int_to_size(count) * sizeof(uint64_t); - unsigned char *buffer = DP_malloc(size); - DP_write_littleendian_uint16(DP_int_to_uint16(count), buffer); - - for (int i = 0; i < count; ++i) { - DP_Annotation *a = DP_annotation_list_at_noinc(al, i); - size_t offset = write_index_annotation(e, a); - if (offset != 0) { - unsigned char *out = buffer + sizeof(uint16_t) - + DP_int_to_size(i) * sizeof(uint64_t); - DP_write_littleendian_uint64(offset, out); - } - else { - DP_free(buffer); - return false; - } - } - - e->annotation.buffer = buffer; - e->annotation.size = size; - return true; -} - -static bool write_index_background_tile(DP_BuildIndexEntryContext *e) -{ - DP_Tile *t = DP_canvas_state_background_tile_noinc(e->cs); - size_t offset; - if (maybe_write_index_tile(e, t, &offset)) { - e->offset.background_tile = offset; - return true; - } - else { - return false; - } -} - -static bool write_index_key_frame(DP_Output *output, int frame_index, - DP_KeyFrame *kf) -{ - size_t title_length; - const char *title = DP_key_frame_title(kf, &title_length); - int layers_count; - const DP_KeyFrameLayer *layers = DP_key_frame_layers(kf, &layers_count); - - bool ok = DP_OUTPUT_WRITE_LITTLEENDIAN( - output, DP_OUTPUT_UINT16(layers_count), - DP_OUTPUT_UINT16(frame_index), - DP_OUTPUT_UINT16(DP_key_frame_layer_id(kf)), - DP_OUTPUT_UINT16(title_length)) - && DP_output_write(output, title, title_length); - if (!ok) { - return false; - } - - for (int i = 0; i < layers_count; ++i) { - const DP_KeyFrameLayer *kfl = &layers[i]; - if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, - DP_OUTPUT_UINT16(kfl->layer_id), - DP_OUTPUT_UINT16(kfl->flags))) { - return false; - } - } - - return true; -} - -static bool write_index_track(DP_Output *output, DP_Track *t) -{ - size_t title_length; - const char *title = DP_track_title(t, &title_length); - int key_frame_count = DP_track_key_frame_count(t); - - bool ok = - DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT16(key_frame_count), - DP_OUTPUT_UINT16(DP_track_id(t)), - DP_OUTPUT_UINT16(title_length)) - && DP_output_write(output, title, title_length); - if (!ok) { - return false; - } - - for (int i = 0; i < key_frame_count; ++i) { - int frame_index = DP_track_frame_index_at_noinc(t, i); - DP_KeyFrame *kf = DP_track_key_frame_at_noinc(t, i); - if (!write_index_key_frame(output, frame_index, kf)) { - return false; - } - } - - return true; -} - -static bool write_index_timeline(DP_BuildIndexEntryContext *e) -{ - DP_Timeline *tl = DP_canvas_state_timeline_noinc(e->cs); - if (e->last->timeline.tl == tl) { - e->current.timeline = e->last->timeline; - e->last->timeline.tl = NULL; - return true; - } - - DP_Output *output = e->output; - bool error; - size_t offset = DP_output_tell(output, &error); - if (error) { - return false; - } - - int track_count = DP_timeline_count(tl); - if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT16(track_count))) { - return false; - } - - for (int i = 0; i < track_count; ++i) { - DP_Track *t = DP_timeline_at_noinc(tl, i); - if (!write_index_track(output, t)) { - return false; - } - } - - e->current.timeline.tl = DP_timeline_incref(tl); - e->current.timeline.offset = offset; - return true; -} - -static bool write_index_metadata(DP_BuildIndexEntryContext *e) -{ - DP_DocumentMetadata *dm = DP_canvas_state_metadata_noinc(e->cs); - if (e->last->metadata.dm == dm) { - e->current.metadata = e->last->metadata; - e->last->metadata.dm = NULL; - return true; - } - - DP_Output *output = e->output; - bool error; - size_t offset = DP_output_tell(output, &error); - if (error) { - return false; - } - - if (!DP_OUTPUT_WRITE_LITTLEENDIAN( - output, DP_OUTPUT_INT32(DP_document_metadata_dpix(dm)), - DP_OUTPUT_INT32(DP_document_metadata_dpiy(dm)), - DP_OUTPUT_INT32(DP_document_metadata_framerate(dm)), - DP_OUTPUT_INT32(DP_document_metadata_frame_count(dm)))) { - return false; - } - - e->current.metadata.dm = DP_document_metadata_incref(dm); - e->current.metadata.offset = offset; - return true; -} - -static bool write_index_canvas_state(DP_BuildIndexEntryContext *e) -{ - DP_Output *output = e->output; - bool error; - size_t offset = DP_output_tell(output, &error); - if (error) { - return false; - } - - DP_debug("Write canvas state at offset %zu", offset); - DP_CanvasState *cs = e->cs; - if (!DP_OUTPUT_WRITE_LITTLEENDIAN( - output, DP_OUTPUT_UINT32(DP_canvas_state_width(cs)), - DP_OUTPUT_UINT32(DP_canvas_state_height(cs)), - DP_OUTPUT_UINT16(e->message_count), - DP_OUTPUT_UINT64(e->offset.history), - DP_OUTPUT_UINT64(e->offset.background_tile), - DP_OUTPUT_UINT64(e->current.timeline.offset), - DP_OUTPUT_UINT64(e->current.metadata.offset), - DP_OUTPUT_UINT64(e->offset.layers))) { - return false; - } - - if (!DP_output_write(output, e->annotation.buffer, e->annotation.size)) { - return false; - } - - e->offset.snapshot = offset; - return true; -} - -static bool write_index_snapshot(DP_BuildIndexEntryContext *e) -{ - bool ok = write_index_history(e) && write_index_layers(e) - && write_index_annotations(e) && write_index_background_tile(e) - && write_index_timeline(e) && write_index_metadata(e) - && write_index_canvas_state(e); - DP_free(e->annotation.buffer); - return ok; -} - -static bool write_index_thumbnail(DP_BuildIndexEntryContext *e) -{ - DP_Output *output = e->output; - bool error; - size_t thumbnail_offset = DP_output_tell(output, &error); - if (error) { - return false; - } - - DP_Image *img = DP_canvas_state_to_flat_image( - e->cs, DP_FLAT_IMAGE_RENDER_FLAGS, NULL, NULL); - if (!img) { - DP_warn("Error creating index thumbnail: %s", DP_error()); - return true; // Keep going without a thumbnail. - } - - DP_Image *thumb; - if (DP_image_thumbnail(img, e->dc, 256, 256, &thumb)) { - if (thumb) { - DP_image_free(img); - } - else { - thumb = img; - } - } - else { - DP_image_free(img); - DP_warn("Error scaling index thumbnail: %s", DP_error()); - return true; // Keep going without a thumbnail. - } - - if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT32(0))) { - return false; - } - - bool write_ok = DP_image_write_png(thumb, output); - DP_image_free(thumb); - if (!write_ok) { - return false; - } - - size_t end_offset = DP_output_tell(output, &error); - if (error) { - return false; - } - - if (!DP_output_seek(output, thumbnail_offset)) { - return false; - } - - size_t size = end_offset - thumbnail_offset - 4; - if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT32(size))) { - return false; - } - - if (!DP_output_seek(output, end_offset)) { - return false; - } - - e->offset.thumbnail = thumbnail_offset; - return true; -} - -static void dispose_index_maps(DP_BuildIndexMaps *maps) -{ - DP_BuildIndexTileMap *tile_entry, *tile_tmp; - HASH_ITER(hh, maps->tiles, tile_entry, tile_tmp) { - HASH_DEL(maps->tiles, tile_entry); - DP_tile_decref(tile_entry->t); - DP_free(tile_entry); - } - - DP_BuildIndexLayerMap *layer_entry, *layer_tmp; - HASH_ITER(hh, maps->layers, layer_entry, layer_tmp) { - HASH_DEL(maps->layers, layer_entry); - if (DP_layer_props_children_noinc(layer_entry->key.lp)) { - DP_layer_group_decref(layer_entry->key.lg); - } - else { - DP_layer_content_decref(layer_entry->key.lc); - } - DP_layer_props_decref(layer_entry->key.lp); - DP_free(layer_entry); - } - - DP_BuildIndexAnnotationMap *annotation_entry, *annotation_tmp; - HASH_ITER(hh, maps->annotations, annotation_entry, annotation_tmp) { - HASH_DEL(maps->annotations, annotation_entry); - DP_annotation_decref(annotation_entry->a); - DP_free(annotation_entry); - } - - DP_document_metadata_decref_nullable(maps->metadata.dm); - DP_timeline_decref_nullable(maps->timeline.tl); -} - -static bool make_index_entry(DP_BuildIndexContext *c, long long message_index, - size_t message_offset) -{ - DP_BuildIndexEntryContext e = {c->output, - c->acls, - c->local_state, - c->ch, - NULL, - c->dc, - {NULL, NULL, NULL, {NULL, 0}, {NULL, 0}}, - &c->last, - 0, - {0, 0, 0, 0, 0}, - {NULL, 0}}; - bool ok = write_index_snapshot(&e) && write_index_thumbnail(&e); - if (!ok) { - dispose_index_maps(&e.current); - return false; - } - - DP_PlayerIndexEntry entry = {message_index, message_offset, - e.offset.snapshot, e.offset.thumbnail}; - DP_VECTOR_PUSH_TYPE(&c->entries, DP_PlayerIndexEntry, entry); - - dispose_index_maps(&c->last); - c->last = e.current; - - return true; -} - -static bool write_index_messages(DP_BuildIndexContext *c) -{ - DP_Player *player = c->player; - DP_AclState *acls = c->acls; - DP_LocalState *ls = c->local_state; - DP_CanvasHistory *ch = c->ch; - DP_DrawContext *dc = c->dc; - int last_percent = 0; - long long last_written_message_index = 0; - - while (true) { - size_t message_offset = DP_player_tell(player); - DP_Message *msg; - DP_PlayerResult result = DP_player_step(player, &msg); - if (result == DP_PLAYER_SUCCESS) { - bool filtered = DP_acl_state_handle(acls, msg, false) - & DP_ACL_STATE_FILTERED_BIT; - if (filtered) { - DP_debug( - "ACL filtered recorded %s message from user %u", - DP_message_type_enum_name_unprefixed(DP_message_type(msg)), - DP_message_context_id(msg)); - } - else { - DP_local_state_handle(ls, dc, msg); - if (DP_message_type_command(DP_message_type(msg))) { - if (!DP_canvas_history_handle(ch, dc, msg)) { - DP_warn("Error handling message in index: %s", - DP_error()); - } - - long long message_index = c->message_count++; - if (c->should_snapshot_fn(c->user)) { - if (!make_index_entry(c, message_index, - message_offset)) { - return false; - } - last_written_message_index = message_index; - } - } - } - DP_message_decref(msg); - - if (c->progress_fn) { - double progress = DP_player_progress(player); - int percent = DP_double_to_int(progress * 100.0 + 0.5); - if (percent > last_percent) { - last_percent = percent; - c->progress_fn(c->user, percent); - } - } - } - else if (result == DP_PLAYER_ERROR_PARSE) { - DP_warn("Can't index message: %s", DP_error()); - } - else if (result == DP_PLAYER_RECORDING_END) { - break; - } - else { - return false; // Input error, bail out. - } - } - - long long message_index = c->message_count - 1; - if (message_index >= 0 && message_index != last_written_message_index) { - return make_index_entry(c, message_index, DP_player_tell(player)); - } - else { - return true; - } -} - -static bool write_index_entry(DP_BuildIndexContext *c, - DP_PlayerIndexEntry *entry) -{ - return DP_OUTPUT_WRITE_LITTLEENDIAN( - c->output, DP_OUTPUT_UINT32(entry->message_index), - DP_OUTPUT_UINT64(entry->message_offset), - DP_OUTPUT_UINT64(entry->snapshot_offset), - DP_OUTPUT_UINT64(entry->thumbnail_offset)); -} - -static bool write_index_finish(DP_BuildIndexContext *c) -{ - DP_Output *output = c->output; - bool error; - size_t entries_offset = DP_output_tell(output, &error); - if (error) { - return false; - } - - size_t count = c->entries.used; - for (size_t i = 0; i < count; ++i) { - if (!write_index_entry( - c, &DP_VECTOR_AT_TYPE(&c->entries, DP_PlayerIndexEntry, i))) { - return false; - } - } - - if (!DP_output_seek(output, INDEX_MAGIC_LENGTH + INDEX_VERSION_LENGTH)) { - return false; - } - - return DP_OUTPUT_WRITE_LITTLEENDIAN(output, - DP_OUTPUT_UINT32(c->message_count), - DP_OUTPUT_UINT64(entries_offset)); -} - -static bool write_index(DP_BuildIndexContext *c) -{ - return write_index_header(c) && write_index_messages(c) - && write_index_finish(c) && DP_output_flush(c->output); -} - -bool DP_player_index_build(DP_Player *player, DP_DrawContext *dc, - DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, - DP_PlayerIndexProgressFn progress_fn, void *user) -{ - DP_ASSERT(player); - DP_ASSERT(dc); - DP_ASSERT(should_snapshot_fn); - if (player->type == DP_PLAYER_TYPE_DEBUG_DUMP) { - DP_error_set("Can't index a debug dump"); - return false; - } - - const char *recording_path = player->recording_path; - if (!recording_path || !player->index_path) { - DP_error_set("Can't index player without a path"); - return false; - } - - DP_Input *input = DP_file_input_new_from_path(recording_path); - if (!input) { - return false; - } - - DP_Player *index_player = - DP_player_new(player->type, recording_path, input, NULL); - if (!index_player) { - return false; - } - else if (!DP_player_compatible(player)) { - DP_error_set("Incompatible recording"); - DP_player_free(player); - return false; - } - - const char *path = index_player->index_path; - DP_Output *output = DP_file_output_save_new_from_path(path); - if (!output) { - DP_player_free(index_player); - return false; - } - - DP_PERF_BEGIN_DETAIL(fn, "index_build", "path=%s", path); - DP_AclState *acls = DP_acl_state_new_playback(); - DP_LocalState *ls = DP_local_state_new(NULL, NULL, NULL); - DP_CanvasHistory *ch = DP_canvas_history_new(NULL, NULL, false, NULL); - DP_BuildIndexContext c = {index_player, - output, - acls, - ls, - ch, - dc, - 0, - DP_VECTOR_NULL, - {NULL, NULL, NULL, {NULL, 0}, {NULL, 0}}, - should_snapshot_fn, - progress_fn, - user}; - DP_VECTOR_INIT_TYPE(&c.entries, DP_PlayerIndexEntry, INITAL_ENTRY_CAPACITY); - bool ok = write_index(&c); - dispose_index_maps(&c.last); - DP_vector_dispose(&c.entries); - DP_canvas_history_free(ch); - DP_local_state_free(ls); - DP_acl_state_free(acls); - DP_output_free(output); - DP_player_free(index_player); - DP_PERF_END(fn); - return ok; -} - - -typedef struct DP_ReadIndexContext { - DP_BufferedInput input; - unsigned int message_count; - size_t index_offset; - DP_Vector entries; -} DP_ReadIndexContext; - -static bool read_index_input(DP_BufferedInput *input, size_t size) -{ - bool error; - size_t read = DP_buffered_input_read(input, size, &error); - if (error) { - return false; - } - else if (read != size) { - DP_error_set("Tried to read %zu byte(s), but got %zu", size, read); - return false; - } - else { - return true; - } -} - -#define READ_INDEX(INPUT, TYPE, OUT) \ - (read_index_input((INPUT), sizeof(TYPE##_t)) \ - && ((OUT) = DP_read_littleendian_##TYPE((INPUT)->buffer), true)) - -#define READ_INDEX_SIZE(INPUT, OUT) \ - (read_index_input((INPUT), sizeof(uint64_t)) \ - && ((OUT) = read_littleendian_size((INPUT)->buffer), true)) - -static size_t read_littleendian_size(const unsigned char *d) -{ - return DP_uint64_to_size(DP_read_littleendian_uint64(d)); -} - -static bool check_index_magic(DP_ReadIndexContext *c) -{ - DP_BufferedInput *input = &c->input; - if (read_index_input(input, INDEX_MAGIC_LENGTH)) { - if (memcmp(input->buffer, INDEX_MAGIC, INDEX_MAGIC_LENGTH) == 0) { - return true; - } - else { - DP_error_set("File does not start with magic '" INDEX_MAGIC "\\0'"); - } - } - return false; -} - -static bool check_index_version(DP_ReadIndexContext *c) -{ - int version; - if (READ_INDEX(&c->input, uint16, version)) { - if (version == INDEX_VERSION) { - return true; - } - else { - DP_error_set("Index version mismatch: expected %d, got %d", - INDEX_VERSION, version); - } - } - return false; -} - -static bool read_index_offset(DP_ReadIndexContext *c) -{ - if (READ_INDEX_SIZE(&c->input, c->index_offset)) { - if (c->index_offset >= INDEX_HEADER_LENGTH) { - return true; - } - else { - DP_error_set("Offset %zu is inside header (incomplete index?)", - c->index_offset); - } - } - return false; -} - -static bool read_index_header(DP_ReadIndexContext *c) -{ - DP_BufferedInput *input = &c->input; - return check_index_magic(c) && check_index_version(c) - && READ_INDEX(input, uint32, c->message_count) && read_index_offset(c); -} - -#define ENTRY_SIZE (sizeof(uint32_t) + sizeof(uint64_t) * (size_t)3) - -static bool read_index_entries(DP_ReadIndexContext *c) -{ - if (!DP_buffered_input_seek(&c->input, c->index_offset)) { - return false; - } - - DP_VECTOR_INIT_TYPE(&c->entries, DP_PlayerIndexEntry, - INITAL_ENTRY_CAPACITY); - - while (true) { - bool error; - size_t read = DP_buffered_input_read(&c->input, ENTRY_SIZE, &error); - if (error) { - return false; - } - else if (read == ENTRY_SIZE) { - DP_PlayerIndexEntry entry = { - DP_read_littleendian_uint32(c->input.buffer), - read_littleendian_size(c->input.buffer + 4), - read_littleendian_size(c->input.buffer + 12), - read_littleendian_size(c->input.buffer + 20), - }; - DP_debug("Read index entry %zu with message index %lld, message " - "offset %zu, snapshot offset %zu, thumbnail offset %zu", - c->entries.used, entry.message_index, entry.message_offset, - entry.snapshot_offset, entry.thumbnail_offset); - DP_VECTOR_PUSH_TYPE(&c->entries, DP_PlayerIndexEntry, entry); - } - else if (read == 0) { - DP_debug("Reached end of index entries"); - return true; - } - else { - DP_error_set("Expected index entry of %zu bytes, but got %zu", - ENTRY_SIZE, read); - return false; - } - } -} - -bool DP_player_index_load(DP_Player *player) -{ - DP_ASSERT(player); - if (player->type == DP_PLAYER_TYPE_DEBUG_DUMP) { - DP_error_set("Can't load index of a debug dump"); - return false; - } - - const char *path = player->index_path; - if (!path) { - DP_error_set("Can't load index of a player without a path"); - return false; - } - - DP_Input *input = DP_file_input_new_from_path(path); - if (!input) { - return false; - } - - DP_PERF_BEGIN_DETAIL(fn, "index_load", "path=%s", path); - DP_ReadIndexContext c = {DP_buffered_input_init(input), 0, 0, - DP_VECTOR_NULL}; - - bool ok = read_index_header(&c) && read_index_entries(&c); - if (ok) { - player_index_dispose(&player->index); - player->index = (DP_PlayerIndex){c.input, c.message_count, - c.entries.elements, c.entries.used}; - } - else { - DP_vector_dispose(&c.entries); - DP_buffered_input_dispose(&c.input); - } - - DP_PERF_END(fn); - return ok; -} - -static bool check_index(DP_Player *player) -{ - if (DP_player_index_loaded(player)) { - return true; - } - else { - DP_error_set("No index set"); - return false; - } -} - - -typedef struct DP_ReadTileMap { - size_t offset; - DP_Tile *t; - UT_hash_handle hh; -} DP_ReadTileMap; - -typedef struct DP_ReadSnapshotContext { - DP_BufferedInput *input; - DP_DrawContext *dc; - DP_TransientCanvasState *tcs; - DP_ReadTileMap *tiles; - DP_PlayerIndexEntrySnapshot *snapshot; -} DP_ReadSnapshotContext; - -struct DP_ReadSnapshotLayer { - union { - DP_TransientLayerContent *tlc; - DP_TransientLayerGroup *tlg; - }; - DP_TransientLayerProps *tlp; -}; - -static struct DP_ReadSnapshotLayer read_snapshot_layer_null(void) -{ - return (struct DP_ReadSnapshotLayer){{NULL}, NULL}; -} - -static bool read_snapshot_layer_ok(struct DP_ReadSnapshotLayer rsl) -{ - return rsl.tlp != NULL; -} - -static bool read_index_tile_inc(DP_ReadSnapshotContext *c, size_t offset, - DP_Tile **out_tile) -{ - if (offset == 0) { - *out_tile = NULL; - return true; - } - - DP_ReadTileMap *entry; - HASH_FIND(hh, c->tiles, &offset, sizeof(offset), entry); - if (!entry) { - DP_debug("Loading tile from offset %zu", offset); - DP_BufferedInput *input = c->input; - size_t size; - bool ok = DP_buffered_input_seek(input, offset) - && READ_INDEX(input, uint16, size) - && read_index_input(input, size); - if (!ok) { - return false; - } - - DP_Tile *t = DP_tile_new_from_compressed(c->dc, 0, input->buffer, size); - if (!t) { - return false; - } - - entry = DP_malloc(sizeof(*entry)); - entry->offset = offset; - entry->t = t; - HASH_ADD(hh, c->tiles, offset, sizeof(offset), entry); - } - - *out_tile = DP_tile_incref(entry->t); - return true; -} - -static bool read_index_offsets(DP_BufferedInput *input, int count, - size_t **out_offsets) -{ - if (count == 0) { - DP_debug("Read nothing for 0 offsets"); - *out_offsets = NULL; - return true; - } - - size_t scount = DP_int_to_size(count); - size_t size = sizeof(uint64_t) * scount; - DP_debug("Read %zu bytes for %d offset(s)", size, count); - if (!read_index_input(input, size)) { - return false; - } - - size_t *offsets = scount == 0 ? NULL : DP_malloc(sizeof(*offsets) * scount); - for (size_t i = 0, j = 0; i < scount; i += 1, j += sizeof(uint64_t)) { - offsets[i] = read_littleendian_size(input->buffer + j); - } - *out_offsets = offsets; - return true; -} - -static bool read_index_canvas_state(DP_ReadSnapshotContext *c, - unsigned int width, unsigned int height) -{ - if (width > UINT16_MAX || height > UINT16_MAX) { - DP_error_set("Canvas dimensions %ux%u out of bounds", width, height); - return false; - } - c->tcs = DP_transient_canvas_state_new_init(); - DP_transient_canvas_state_width_set(c->tcs, DP_uint_to_int(width)); - DP_transient_canvas_state_height_set(c->tcs, DP_uint_to_int(height)); - return true; -} - -static DP_Annotation *read_index_annotation(DP_BufferedInput *input, - size_t offset) -{ - DP_debug("Read annotation at offset %zu", offset); - int id, x, y, width, height, valign; - uint32_t background_color; - size_t text_length; - bool ok = DP_buffered_input_seek(input, offset) - && READ_INDEX(input, uint16, id) && READ_INDEX(input, int32, x) - && READ_INDEX(input, int32, y) && READ_INDEX(input, int32, width) - && READ_INDEX(input, int32, height) - && READ_INDEX(input, uint32, background_color) - && READ_INDEX(input, uint8, valign) - && READ_INDEX(input, uint16, text_length) - && read_index_input(input, text_length); - if (ok) { - DP_TransientAnnotation *ta = - DP_transient_annotation_new_init(id, x, y, width, height); - DP_transient_annotation_background_color_set(ta, background_color); - DP_transient_annotation_valign_set(ta, valign); - DP_transient_annotation_text_set(ta, (const char *)input->buffer, - text_length); - return DP_transient_annotation_persist(ta); - } - else { - return NULL; - } -} - -static bool read_index_annotations(DP_ReadSnapshotContext *c, int count) -{ - DP_debug("Read %d annotation(s)", count); - DP_BufferedInput *input = c->input; - size_t *offsets; - if (!read_index_offsets(input, count, &offsets)) { - return false; - } - - bool ok = true; - DP_TransientAnnotationList *tal = - DP_transient_canvas_state_transient_annotations(c->tcs, count); - for (int i = 0; i < count; ++i) { - DP_Annotation *a = read_index_annotation(input, offsets[i]); - if (a) { - DP_transient_annotation_list_insert_noinc(tal, a, i); - } - else { - ok = false; - break; - } - } - - DP_free(offsets); - return ok; -} - -static bool read_index_background_tile(DP_ReadSnapshotContext *c, size_t offset) -{ - DP_debug("Read background tile at offset %zu", offset); - DP_Tile *t; - if (read_index_tile_inc(c, offset, &t)) { - DP_transient_canvas_state_background_tile_set_noinc(c->tcs, t, - DP_tile_opaque(t)); - return true; - } - else { - return false; - } -} - -static DP_TransientKeyFrame *read_index_key_frame(DP_ReadSnapshotContext *c, - int *out_frame_index) -{ - DP_BufferedInput *input = c->input; - int layers_count, layer_id; - size_t title_length; - bool ok = READ_INDEX(input, uint16, layers_count) - && READ_INDEX(input, uint16, *out_frame_index) - && READ_INDEX(input, uint16, layer_id) - && READ_INDEX(input, uint16, title_length) - && read_index_input(input, title_length); - if (!ok) { - return false; - } - - DP_TransientKeyFrame *tkf = - DP_transient_key_frame_new_init(layer_id, layers_count); - DP_transient_key_frame_title_set(tkf, (const char *)input->buffer, - title_length); - for (int i = 0; i < layers_count; ++i) { - DP_KeyFrameLayer kfl; - ok = READ_INDEX(input, uint16, kfl.layer_id) - && READ_INDEX(input, uint16, kfl.flags); - if (!ok) { - DP_transient_key_frame_decref(tkf); - return false; - } - DP_transient_key_frame_layer_set(tkf, kfl, i); - } - - return tkf; -} - -static DP_TransientTrack *read_index_track(DP_ReadSnapshotContext *c) -{ - DP_BufferedInput *input = c->input; - int key_frame_count, track_id; - size_t title_length; - bool ok = READ_INDEX(input, uint16, key_frame_count) - && READ_INDEX(input, uint16, track_id) - && READ_INDEX(input, uint16, title_length) - && read_index_input(input, title_length); - if (!ok) { - return false; - } - - DP_TransientTrack *tt = DP_transient_track_new_init(key_frame_count); - DP_transient_track_id_set(tt, track_id); - DP_transient_track_title_set(tt, (const char *)input->buffer, title_length); - for (int i = 0; i < key_frame_count; ++i) { - int frame_index; - DP_TransientKeyFrame *tkf = read_index_key_frame(c, &frame_index); - if (!tkf) { - DP_transient_track_decref(tt); - return false; - } - DP_transient_track_set_transient_noinc(tt, frame_index, tkf, i); - } - - return tt; -} - -static bool read_index_timeline(DP_ReadSnapshotContext *c, size_t offset) -{ - DP_debug("Read timeline at offset %zu", offset); - DP_BufferedInput *input = c->input; - int track_count; - bool ok = DP_buffered_input_seek(input, offset) - && READ_INDEX(input, uint16, track_count); - if (!ok) { - return false; - } - - DP_TransientTimeline *ttl = - DP_transient_canvas_state_transient_timeline(c->tcs, track_count); - DP_debug("Read %d timeline track(s)", track_count); - for (int i = 0; i < track_count; ++i) { - DP_TransientTrack *tt = read_index_track(c); - if (!tt) { - return false; - } - DP_transient_timeline_set_transient_noinc(ttl, tt, i); - } - return true; -} - -static bool read_index_metadata(DP_ReadSnapshotContext *c, size_t offset) -{ - DP_debug("Read document metadata at offset %zu", offset); - DP_BufferedInput *input = c->input; - int dpix, dpiy, framerate, frame_count; - bool ok = DP_buffered_input_seek(input, offset) - && READ_INDEX(input, int32, dpix) && READ_INDEX(input, int32, dpiy) - && READ_INDEX(input, int32, framerate) - && READ_INDEX(input, int32, frame_count); - if (ok) { - DP_TransientDocumentMetadata *tdm = - DP_transient_canvas_state_transient_metadata(c->tcs); - DP_transient_document_metadata_dpix_set(tdm, dpix); - DP_transient_document_metadata_dpiy_set(tdm, dpiy); - DP_transient_document_metadata_framerate_set(tdm, framerate); - DP_transient_document_metadata_frame_count_set(tdm, frame_count); - return true; - } - else { - return false; - } -} - -static bool read_index_layer_title(DP_BufferedInput *input, size_t title_length, - char **out_title) -{ - if (read_index_input(input, title_length)) { - char *title = DP_malloc(title_length); - memcpy(title, input->buffer, title_length); - *out_title = title; - return true; - } - else { - return false; - } -} - -static DP_TransientLayerProps * -to_transient_layer_props(struct DP_BuildIndexLayerProps *bilp, - DP_TransientLayerPropsList *child_tlpl) -{ - DP_TransientLayerProps *tlp = - DP_transient_layer_props_new_init_with_transient_children_noinc( - bilp->layer_id, child_tlpl); - DP_transient_layer_props_title_set(tlp, bilp->title, bilp->title_length); - DP_transient_layer_props_opacity_set(tlp, DP_channel8_to_15(bilp->opacity)); - if (DP_blend_mode_valid_for_layer(bilp->blend_mode)) { - DP_transient_layer_props_blend_mode_set(tlp, bilp->blend_mode); - } - DP_transient_layer_props_hidden_set(tlp, bilp->hidden != 0); - DP_transient_layer_props_censored_set(tlp, bilp->censored != 0); - DP_transient_layer_props_isolated_set(tlp, bilp->isolated != 0); - return tlp; -} - -#define READ_INDEX_LAYER_ALLOW_CONTENT (1 << 0) -#define READ_INDEX_LAYER_ALLOW_GROUP (1 << 1) -#define READ_INDEX_LAYER_ALLOW_ANY \ - (READ_INDEX_LAYER_ALLOW_CONTENT | READ_INDEX_LAYER_ALLOW_GROUP) -#define READ_INDEX_LAYER_SUBLAYER (1 << 2) - -static struct DP_ReadSnapshotLayer -read_index_layer(DP_ReadSnapshotContext *c, size_t offset, unsigned int flags); - -static struct DP_ReadSnapshotLayer -read_index_layer_content(DP_ReadSnapshotContext *c, - struct DP_BuildIndexLayerProps *bilp, - int sublayer_count, size_t *sublayer_offsets) -{ - DP_BufferedInput *input = c->input; - int width = DP_transient_canvas_state_width(c->tcs); - int height = DP_transient_canvas_state_height(c->tcs); - int tile_total = DP_tile_total_round(width, height); - - size_t *tile_offsets; - if (!read_index_offsets(input, tile_total, &tile_offsets)) { - return read_snapshot_layer_null(); - } - - DP_TransientLayerList *sub_tll = - DP_transient_layer_list_new_init(sublayer_count); - DP_TransientLayerPropsList *sub_tlpl = - DP_transient_layer_props_list_new_init(sublayer_count); - for (int i = 0; i < sublayer_count; ++i) { - struct DP_ReadSnapshotLayer rsl = read_index_layer( - c, sublayer_offsets[i], - READ_INDEX_LAYER_ALLOW_CONTENT | READ_INDEX_LAYER_SUBLAYER); - if (read_snapshot_layer_ok(rsl)) { - // Layers are stored inverted, so prepend the new ones at index 0. - DP_transient_layer_list_insert_transient_content_noinc(sub_tll, - rsl.tlc, 0); - DP_transient_layer_props_list_insert_transient_noinc(sub_tlpl, - rsl.tlp, 0); - } - else { - DP_transient_layer_props_list_decref(sub_tlpl); - DP_transient_layer_list_decref(sub_tll); - DP_free(tile_offsets); - return read_snapshot_layer_null(); - } - } - - DP_TransientLayerContent *tlc = - DP_transient_layer_content_new_init_with_transient_sublayers_noinc( - width, height, NULL, sub_tll, sub_tlpl); - - for (int i = 0; i < tile_total; ++i) { - DP_Tile *t; - if (read_index_tile_inc(c, tile_offsets[i], &t)) { - DP_transient_layer_content_tile_set_noinc(tlc, t, i); - } - else { - DP_transient_layer_content_decref(tlc); - DP_free(tile_offsets); - return read_snapshot_layer_null(); - } - } - DP_free(tile_offsets); - - DP_TransientLayerProps *tlp = to_transient_layer_props(bilp, NULL); - return (struct DP_ReadSnapshotLayer){{.tlc = tlc}, tlp}; -} - -static struct DP_ReadSnapshotLayer -read_index_layer_group(DP_ReadSnapshotContext *c, - struct DP_BuildIndexLayerProps *bilp, int child_count, - size_t *child_offsets) -{ - DP_TransientLayerList *child_tll = - DP_transient_layer_list_new_init(child_count); - DP_TransientLayerPropsList *child_tlpl = - DP_transient_layer_props_list_new_init(child_count); - for (int i = 0; i < child_count; ++i) { - struct DP_ReadSnapshotLayer rsl = - read_index_layer(c, child_offsets[i], READ_INDEX_LAYER_ALLOW_ANY); - if (read_snapshot_layer_ok(rsl)) { - // Layers are stored inverted, so prepend the new ones at index 0. - if (DP_transient_layer_props_children_noinc(rsl.tlp)) { - DP_transient_layer_list_insert_transient_group_noinc( - child_tll, rsl.tlg, 0); - } - else { - DP_transient_layer_list_insert_transient_content_noinc( - child_tll, rsl.tlc, 0); - } - DP_transient_layer_props_list_insert_transient_noinc(child_tlpl, - rsl.tlp, 0); - } - else { - DP_transient_layer_props_list_decref(child_tlpl); - DP_transient_layer_list_decref(child_tll); - return read_snapshot_layer_null(); - } - } - - DP_TransientLayerGroup *tlg = - DP_transient_layer_group_new_init_with_transient_children_noinc( - DP_transient_canvas_state_width(c->tcs), - DP_transient_canvas_state_height(c->tcs), child_tll); - DP_TransientLayerProps *tlp = to_transient_layer_props(bilp, child_tlpl); - return (struct DP_ReadSnapshotLayer){{.tlg = tlg}, tlp}; -} - -static struct DP_ReadSnapshotLayer -read_index_layer(DP_ReadSnapshotContext *c, size_t offset, unsigned int flags) -{ - DP_debug("Read layer at offset %zu", offset); - DP_BufferedInput *input = c->input; - struct DP_BuildIndexLayerProps bilp = {0}; - int child_count; - size_t *child_offsets = NULL; - bool ok = DP_buffered_input_seek(input, offset) - && READ_INDEX(input, uint16, bilp.layer_id) - && READ_INDEX(input, uint16, bilp.title_length) - && read_index_layer_title(input, bilp.title_length, &bilp.buffer) - && READ_INDEX(input, uint8, bilp.opacity) - && READ_INDEX(input, uint8, bilp.blend_mode) - && READ_INDEX(input, uint8, bilp.hidden) - && READ_INDEX(input, uint8, bilp.censored) - && READ_INDEX(input, uint8, bilp.isolated) - && READ_INDEX(input, uint8, bilp.group) - && READ_INDEX(input, uint16, child_count) - && read_index_offsets(input, child_count, &child_offsets); - - struct DP_ReadSnapshotLayer rsl = read_snapshot_layer_null(); - if (ok) { - if (bilp.group) { - if (flags & READ_INDEX_LAYER_ALLOW_GROUP) { - rsl = read_index_layer_group(c, &bilp, child_count, - child_offsets); - } - else { - DP_error_set("Expected layer content, but got layer group"); - } - } - else if (flags & READ_INDEX_LAYER_ALLOW_CONTENT) { - bool sublayer = flags & READ_INDEX_LAYER_SUBLAYER; - rsl = read_index_layer_content(c, &bilp, sublayer ? 0 : child_count, - sublayer ? NULL : child_offsets); - } - else { - DP_error_set("Expected layer group, but got layer content"); - } - } - - DP_free(child_offsets); - DP_free(bilp.buffer); - return rsl; -} - -static bool read_index_layers(DP_ReadSnapshotContext *c, size_t offset) -{ - DP_debug("Read layers at offset %zu", offset); - struct DP_ReadSnapshotLayer rsl = - read_index_layer(c, offset, READ_INDEX_LAYER_ALLOW_GROUP); - if (read_snapshot_layer_ok(rsl)) { - DP_transient_canvas_state_layers_set_inc( - c->tcs, DP_transient_layer_group_children_noinc(rsl.tlg)); - DP_transient_canvas_state_layer_props_set_inc( - c->tcs, DP_transient_layer_props_children_noinc(rsl.tlp)); - DP_transient_layer_group_decref(rsl.tlg); - DP_transient_layer_props_decref(rsl.tlp); - return true; - } - else { - return false; - } -} - -static bool read_index_history(DP_ReadSnapshotContext *c, size_t offset, - int message_count) -{ - DP_BufferedInput *input = c->input; - if (!DP_buffered_input_seek(input, offset)) { - return false; - } - - DP_PlayerIndexEntrySnapshot *snapshot = DP_malloc(DP_FLEX_SIZEOF( - DP_PlayerIndexEntrySnapshot, messages, DP_int_to_size(message_count))); - snapshot->cs = NULL; - snapshot->message_count = message_count; - for (int i = 0; i < message_count; ++i) { - snapshot->messages[i] = NULL; - } - c->snapshot = snapshot; - - for (int i = 0; i < message_count; ++i) { - size_t length; - bool ok = READ_INDEX(input, uint16, length) - && read_index_input(input, length); - if (!ok) { - return false; - } - - DP_Message *msg = DP_message_deserialize_length( - input->buffer, length, length < 2 ? 0 : length - 2, true); - if (msg) { - snapshot->messages[i] = msg; - } - else { - DP_warn("Error deserializing history message %d", i); - } - } - - return true; -} - -static bool read_index_snapshot(DP_ReadSnapshotContext *c) -{ - DP_BufferedInput *input = c->input; - uint32_t width, height; - size_t history_offset, background_tile_offset, timeline_offset, - metadata_offset, layers_offset; - int message_count, annotation_count; - return READ_INDEX(input, uint32, width) && READ_INDEX(input, uint32, height) - && READ_INDEX(input, uint16, message_count) - && READ_INDEX_SIZE(input, history_offset) - && READ_INDEX_SIZE(input, background_tile_offset) - && READ_INDEX_SIZE(input, timeline_offset) - && READ_INDEX_SIZE(input, metadata_offset) - && READ_INDEX_SIZE(input, layers_offset) - && READ_INDEX(input, uint16, annotation_count) - && read_index_canvas_state(c, width, height) - && read_index_annotations(c, annotation_count) - && read_index_background_tile(c, background_tile_offset) - && read_index_timeline(c, timeline_offset) - && read_index_metadata(c, metadata_offset) - && read_index_layers(c, layers_offset) - && read_index_history(c, history_offset, message_count); -} - -DP_PlayerIndexEntrySnapshot * -DP_player_index_entry_load(DP_Player *player, DP_DrawContext *dc, - DP_PlayerIndexEntry entry) -{ - DP_ASSERT(player); - size_t snapshot_offset = entry.snapshot_offset; - DP_debug("Load snapshot from offset %zu", snapshot_offset); - if (snapshot_offset == 0) { - DP_PlayerIndexEntrySnapshot *snapshot = DP_malloc(sizeof(*snapshot)); - snapshot->cs = DP_canvas_state_new(); - snapshot->message_count = 0; - return snapshot; - } - - DP_BufferedInput *input = &player->index.input; - if (!DP_buffered_input_seek(input, snapshot_offset)) { - return NULL; - } - - DP_PERF_BEGIN_DETAIL(fn, "index_entry_load", "offset=%zu", snapshot_offset); - DP_ReadSnapshotContext c = {input, dc, NULL, NULL, NULL}; - bool ok = read_index_snapshot(&c); - - DP_ReadTileMap *tile_entry, *tile_tmp; - HASH_ITER(hh, c.tiles, tile_entry, tile_tmp) { - HASH_DEL(c.tiles, tile_entry); - DP_tile_decref(tile_entry->t); - DP_free(tile_entry); - } - - if (ok) { - DP_transient_canvas_state_layer_routes_reindex(c.tcs, dc); - DP_transient_canvas_state_timeline_cleanup(c.tcs); - c.snapshot->cs = DP_transient_canvas_state_persist(c.tcs); - } - else { - DP_transient_canvas_state_decref_nullable(c.tcs); - DP_player_index_entry_snapshot_free(c.snapshot); - c.snapshot = NULL; - } - - DP_PERF_END(fn); - return c.snapshot; -} - -DP_CanvasState *DP_player_index_entry_snapshot_canvas_state_inc( - DP_PlayerIndexEntrySnapshot *snapshot) -{ - DP_ASSERT(snapshot); - return DP_canvas_state_incref(snapshot->cs); -} - -int DP_player_index_entry_snapshot_message_count( - DP_PlayerIndexEntrySnapshot *snapshot) -{ - DP_ASSERT(snapshot); - return snapshot->message_count; -} - -DP_Message *DP_player_index_entry_snapshot_message_at_inc( - DP_PlayerIndexEntrySnapshot *snapshot, int i) -{ - DP_ASSERT(snapshot); - DP_ASSERT(i >= 0); - DP_ASSERT(i < snapshot->message_count); - return DP_message_incref_nullable(snapshot->messages[i]); -} - -void DP_player_index_entry_snapshot_free(DP_PlayerIndexEntrySnapshot *snapshot) -{ - if (snapshot) { - int message_count = snapshot->message_count; - for (int i = 0; i < message_count; ++i) { - DP_message_decref_nullable(snapshot->messages[i]); - } - DP_canvas_state_decref_nullable(snapshot->cs); - DP_free(snapshot); - } -} - - -unsigned int DP_player_index_message_count(DP_Player *player) -{ - DP_ASSERT(player); - return check_index(player) ? player->index.message_count : 0; -} - -size_t DP_player_index_entry_count(DP_Player *player) -{ - DP_ASSERT(player); - return check_index(player) ? player->index.entry_count : 0; -} - -DP_PlayerIndexEntry DP_player_index_entry_search(DP_Player *player, - long long position, bool after) -{ - DP_ASSERT(player); - if (!check_index(player)) { - return (DP_PlayerIndexEntry){0, 0, 0, 0}; - } - - size_t entry_count = player->index.entry_count; - DP_PlayerIndexEntry best_entry = (DP_PlayerIndexEntry){0, 0, 0, 0}; - long long best_message_index = -1; - for (size_t i = 0; i < entry_count; ++i) { - DP_PlayerIndexEntry entry = player->index.entries[i]; - long long message_index = entry.message_index; - bool is_best_candidate = after - ? (message_index >= position - && (message_index < best_message_index - || best_message_index == -1)) - : (message_index <= position - && message_index > best_message_index); - if (is_best_candidate) { - best_entry = entry; - best_message_index = message_index; - } - } - // If we didn't find any index, rewind back to the beginning of the - // recording. Which isn't at offset 0, since there's a header to skip. - if (best_message_index == -1) { - best_entry.message_offset = player_body_offset(player); - } - return best_entry; -} - -static void assign_out_error(bool error, bool *out_error) -{ - if (out_error) { - *out_error = error; - } -} - -DP_Image *DP_player_index_thumbnail_at(DP_Player *player, size_t index, - bool *out_error) -{ - DP_ASSERT(player); - if (!check_index(player)) { - assign_out_error(true, out_error); - return NULL; - } - - size_t entry_count = player->index.entry_count; - if (index >= entry_count) { - DP_error_set("Entry index %zu beyond count %zu", index, entry_count); - assign_out_error(true, out_error); - return NULL; - } - - size_t thumbnail_offset = player->index.entries[index].thumbnail_offset; - if (thumbnail_offset == 0) { - assign_out_error(false, out_error); - return NULL; - } - - DP_BufferedInput *input = &player->index.input; - if (!DP_buffered_input_seek(input, thumbnail_offset)) { - assign_out_error(true, out_error); - return NULL; - } - - bool error; - size_t read = DP_buffered_input_read(input, sizeof(uint32_t), &error); - if (error) { - assign_out_error(true, out_error); - return NULL; - } - else if (read != sizeof(uint32_t)) { - DP_error_set("Premature end of thumbnail length"); - assign_out_error(true, out_error); - return NULL; - } - - size_t length = DP_read_littleendian_uint32(input->buffer); - if (length == 0) { - DP_error_set("Thumbnail has zero length"); - assign_out_error(true, out_error); - return NULL; - } - - read = DP_buffered_input_read(input, length, &error); - if (error) { - assign_out_error(true, out_error); - return NULL; - } - else if (read != length) { - DP_error_set("Premature end of thumbnail data"); - assign_out_error(true, out_error); - return NULL; - } - - DP_Input *mem_input = DP_mem_input_new(input->buffer, length, NULL, NULL); - DP_Image *img = DP_image_read_png(mem_input); - DP_input_free(mem_input); - - assign_out_error(img == NULL, out_error); - return img; -} diff --git a/src/drawdance/libengine/dpengine/player.h b/src/drawdance/libengine/dpengine/player.h index 7e8c0941b8..c5d162ff59 100644 --- a/src/drawdance/libengine/dpengine/player.h +++ b/src/drawdance/libengine/dpengine/player.h @@ -22,13 +22,13 @@ #ifndef DPMSG_PLAYER_H #define DPMSG_PLAYER_H #include "canvas_history.h" -#include "load.h" +#include "load_enums.h" #include +#include typedef struct DP_CanvasState DP_CanvasState; typedef struct DP_DrawContext DP_DrawContext; typedef struct DP_Image DP_Image; -typedef struct DP_Input DP_Input; typedef struct DP_Message DP_Message; typedef struct json_object_t JSON_Object; @@ -64,10 +64,12 @@ typedef struct DP_PlayerIndexEntry { size_t thumbnail_offset; } DP_PlayerIndexEntry; -typedef struct DP_PlayerIndexEntrySnapshot DP_PlayerIndexEntrySnapshot; - -typedef bool (*DP_PlayerIndexShouldSnapshotFn)(void *user); -typedef void (*DP_PlayerIndexProgressFn)(void *user, int percent); +typedef struct DP_PlayerIndex { + DP_BufferedInput input; + unsigned int message_count; + DP_PlayerIndexEntry *entries; + size_t entry_count; +} DP_PlayerIndex; DP_Player *DP_player_new(DP_PlayerType type, const char *path_or_null, @@ -85,8 +87,16 @@ bool DP_player_compatible(DP_Player *player); void DP_player_acl_override_set(DP_Player *player, bool override); +const char *DP_player_recording_path(DP_Player *player); + +const char *DP_player_index_path(DP_Player *player); + bool DP_player_index_loaded(DP_Player *player); +DP_PlayerIndex *DP_player_index(DP_Player *player); + +void DP_player_index_set(DP_Player *player, DP_PlayerIndex index); + size_t DP_player_tell(DP_Player *player); double DP_player_progress(DP_Player *player); @@ -101,42 +111,11 @@ DP_PlayerResult DP_player_step_dump(DP_Player *player, DP_DumpType *out_type, bool DP_player_seek(DP_Player *player, long long position, size_t offset); +size_t DP_player_body_offset(DP_Player *player); + bool DP_player_rewind(DP_Player *player); bool DP_player_seek_dump(DP_Player *player, long long position); -bool DP_player_index_build(DP_Player *player, DP_DrawContext *dc, - DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, - DP_PlayerIndexProgressFn progress_fn, void *user); - - -bool DP_player_index_load(DP_Player *player); - -unsigned int DP_player_index_message_count(DP_Player *player); - -size_t DP_player_index_entry_count(DP_Player *player); - -DP_PlayerIndexEntry -DP_player_index_entry_search(DP_Player *player, long long position, bool after); - -DP_PlayerIndexEntrySnapshot * -DP_player_index_entry_load(DP_Player *player, DP_DrawContext *dc, - DP_PlayerIndexEntry entry); - -DP_CanvasState *DP_player_index_entry_snapshot_canvas_state_inc( - DP_PlayerIndexEntrySnapshot *snapshot); - -int DP_player_index_entry_snapshot_message_count( - DP_PlayerIndexEntrySnapshot *snapshot); - -DP_Message *DP_player_index_entry_snapshot_message_at_inc( - DP_PlayerIndexEntrySnapshot *snapshot, int i); - -void DP_player_index_entry_snapshot_free(DP_PlayerIndexEntrySnapshot *snapshot); - -DP_Image *DP_player_index_thumbnail_at(DP_Player *player, size_t index, - bool *out_error); - - #endif diff --git a/src/drawdance/libengine/dpengine/save_enums.h b/src/drawdance/libengine/dpengine/save_enums.h new file mode 100644 index 0000000000..f2462a61e2 --- /dev/null +++ b/src/drawdance/libengine/dpengine/save_enums.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPENGINE_SAVE_ENUMS_H +#define DPENGINE_SAVE_ENUMS_H + +typedef enum DP_SaveImageType { + DP_SAVE_IMAGE_UNKNOWN, + DP_SAVE_IMAGE_ORA, + DP_SAVE_IMAGE_PNG, + DP_SAVE_IMAGE_JPEG, + DP_SAVE_IMAGE_PSD, + DP_SAVE_IMAGE_WEBP, +} DP_SaveImageType; + +typedef enum DP_SaveResult { + DP_SAVE_RESULT_SUCCESS, + DP_SAVE_RESULT_BAD_ARGUMENTS, + DP_SAVE_RESULT_UNKNOWN_FORMAT, + DP_SAVE_RESULT_FLATTEN_ERROR, + DP_SAVE_RESULT_OPEN_ERROR, + DP_SAVE_RESULT_WRITE_ERROR, + DP_SAVE_RESULT_INTERNAL_ERROR, + DP_SAVE_RESULT_CANCEL, +} DP_SaveResult; + +#endif diff --git a/src/drawdance/libengine/test/dptest/dptest_engine.h b/src/drawdance/libengine/test/dptest/dptest_engine.h deleted file mode 100644 index 3784d6b800..0000000000 --- a/src/drawdance/libengine/test/dptest/dptest_engine.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef DPTEST_DPTEST_ENGINE_H -#define DPTEST_DPTEST_ENGINE_H -#include - -typedef struct DP_Image DP_Image; - - -bool DP_test_image_eq_ok(DP_TestContext *T, const char *file, int line, - const char *sa, const char *sb, DP_Image *a, - DP_Image *b, const char *fmt, ...) DP_FORMAT(8, 9); - -bool DP_test_image_file_eq_ok(DP_TestContext *T, const char *file, int line, - const char *sa, const char *sb, const char *a, - const char *b, const char *fmt, ...) - DP_FORMAT(8, 9); - - -#define IMAGE_EQ_OK(A, B, ...) \ - DP_test_image_eq_ok(TEST_ARGS, __FILE__, __LINE__, #A, #B, (A), (B), \ - __VA_ARGS__) - -#define IMAGE_FILE_EQ_OK(A, B, ...) \ - DP_test_image_file_eq_ok(TEST_ARGS, __FILE__, __LINE__, #A, #B, (A), (B), \ - __VA_ARGS__) - - -#endif diff --git a/src/drawdance/libengine/test/pixel_conversion.c b/src/drawdance/libengine/test/pixel_conversion.c index f88f600030..5fbb5963f8 100644 --- a/src/drawdance/libengine/test/pixel_conversion.c +++ b/src/drawdance/libengine/test/pixel_conversion.c @@ -3,7 +3,7 @@ #include #include #include -#include +#include // "Oracle" versions of conversion functions that go via floating point. The diff --git a/src/drawdance/libengine/test/resize_image.h b/src/drawdance/libengine/test/resize_image.h deleted file mode 100644 index f9e0c89474..0000000000 --- a/src/drawdance/libengine/test/resize_image.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#define WIDTH 64 -#define HEIGHT 32 - -typedef struct ResizeTestCase { - const char *name; - int x, y, dw, dh; -} ResizeTestCase; - -ResizeTestCase resize_test_cases[] = { - {"same", 0, 0, 0, 0}, - {"negative_x", -20, 0, 0, 0}, - {"positive_x", 20, 0, 0, 0}, - {"negative_y", 0, -10, 0, 0}, - {"positive_y", 0, 10, 0, 0}, - {"smaller_width", 0, 0, -16, 0}, - {"larger_width", 0, 0, 16, 0}, - {"smaller_height", 0, 0, 0, -8}, - {"larger_height", 0, 10, 0, 8}, - {"everything", -20, 10, 50, -12}, - {"out_of_bounds", -WIDTH, -HEIGHT, 0, 0}, -}; diff --git a/src/drawdance/libimpex/CMakeLists.txt b/src/drawdance/libimpex/CMakeLists.txt new file mode 100644 index 0000000000..1014bcb668 --- /dev/null +++ b/src/drawdance/libimpex/CMakeLists.txt @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: MIT + +dp_add_library(dpimpex) +dp_target_sources(dpimpex + dpimpex/image_impex.c + dpimpex/image_impex.h + dpimpex/image_jpeg.h + dpimpex/image_png.h + dpimpex/image_webp.c + dpimpex/image_webp.h + dpimpex/load.c + dpimpex/load.h + dpimpex/load_animation.c + dpimpex/load_animation.h + dpimpex/load_psd.cpp + dpimpex/paint_engine_playback.c + dpimpex/paint_engine_playback.h + dpimpex/player_index.c + dpimpex/player_index.h + dpimpex/save.c + dpimpex/save.h + dpimpex/save_psd.c + dpimpex/utf16be.h + dpimpex/utf16be_qt.cpp + dpimpex/xml_stream.h + dpimpex/xml_stream_qt.cpp + dpimpex/zip_archive.h +) + +target_include_directories(dpimpex PUBLIC ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(dpimpex PUBLIC + dpengine dpmsg dpcommon dpwebp::dpwebp psd_sdk jo_gifx + "Qt${QT_VERSION_MAJOR}::Core" "Qt${QT_VERSION_MAJOR}::Xml" +) + +if(ZIP_IMPL STREQUAL "LIBZIP") + target_sources(dpimpex PRIVATE dpimpex/zip_archive_libzip.c) + target_link_libraries(dpimpex PUBLIC libzip::zip) +elseif(ZIP_IMPL STREQUAL "KARCHIVE") + target_sources(dpimpex PRIVATE dpimpex/zip_archive_karchive.cpp) + target_link_libraries(dpimpex PUBLIC KF${QT_VERSION_MAJOR}::Archive) +else() + message(SEND_ERROR "Unknown ZIP_IMPL value '${ZIP_IMPL}'") +endif() + +if(IMAGE_IMPL STREQUAL "LIBS") + target_sources(dpimpex PRIVATE + dpimpex/image_jpeg.c + dpimpex/image_png.c + ) + target_link_libraries(dpimpex PUBLIC + PNG::PNG + JPEG::JPEG + ) +elseif(IMAGE_IMPL STREQUAL "QT") + target_sources(dpimpex PRIVATE dpimpex/image_qt.cpp) + target_link_libraries(dpimpex PUBLIC "Qt${QT_VERSION_MAJOR}::Gui") +else() + message(SEND_ERROR "Unknown IMAGE_IMPL value '${IMAGE_IMPL}'") +endif() + +if(TARGET LIBAV::LIBAV) + target_sources(dpimpex PRIVATE + dpimpex/save_video.c + dpimpex/save_video.h + ) + target_link_libraries(dpimpex PUBLIC LIBAV::LIBAV) + target_compile_definitions(dpimpex PUBLIC DP_LIBAV=1) +endif() + +if(TESTS) + add_library(dptest_impex) + target_sources(dptest_impex PRIVATE + test/dptest/dptest_impex.c + test/dptest/dptest_impex.h + ) + target_include_directories(dptest_impex PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/test/dptest" + ) + target_link_libraries(dptest_impex PUBLIC dptest dpimpex) + add_dptest_targets(impex dptest_impex + test/image_thumbnail.c + test/resize_image.c + ) +endif() diff --git a/src/drawdance/libimpex/dpimpex/image_impex.c b/src/drawdance/libimpex/dpimpex/image_impex.c new file mode 100644 index 0000000000..6b5f2d0557 --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/image_impex.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include "image_impex.h" +#include "image_jpeg.h" +#include "image_png.h" +#include "image_webp.h" +#include +#include +#include +#include + + +static void assign_type(DP_ImageFileType *out_type, DP_ImageFileType type) +{ + if (out_type) { + *out_type = type; + } +} + +static DP_Image *read_image_guess(DP_Input *input, DP_ImageFileType *out_type) +{ + unsigned char buf[12]; + bool error; + size_t read = DP_input_read(input, buf, sizeof(buf), &error); + if (error) { + return NULL; + } + + DP_ImageFileType type = DP_image_guess(buf, read); + assign_type(out_type, type); + + DP_Image *(*read_fn)(DP_Input *); + switch (type) { + case DP_IMAGE_FILE_TYPE_PNG: + read_fn = DP_image_png_read; + break; + case DP_IMAGE_FILE_TYPE_JPEG: + read_fn = DP_image_jpeg_read; + break; + case DP_IMAGE_FILE_TYPE_WEBP: + read_fn = DP_image_webp_read; + break; + default: + DP_error_set("Could not guess image format"); + return NULL; + } + + if (!DP_input_rewind(input)) { + return NULL; + } + + return read_fn(input); +} + +DP_Image *DP_image_new_from_file(DP_Input *input, DP_ImageFileType type, + DP_ImageFileType *out_type) +{ + DP_ASSERT(input); + switch (type) { + case DP_IMAGE_FILE_TYPE_GUESS: + return read_image_guess(input, out_type); + case DP_IMAGE_FILE_TYPE_PNG: + assign_type(out_type, DP_IMAGE_FILE_TYPE_PNG); + return DP_image_png_read(input); + case DP_IMAGE_FILE_TYPE_JPEG: + assign_type(out_type, DP_IMAGE_FILE_TYPE_JPEG); + return DP_image_jpeg_read(input); + case DP_IMAGE_FILE_TYPE_WEBP: + assign_type(out_type, DP_IMAGE_FILE_TYPE_WEBP); + return DP_image_webp_read(input); + default: + assign_type(out_type, DP_IMAGE_FILE_TYPE_UNKNOWN); + DP_error_set("Unknown image file type %d", (int)type); + return NULL; + } +} + +DP_Image *DP_image_read_png(DP_Input *input) +{ + DP_ASSERT(input); + return DP_image_png_read(input); +} + +DP_Image *DP_image_read_jpeg(DP_Input *input) +{ + DP_ASSERT(input); + return DP_image_jpeg_read(input); +} + +DP_Image *DP_image_read_webp(DP_Input *input) +{ + DP_ASSERT(input); + return DP_image_webp_read(input); +} + +bool DP_image_write_png(DP_Image *img, DP_Output *output) +{ + DP_ASSERT(img); + DP_ASSERT(output); + return DP_image_png_write(output, DP_image_width(img), DP_image_height(img), + DP_image_pixels(img)); +} + +bool DP_image_write_jpeg(DP_Image *img, DP_Output *output) +{ + DP_ASSERT(img); + DP_ASSERT(output); + return DP_image_jpeg_write(output, DP_image_width(img), + DP_image_height(img), DP_image_pixels(img)); +} + +bool DP_image_write_webp(DP_Image *img, DP_Output *output) +{ + DP_ASSERT(img); + DP_ASSERT(output); + return DP_image_webp_write(output, DP_image_width(img), + DP_image_height(img), DP_image_pixels(img)); +} diff --git a/src/drawdance/libimpex/dpimpex/image_impex.h b/src/drawdance/libimpex/dpimpex/image_impex.h new file mode 100644 index 0000000000..54387e095e --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/image_impex.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_IMAGE_H +#define DPIMPEX_IMAGE_H +#include +#include + +typedef struct DP_Image DP_Image; +typedef struct DP_Input DP_Input; +typedef struct DP_Output DP_Output; + + +DP_Image *DP_image_new_from_file(DP_Input *input, DP_ImageFileType type, + DP_ImageFileType *out_type); + +DP_Image *DP_image_read_png(DP_Input *input); +DP_Image *DP_image_read_jpeg(DP_Input *input); +DP_Image *DP_image_read_jpeg(DP_Input *input); + +bool DP_image_write_png(DP_Image *img, DP_Output *output) DP_MUST_CHECK; +bool DP_image_write_jpeg(DP_Image *img, DP_Output *output) DP_MUST_CHECK; +bool DP_image_write_webp(DP_Image *img, DP_Output *output) DP_MUST_CHECK; + + +#endif diff --git a/src/drawdance/libengine/dpengine/image_jpeg.c b/src/drawdance/libimpex/dpimpex/image_jpeg.c similarity index 87% rename from src/drawdance/libengine/dpengine/image_jpeg.c rename to src/drawdance/libimpex/dpimpex/image_jpeg.c index c3f8209123..c9b75a358f 100644 --- a/src/drawdance/libengine/dpengine/image_jpeg.c +++ b/src/drawdance/libimpex/dpimpex/image_jpeg.c @@ -1,24 +1,4 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include "image_jpeg.h" #include "image.h" #include "pixels.h" diff --git a/src/drawdance/libimpex/dpimpex/image_jpeg.h b/src/drawdance/libimpex/dpimpex/image_jpeg.h new file mode 100644 index 0000000000..b0f3f38525 --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/image_jpeg.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_IMAGE_JPEG_H +#define DPIMPEX_IMAGE_JPEG_H +#include + +typedef struct DP_Image DP_Image; +typedef struct DP_Input DP_Input; +typedef struct DP_Output DP_Output; +typedef union DP_Pixel8 DP_Pixel8; + + +DP_Image *DP_image_jpeg_read(DP_Input *input); + +bool DP_image_jpeg_write(DP_Output *output, int width, int height, + DP_Pixel8 *pixels); + + +#endif diff --git a/src/drawdance/libengine/dpengine/image_png.c b/src/drawdance/libimpex/dpimpex/image_png.c similarity index 87% rename from src/drawdance/libengine/dpengine/image_png.c rename to src/drawdance/libimpex/dpimpex/image_png.c index 08f02dc6f7..afa02447c2 100644 --- a/src/drawdance/libengine/dpengine/image_png.c +++ b/src/drawdance/libimpex/dpimpex/image_png.c @@ -1,24 +1,4 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include "image_png.h" #include "image.h" #include "pixels.h" diff --git a/src/drawdance/libimpex/dpimpex/image_png.h b/src/drawdance/libimpex/dpimpex/image_png.h new file mode 100644 index 0000000000..6c7d54617e --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/image_png.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_IMAGE_PNG_H +#define DPIMPEX_IMAGE_PNG_H +#include + +typedef struct DP_Image DP_Image; +typedef struct DP_Input DP_Input; +typedef struct DP_Output DP_Output; +typedef union DP_Pixel8 DP_Pixel8; +typedef union DP_UPixel8 DP_UPixel8; + + +DP_Image *DP_image_png_read(DP_Input *input); + +bool DP_image_png_write(DP_Output *output, int width, int height, + DP_Pixel8 *pixels); + +bool DP_image_png_write_unpremultiplied(DP_Output *output, int width, int height, + DP_UPixel8 *pixels); + + +#endif diff --git a/src/drawdance/libengine/dpengine/image_qt.cpp b/src/drawdance/libimpex/dpimpex/image_qt.cpp similarity index 83% rename from src/drawdance/libengine/dpengine/image_qt.cpp rename to src/drawdance/libimpex/dpimpex/image_qt.cpp index 3a1e40f968..7c5e06a999 100644 --- a/src/drawdance/libengine/dpengine/image_qt.cpp +++ b/src/drawdance/libimpex/dpimpex/image_qt.cpp @@ -1,33 +1,13 @@ -/* - * Copyright (c) 2023 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-License-Identifier: GPL-3.0-or-later extern "C" { -#include "image.h" #include "image_jpeg.h" #include "image_png.h" -#include "pixels.h" #include #include #include #include +#include +#include } #include #include diff --git a/src/drawdance/libengine/dpengine/image_webp.c b/src/drawdance/libimpex/dpimpex/image_webp.c similarity index 98% rename from src/drawdance/libengine/dpengine/image_webp.c rename to src/drawdance/libimpex/dpimpex/image_webp.c index 161ebe7cff..7cb54ced68 100644 --- a/src/drawdance/libengine/dpengine/image_webp.c +++ b/src/drawdance/libimpex/dpimpex/image_webp.c @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "image_webp.h" -#include "image.h" -#include "pixels.h" #include #include #include #include +#include +#include #include #include diff --git a/src/drawdance/libengine/dpengine/image_webp.h b/src/drawdance/libimpex/dpimpex/image_webp.h similarity index 86% rename from src/drawdance/libengine/dpengine/image_webp.h rename to src/drawdance/libimpex/dpimpex/image_webp.h index 19f216c621..3dc5ea3461 100644 --- a/src/drawdance/libengine/dpengine/image_webp.h +++ b/src/drawdance/libimpex/dpimpex/image_webp.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DP_IMAGE_WEBP_H -#define DP_IMAGE_WEBP_H +#ifndef DPIMPEX_IMAGE_WEBP_H +#define DPIMPEX_IMAGE_WEBP_H #include typedef struct DP_Image DP_Image; diff --git a/src/drawdance/libengine/dpengine/load.c b/src/drawdance/libimpex/dpimpex/load.c similarity index 97% rename from src/drawdance/libengine/dpengine/load.c rename to src/drawdance/libimpex/dpimpex/load.c index ef337804bf..0bdb65afa6 100644 --- a/src/drawdance/libengine/dpengine/load.c +++ b/src/drawdance/libimpex/dpimpex/load.c @@ -1,43 +1,7 @@ -/* - * Copyright (C) 2022 - 2023 askmeaboutloom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * -------------------------------------------------------------------- - * - * This code is based on Drawpile, using it under the GNU General Public - * License, version 3. See 3rdparty/licenses/drawpile/COPYING for details. - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include "load.h" -#include "annotation.h" -#include "canvas_state.h" -#include "document_metadata.h" -#include "draw_context.h" -#include "image.h" -#include "key_frame.h" -#include "layer_content.h" -#include "layer_group.h" -#include "layer_list.h" -#include "layer_props.h" -#include "layer_props_list.h" -#include "ops.h" -#include "player.h" +#include "image_impex.h" #include "save.h" -#include "text.h" -#include "tile.h" -#include "timeline.h" -#include "track.h" #include "xml_stream.h" #include "zip_archive.h" #include @@ -48,6 +12,23 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/drawdance/libimpex/dpimpex/load.h b/src/drawdance/libimpex/dpimpex/load.h new file mode 100644 index 0000000000..d055bd574c --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/load.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_LOAD_H +#define DPIMPEX_LOAD_H +#include +#include +#include + +typedef struct DP_CanvasState DP_CanvasState; +typedef struct DP_DrawContext DP_DrawContext; +typedef struct DP_Input DP_Input; +typedef struct DP_Player DP_Player; + +#define DP_LOAD_FLAG_NONE 0u +#define DP_LOAD_FLAG_SINGLE_THREAD (1u << 0u) + + +typedef struct DP_LoadFormat { + const char *title; + const char **extensions; // Terminated by a NULL element. +} DP_LoadFormat; + +// Returns supported formats for loading, terminated by a {NULL, NULL} element. +const DP_LoadFormat *DP_load_supported_formats(void); + + +typedef void (*DP_LoadFixedLayerFn)(void *user, int layer_id); + +DP_SaveImageType DP_load_guess(const unsigned char *buf, size_t size); + +DP_CanvasState *DP_load(DP_DrawContext *dc, const char *path, + const char *flat_image_layer_title, unsigned int flags, + DP_LoadResult *out_result, DP_SaveImageType *out_type); + +DP_CanvasState *DP_load_ora(DP_DrawContext *dc, const char *path, + unsigned int flags, + DP_LoadFixedLayerFn on_fixed_layer, void *user, + DP_LoadResult *out_result); + +DP_CanvasState *DP_load_psd(DP_DrawContext *dc, DP_Input *input, + DP_LoadResult *out_result); + +DP_Player *DP_load_recording(const char *path, DP_LoadResult *out_result); + +DP_Player *DP_load_debug_dump(const char *path, DP_LoadResult *out_result); + + +#endif diff --git a/src/drawdance/libengine/dpengine/load_animation.c b/src/drawdance/libimpex/dpimpex/load_animation.c similarity index 96% rename from src/drawdance/libengine/dpengine/load_animation.c rename to src/drawdance/libimpex/dpimpex/load_animation.c index a330da933f..059dd66548 100644 --- a/src/drawdance/libengine/dpengine/load_animation.c +++ b/src/drawdance/libimpex/dpimpex/load_animation.c @@ -1,21 +1,22 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "load_animation.h" -#include "canvas_state.h" -#include "document_metadata.h" -#include "draw_context.h" -#include "image.h" -#include "key_frame.h" -#include "layer_content.h" -#include "layer_group.h" -#include "layer_list.h" -#include "layer_props.h" -#include "layer_props_list.h" -#include "tile.h" -#include "timeline.h" -#include "track.h" +#include "image_impex.h" #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/drawdance/libengine/dpengine/load_animation.h b/src/drawdance/libimpex/dpimpex/load_animation.h similarity index 95% rename from src/drawdance/libengine/dpengine/load_animation.h rename to src/drawdance/libimpex/dpimpex/load_animation.h index 14cd9d08b3..3dce89fea1 100644 --- a/src/drawdance/libengine/dpengine/load_animation.h +++ b/src/drawdance/libimpex/dpimpex/load_animation.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DPENGINE_LOAD_ANIMATION_H -#define DPENGINE_LOAD_ANIMATION_H +#ifndef DPIMPEX_LOAD_ANIMATION_H +#define DPIMPEX_LOAD_ANIMATION_H #include "load.h" #include diff --git a/src/drawdance/libengine/dpengine/load_psd.cpp b/src/drawdance/libimpex/dpimpex/load_psd.cpp similarity index 98% rename from src/drawdance/libengine/dpengine/load_psd.cpp rename to src/drawdance/libimpex/dpimpex/load_psd.cpp index df0d53a46a..6f9001134d 100644 --- a/src/drawdance/libengine/dpengine/load_psd.cpp +++ b/src/drawdance/libimpex/dpimpex/load_psd.cpp @@ -1,16 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-or-later extern "C" { -#include "canvas_state.h" -#include "image.h" -#include "layer_content.h" -#include "layer_group.h" -#include "layer_list.h" -#include "layer_props.h" -#include "layer_props_list.h" #include "load.h" #include "utf16be.h" #include #include +#include +#include +#include +#include +#include +#include +#include #include } #include diff --git a/src/drawdance/libimpex/dpimpex/paint_engine_playback.c b/src/drawdance/libimpex/dpimpex/paint_engine_playback.c new file mode 100644 index 0000000000..2ed1d0b16c --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/paint_engine_playback.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_PAINT_ENGINE_PLAYBACK_H +#define DPIMPEX_PAINT_ENGINE_PLAYBACK_H +#include "paint_engine_playback.h" +#include "player_index.h" +#include +#include +#include +#include +#include + + +#define PLAYBACK_STEP_MESSAGES 0 +#define PLAYBACK_STEP_UNDO_POINTS 1 +#define PLAYBACK_STEP_MSECS 2 + + +static void push_playback(DP_PaintEnginePushMessageFn push_message, void *user, + long long position) +{ + push_message(user, DP_msg_internal_playback_new(0, position)); +} + +static long long guess_message_msecs(DP_Message *msg, DP_MessageType type, + bool *out_next_has_time) +{ + // The recording format doesn't contain proper timing information, so we + // just make some wild guesses as to how long each message takes to try to + // get some vaguely okayly timed playback out of it. + switch (type) { + case DP_MSG_DRAW_DABS_CLASSIC: + case DP_MSG_DRAW_DABS_PIXEL: + case DP_MSG_DRAW_DABS_PIXEL_SQUARE: + case DP_MSG_DRAW_DABS_MYPAINT: + return 5; + case DP_MSG_PUT_IMAGE: + case DP_MSG_MOVE_REGION: + case DP_MSG_MOVE_RECT: + case DP_MSG_MOVE_POINTER: + return 10; + case DP_MSG_LAYER_CREATE: + case DP_MSG_LAYER_ATTRIBUTES: + case DP_MSG_LAYER_RETITLE: + case DP_MSG_LAYER_ORDER: + case DP_MSG_LAYER_DELETE: + case DP_MSG_LAYER_VISIBILITY: + case DP_MSG_ANNOTATION_RESHAPE: + case DP_MSG_ANNOTATION_EDIT: + return 20; + case DP_MSG_CANVAS_RESIZE: + return 60; + case DP_MSG_UNDO: + // An undo for user 0 means that the next message is actually an undone + // entry, which happens at the start of recordings. We treat those + // messages as having a length of 0, since they just get appended to the + // history and aren't visible. + if (DP_message_context_id(msg) == 0 + && DP_msg_undo_override_user(DP_message_internal(msg)) == 0) { + *out_next_has_time = false; + return 0; + } + else { + return 20; + } + default: + return 0; + } +} + +static DP_PlayerResult +skip_playback_forward(DP_PaintEngine *pe, long long steps, int what, + DP_PaintEngineFilterMessageFn filter_message_or_null, + DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_ASSERT(steps >= 0); + DP_ASSERT(push_message); + DP_ASSERT(what == PLAYBACK_STEP_MESSAGES + || what == PLAYBACK_STEP_UNDO_POINTS + || what == PLAYBACK_STEP_MSECS); + DP_debug("Skip playback forward by %lld %s", steps, + what == PLAYBACK_STEP_MESSAGES ? "step(s)" + : what == PLAYBACK_STEP_UNDO_POINTS ? "undo point(s)" + : "millisecond(s)"); + + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + long long done = 0; + if (what == PLAYBACK_STEP_MSECS) { + done += DP_paint_engine_playback(pe)->msecs; + } + + while (done < steps) { + DP_Message *msg; + DP_PlayerResult result = DP_player_step(player, &msg); + if (result == DP_PLAYER_SUCCESS) { + bool should_time = true; + if (filter_message_or_null) { + unsigned int flags = filter_message_or_null(user, msg); + if (flags & DP_PAINT_ENGINE_FILTER_MESSAGE_FLAG_NO_TIME) { + should_time = false; + } + } + + DP_MessageType type = DP_message_type(msg); + if (type == DP_MSG_INTERVAL) { + if (what == PLAYBACK_STEP_MESSAGES) { + ++done; + } + else if (what == PLAYBACK_STEP_MSECS && should_time) { + DP_MsgInterval *mi = DP_message_internal(msg); + // Really no point in delaying playback for ages, it just + // makes the user wonder if there's something broken. + done += DP_min_int(1000, DP_msg_interval_msecs(mi)); + } + DP_message_decref(msg); + } + else { + if (what == PLAYBACK_STEP_MESSAGES + || (what == PLAYBACK_STEP_UNDO_POINTS + && type == DP_MSG_UNDO_POINT)) { + ++done; + } + else if (what == PLAYBACK_STEP_MSECS) { + if (DP_paint_engine_playback(pe)->next_has_time) { + long long msecs = guess_message_msecs( + msg, type, + &DP_paint_engine_playback(pe)->next_has_time); + if (should_time) { + done += msecs; + } + } + else { + DP_paint_engine_playback(pe)->next_has_time = true; + } + } + push_message(user, msg); + } + } + else if (result == DP_PLAYER_ERROR_PARSE) { + if (what == PLAYBACK_STEP_MESSAGES) { + ++done; + } + DP_warn("Can't play back message: %s", DP_error()); + } + else { + // We're either at the end of the recording or encountered an input + // error. In either case, we're done playing this recording, report + // a negative value as the position to indicate that. + push_playback(push_message, user, -1); + return result; + } + } + + if (what == PLAYBACK_STEP_MSECS) { + DP_paint_engine_playback(pe)->msecs = done - steps; + } + + // If we don't have an index, report the position as a percentage + // completion based on the amount of bytes read from the recording. + long long position = + DP_player_index_loaded(player) + ? DP_player_position(player) + : DP_double_to_llong(DP_player_progress(player) * 100.0 + 0.5); + push_playback(push_message, user, position); + return DP_PLAYER_SUCCESS; +} + +DP_PlayerResult +DP_paint_engine_playback_step(DP_PaintEngine *pe, long long steps, + DP_PaintEnginePushMessageFn push_message, + void *user) +{ + DP_ASSERT(pe); + DP_ASSERT(steps >= 0); + DP_ASSERT(push_message); + return skip_playback_forward(pe, steps, PLAYBACK_STEP_MESSAGES, NULL, + push_message, user); +} + +static DP_PlayerResult rewind_playback(DP_PaintEngine *pe, + DP_PaintEnginePushMessageFn push_message, + void *user) +{ + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + if (DP_player_rewind(player)) { + push_message(user, DP_msg_internal_reset_new(0)); + push_playback(push_message, user, 0); + return DP_PLAYER_SUCCESS; + } + else { + push_playback(push_message, user, -1); + return DP_PLAYER_ERROR_INPUT; + } +} + +static DP_PlayerResult +jump_playback_to(DP_PaintEngine *pe, DP_DrawContext *dc, long long to, + bool relative, bool exact, + DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + if (!DP_player_index_loaded(player)) { + DP_error_set("No index loaded"); + push_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + long long player_position = DP_player_position(player); + long long target_position = relative ? player_position + to : to; + DP_debug("Jump playback from %lld to %s %lld", player_position, + exact ? "exactly" : "snapshot nearest", target_position); + + long long message_count = + DP_uint_to_llong(DP_player_index_message_count(player)); + if (message_count == 0) { + DP_error_set("Recording contains no messages"); + push_playback(push_message, user, player_position); + return DP_PLAYER_ERROR_INPUT; + } + else if (target_position < 0) { + target_position = 0; + } + else if (target_position >= message_count) { + target_position = message_count - 1; + } + DP_debug("Clamped position to %lld (message count %lld)", target_position, + message_count); + + DP_PlayerIndexEntry entry = DP_player_index_entry_search( + player, target_position, relative && !exact && to > 0); + DP_debug("Loaded entry with message index %lld, message offset %zu, " + "snapshot offset %zu, thumbnail offset %zu", + entry.message_index, entry.message_offset, entry.snapshot_offset, + entry.thumbnail_offset); + + bool inside_snapshot = player_position >= entry.message_index + && player_position < target_position; + if (inside_snapshot) { + long long steps = target_position - player_position; + DP_debug("Already inside snapshot, stepping forward by %lld", steps); + DP_PlayerResult result = skip_playback_forward( + pe, steps, PLAYBACK_STEP_MESSAGES, NULL, push_message, user); + // If skipping playback forward doesn't work, e.g. because we're + // in an input error state, punt to reloading the snapshot. + if (result == DP_PLAYER_SUCCESS) { + return result; + } + else { + DP_warn("Skipping inside snapshot failed with result %d: %s", + (int)result, DP_error()); + } + } + + DP_PlayerIndexEntrySnapshot *snapshot = + DP_player_index_entry_load(player, dc, entry); + if (!snapshot) { + DP_debug("Reading snapshot failed: %s", DP_error()); + push_playback(push_message, user, player_position); + return DP_PLAYER_ERROR_INPUT; + } + + if (!DP_player_seek(player, entry.message_index, entry.message_offset)) { + DP_player_index_entry_snapshot_free(snapshot); + push_playback(push_message, user, player_position); + return DP_PLAYER_ERROR_INPUT; + } + + DP_CanvasState *cs = + DP_player_index_entry_snapshot_canvas_state_inc(snapshot); + push_message(user, DP_msg_internal_reset_to_state_new(0, cs)); + + int count = DP_player_index_entry_snapshot_message_count(snapshot); + for (int i = 0; i < count; ++i) { + DP_Message *msg = + DP_player_index_entry_snapshot_message_at_inc(snapshot, i); + if (msg) { + push_message(user, msg); + } + } + + DP_player_index_entry_snapshot_free(snapshot); + return skip_playback_forward( + pe, exact ? target_position - entry.message_index : 0, + PLAYBACK_STEP_MESSAGES, NULL, push_message, user); +} + +DP_PlayerResult DP_paint_engine_playback_skip_by( + DP_PaintEngine *pe, DP_DrawContext *dc, long long steps, bool by_snapshots, + DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_ASSERT(pe); + if (steps < 0 || by_snapshots) { + return jump_playback_to(pe, dc, steps, true, !by_snapshots, + push_message, user); + } + else { + return skip_playback_forward(pe, steps, PLAYBACK_STEP_UNDO_POINTS, NULL, + push_message, user); + } +} + +DP_PlayerResult DP_paint_engine_playback_jump_to( + DP_PaintEngine *pe, DP_DrawContext *dc, long long position, + DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_ASSERT(pe); + if (position <= 0) { + return rewind_playback(pe, push_message, user); + } + else { + return jump_playback_to(pe, dc, position, false, true, push_message, + user); + } +} + +DP_PlayerResult DP_paint_engine_playback_begin(DP_PaintEngine *pe) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + DP_paint_engine_playback(pe)->msecs = 0; + return DP_PLAYER_SUCCESS; + } + else { + DP_error_set("No player set"); + return DP_PLAYER_ERROR_OPERATION; + } +} + +DP_PlayerResult DP_paint_engine_playback_play( + DP_PaintEngine *pe, long long msecs, + DP_PaintEngineFilterMessageFn filter_message_or_null, + DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_ASSERT(pe); + return skip_playback_forward(pe, msecs, PLAYBACK_STEP_MSECS, + filter_message_or_null, push_message, user); +} + +bool DP_paint_engine_playback_index_build( + DP_PaintEngine *pe, DP_DrawContext *dc, + DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, + DP_PlayerIndexProgressFn progress_fn, void *user) +{ + DP_ASSERT(pe); + DP_ASSERT(dc); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + return DP_player_index_build(player, dc, should_snapshot_fn, + progress_fn, user); + } + else { + DP_error_set("No player set"); + return false; + } +} + +bool DP_paint_engine_playback_index_load(DP_PaintEngine *pe) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + return DP_player_index_load(player); + } + else { + DP_error_set("No player set"); + return false; + } +} + +unsigned int DP_paint_engine_playback_index_message_count(DP_PaintEngine *pe) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + return DP_player_index_message_count(player); + } + else { + DP_error_set("No player set"); + return 0; + } +} + +size_t DP_paint_engine_playback_index_entry_count(DP_PaintEngine *pe) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + return DP_player_index_entry_count(player); + } + else { + DP_error_set("No player set"); + return 0; + } +} + +DP_Image *DP_paint_engine_playback_index_thumbnail_at(DP_PaintEngine *pe, + size_t index, + bool *out_error) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + return DP_player_index_thumbnail_at(player, index, out_error); + } + else { + DP_error_set("No player set"); + if (out_error) { + *out_error = true; + } + return NULL; + } +} + +static DP_PlayerResult step_dump(DP_Player *player, + DP_PaintEnginePushMessageFn push_message, + void *user) +{ + DP_DumpType type; + int count; + DP_Message **msgs; + DP_PlayerResult result = DP_player_step_dump(player, &type, &count, &msgs); + if (result == DP_PLAYER_SUCCESS) { + push_message(user, DP_msg_internal_dump_command_new_inc(0, (int)type, + count, msgs)); + } + return result; +} + +static void push_dump_playback(DP_PaintEnginePushMessageFn push_message, + void *user, long long position) +{ + push_message(user, DP_msg_internal_dump_playback_new(0, position)); +} + +DP_PlayerResult DP_paint_engine_playback_dump_step( + DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_dump_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + DP_PlayerResult result = step_dump(player, push_message, user); + long long position_to_report = + result == DP_PLAYER_SUCCESS ? DP_player_position(player) : -1; + push_dump_playback(push_message, user, position_to_report); + return result; +} + +DP_PlayerResult DP_paint_engine_playback_dump_jump_previous_reset( + DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_dump_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + if (!DP_player_seek_dump(player, DP_player_position(player) - 1)) { + push_dump_playback(push_message, user, -1); + return DP_PLAYER_ERROR_INPUT; + } + + push_message(user, DP_msg_internal_reset_new(0)); + push_dump_playback(push_message, user, DP_player_position(player)); + return DP_PLAYER_SUCCESS; +} + +static int step_toward_next_reset(DP_Player *player, bool stop_at_reset, + DP_PaintEnginePushMessageFn push_message, + void *user) +{ + long long position = DP_player_position(player); + size_t offset = DP_player_tell(player); + DP_DumpType type; + int count; + DP_Message **msgs; + DP_PlayerResult result = DP_player_step_dump(player, &type, &count, &msgs); + if (result == DP_PLAYER_SUCCESS) { + if (type == DP_DUMP_RESET && stop_at_reset) { + if (DP_player_seek(player, position, offset)) { + return -1; + } + else { + return DP_PLAYER_ERROR_INPUT; + } + } + else { + push_message(user, DP_msg_internal_dump_command_new_inc( + 0, (int)type, count, msgs)); + } + } + return (int)result; +} + +DP_PlayerResult DP_paint_engine_playback_dump_jump_next_reset( + DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_dump_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + // We want to jump up to, but excluding the next reset. So we keep walking + // through the dump until we hit a reset and then seek to before it. If we + // hit a reset as our very first message, we push through it, since + // otherwise we'd be doing nothing at all until manually stepping forward. + int result = step_toward_next_reset(player, false, push_message, user); + while (result == DP_PLAYER_SUCCESS) { + result = step_toward_next_reset(player, true, push_message, user); + } + + push_dump_playback(push_message, user, DP_player_position(player)); + return result == -1 ? DP_PLAYER_SUCCESS : (DP_PlayerResult)result; +} + +DP_PlayerResult +DP_paint_engine_playback_dump_jump(DP_PaintEngine *pe, long long position, + DP_PaintEnginePushMessageFn push_message, + void *user) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (!player) { + DP_error_set("No player set"); + push_dump_playback(push_message, user, -1); + return DP_PLAYER_ERROR_OPERATION; + } + + if (!DP_player_seek_dump(player, position)) { + push_dump_playback(push_message, user, -1); + return DP_PLAYER_ERROR_INPUT; + } + + if (DP_player_position(player) == 0) { + push_message(user, DP_msg_internal_reset_new(0)); + } + + DP_PlayerResult result = DP_PLAYER_SUCCESS; + while (DP_player_position(player) < position) { + result = step_dump(player, push_message, user); + if (result != DP_PLAYER_SUCCESS) { + break; + } + } + push_dump_playback(push_message, user, DP_player_position(player)); + return result; +} + +bool DP_paint_engine_playback_flush(DP_PaintEngine *pe, + DP_PaintEnginePushMessageFn push_message, + void *user) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + while (true) { + DP_Message *msg; + DP_PlayerResult result = DP_player_step(player, &msg); + if (result == DP_PLAYER_SUCCESS) { + DP_MessageType type = DP_message_type(msg); + if (type == DP_MSG_INTERVAL) { + DP_message_decref(msg); + } + else { + push_message(user, msg); + } + } + else if (result == DP_PLAYER_ERROR_PARSE) { + DP_warn("Can't play back message: %s", DP_error()); + } + else if (result == DP_PLAYER_RECORDING_END) { + return true; + } + else { + return false; // Some kind of input error. + } + } + } + else { + return false; + } +} + +bool DP_paint_engine_playback_close(DP_PaintEngine *pe) +{ + DP_ASSERT(pe); + DP_Player *player = DP_paint_engine_playback(pe)->player; + if (player) { + DP_player_free(player); + DP_paint_engine_playback(pe)->player = NULL; + return true; + } + else { + return false; + } +} + + +#endif diff --git a/src/drawdance/libimpex/dpimpex/paint_engine_playback.h b/src/drawdance/libimpex/dpimpex/paint_engine_playback.h new file mode 100644 index 0000000000..e9570c075e --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/paint_engine_playback.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_PAINT_ENGINE_PLAYBACK_H +#define DPIMPEX_PAINT_ENGINE_PLAYBACK_H +#include "player_index.h" +#include +#include + + +DP_PlayerResult +DP_paint_engine_playback_step(DP_PaintEngine *pe, long long steps, + DP_PaintEnginePushMessageFn push_message, + void *user); + +DP_PlayerResult DP_paint_engine_playback_skip_by( + DP_PaintEngine *pe, DP_DrawContext *dc, long long steps, bool by_snapshots, + DP_PaintEnginePushMessageFn push_message, void *user); + +DP_PlayerResult DP_paint_engine_playback_jump_to( + DP_PaintEngine *pe, DP_DrawContext *dc, long long position, + DP_PaintEnginePushMessageFn push_message, void *user); + +DP_PlayerResult DP_paint_engine_playback_begin(DP_PaintEngine *pe); + +DP_PlayerResult DP_paint_engine_playback_play( + DP_PaintEngine *pe, long long msecs, + DP_PaintEngineFilterMessageFn filter_message_or_null, + DP_PaintEnginePushMessageFn push_message, void *user); + +bool DP_paint_engine_playback_index_build( + DP_PaintEngine *pe, DP_DrawContext *dc, + DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, + DP_PlayerIndexProgressFn progress_fn, void *user); + +bool DP_paint_engine_playback_index_load(DP_PaintEngine *pe); + +unsigned int DP_paint_engine_playback_index_message_count(DP_PaintEngine *pe); + +size_t DP_paint_engine_playback_index_entry_count(DP_PaintEngine *pe); + +DP_Image *DP_paint_engine_playback_index_thumbnail_at(DP_PaintEngine *pe, + size_t index, + bool *out_error); + +DP_PlayerResult DP_paint_engine_playback_dump_step( + DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user); + +DP_PlayerResult DP_paint_engine_playback_dump_jump_previous_reset( + DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user); + +DP_PlayerResult DP_paint_engine_playback_dump_jump_next_reset( + DP_PaintEngine *pe, DP_PaintEnginePushMessageFn push_message, void *user); + +DP_PlayerResult +DP_paint_engine_playback_dump_jump(DP_PaintEngine *pe, long long position, + DP_PaintEnginePushMessageFn push_message, + void *user); + +bool DP_paint_engine_playback_flush(DP_PaintEngine *pe, + DP_PaintEnginePushMessageFn push_message, + void *user); + +bool DP_paint_engine_playback_close(DP_PaintEngine *pe); + + +#endif diff --git a/src/drawdance/libimpex/dpimpex/player_index.c b/src/drawdance/libimpex/dpimpex/player_index.c new file mode 100644 index 0000000000..2b9b68f767 --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/player_index.c @@ -0,0 +1,2043 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include "player_index.h" +#include "image_impex.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DP_PERF_CONTEXT "player" +#define INDEX_MAGIC "DPIDX" +#define INDEX_MAGIC_LENGTH 6 +#define INDEX_VERSION 12 +#define INDEX_VERSION_LENGTH 2 +#define INDEX_HEADER_LENGTH (INDEX_MAGIC_LENGTH + INDEX_VERSION_LENGTH + 12) +#define INITAL_ENTRY_CAPACITY 64 + +static_assert(INDEX_MAGIC_LENGTH < sizeof(DP_OutputBinaryEntry), + "index header fits into output binary entry"); + +struct DP_PlayerIndexEntrySnapshot { + DP_CanvasState *cs; + int message_count; + DP_Message *messages[]; +}; + +typedef struct DP_BuildIndexTileMap { + DP_Tile *t; + size_t offset; + UT_hash_handle hh; +} DP_BuildIndexTileMap; + +typedef struct DP_BuildIndexLayerKey { + union { + DP_LayerContent *lc; + DP_LayerGroup *lg; + }; + DP_LayerProps *lp; +} DP_BuildIndexLayerKey; + +typedef struct DP_BuildIndexLayerMap { + DP_BuildIndexLayerKey key; + size_t offset; + UT_hash_handle hh; +} DP_BuildIndexLayerMap; + +typedef struct DP_BuildIndexAnnotationMap { + DP_Annotation *a; + size_t offset; + UT_hash_handle hh; +} DP_BuildIndexAnnotationMap; + +typedef struct DP_BuildIndexMaps { + DP_BuildIndexTileMap *tiles; + DP_BuildIndexLayerMap *layers; + DP_BuildIndexAnnotationMap *annotations; + struct { + DP_DocumentMetadata *dm; + size_t offset; + } metadata; + struct { + DP_Timeline *tl; + size_t offset; + } timeline; +} DP_BuildIndexMaps; + +typedef struct DP_BuildIndexEntryContext { + DP_Output *output; + DP_AclState *acls; + DP_LocalState *local_state; + DP_CanvasHistory *ch; + DP_CanvasState *cs; + DP_DrawContext *dc; + DP_BuildIndexMaps current; + DP_BuildIndexMaps *last; + int message_count; + struct { + size_t history; + size_t layers; + size_t background_tile; + size_t snapshot; + size_t thumbnail; + } offset; + struct { + unsigned char *buffer; + size_t size; + } annotation; +} DP_BuildIndexEntryContext; + +typedef struct DP_BuildIndexContext { + DP_Player *player; + DP_Output *output; + DP_AclState *acls; + DP_LocalState *local_state; + DP_CanvasHistory *ch; + DP_DrawContext *dc; + long long message_count; + DP_Vector entries; + DP_BuildIndexMaps last; + DP_PlayerIndexShouldSnapshotFn should_snapshot_fn; + DP_PlayerIndexProgressFn progress_fn; + void *user; +} DP_BuildIndexContext; + +struct DP_BuildIndexLayerProps { + uint16_t layer_id; + uint16_t title_length; + union { + const char *title; + char *buffer; + }; + uint8_t opacity; + uint8_t blend_mode; + uint8_t hidden; + uint8_t censored; + uint8_t isolated; + uint8_t group; +}; + +static bool write_index_header(DP_BuildIndexContext *c) +{ + return DP_OUTPUT_WRITE_LITTLEENDIAN( + c->output, DP_OUTPUT_BYTES(INDEX_MAGIC, INDEX_MAGIC_LENGTH), + DP_OUTPUT_UINT16(INDEX_VERSION), DP_OUTPUT_UINT32(0), + DP_OUTPUT_UINT64(0)); +} + +unsigned char *get_message_buffer(void *user, size_t length) +{ + return DP_draw_context_pool_require(user, length); +} + +static bool write_index_history_message_dec(DP_BuildIndexEntryContext *e, + DP_Message *msg) +{ + size_t length = DP_message_serialize(msg, false, get_message_buffer, e->dc); + DP_message_decref(msg); + if (length == 0) { + DP_error_set("Error serializing history message %d", + e->message_count + 1); + return true; // Not a fatal error. + } + else { + DP_Output *output = e->output; + bool ok = DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT16(length)) + && DP_output_write(output, DP_draw_context_pool(e->dc), length); + if (ok) { + ++e->message_count; + return true; + } + else { + return false; + } + } +} + +static bool init_reset_image(void *user, DP_CanvasState *cs) +{ + DP_BuildIndexEntryContext *e = user; + e->cs = cs; + DP_Message *msg = DP_acl_state_msg_feature_access_all_new(0); + return write_index_history_message_dec(e, msg); +} + +static bool write_reset_image_message(void *user, DP_Message *msg) +{ + return write_index_history_message_dec(user, msg); +} + +static bool write_index_history(DP_BuildIndexEntryContext *e) +{ + bool error; + size_t offset = DP_output_tell(e->output, &error); + if (error) { + return false; + } + + if (!DP_canvas_history_reset_image_new(e->ch, init_reset_image, + write_reset_image_message, e)) { + return false; + } + + // The state of the permissions at this point. + if (!DP_acl_state_reset_image_build( + e->acls, 0, DP_ACL_STATE_RESET_IMAGE_RECORDING_FLAGS, + write_reset_image_message, e)) { + return false; + } + // Local changes (hidden layers, local canvas background). + if (!DP_local_state_reset_image_build(e->local_state, e->dc, + write_reset_image_message, e)) { + return false; + } + + e->offset.history = offset; + return true; +} + +static DP_BuildIndexTileMap *search_tile(DP_BuildIndexTileMap *tiles, + DP_Tile *t) +{ + DP_BuildIndexTileMap *entry; + HASH_FIND_PTR(tiles, &t, entry); + return entry; +} + +static void move_tile_offset(DP_BuildIndexEntryContext *e, + DP_BuildIndexTileMap *entry) +{ + HASH_DEL(e->last->tiles, entry); + HASH_ADD_PTR(e->current.tiles, t, entry); +} + +static void move_tile_offsets(DP_BuildIndexEntryContext *e, DP_LayerContent *lc) +{ + DP_TileCounts tile_counts = DP_tile_counts_round( + DP_layer_content_width(lc), DP_layer_content_height(lc)); + for (int y = 0; y < tile_counts.y; ++y) { + for (int x = 0; x < tile_counts.x; ++x) { + DP_Tile *t = DP_layer_content_tile_at_noinc(lc, x, y); + DP_BuildIndexTileMap *entry; + bool should_move = t && !search_tile(e->current.tiles, t) + && (entry = search_tile(e->last->tiles, t)) != NULL; + if (should_move) { + move_tile_offset(e, entry); + } + } + } +} + +static unsigned char *get_compression_buffer(size_t size, void *user) +{ + size_t required_capacity = sizeof(uint16_t) + size; + unsigned char *pool = DP_draw_context_pool_require(user, required_capacity); + return pool + sizeof(uint16_t); +} + +static size_t write_index_tile(DP_BuildIndexEntryContext *e, DP_Tile *t) +{ + size_t size = DP_tile_compress(t, DP_draw_context_tile8_buffer(e->dc), + get_compression_buffer, e->dc); + if (size == 0) { + return 0; + } + + bool error; + size_t offset = DP_output_tell(e->output, &error); + if (error) { + return 0; + } + + unsigned char *buffer = DP_draw_context_pool(e->dc); + DP_write_littleendian_uint16(DP_size_to_uint16(size), buffer); + if (!DP_output_write(e->output, buffer, size + sizeof(uint16_t))) { + return 0; + } + + return offset; +} + +static bool maybe_write_index_tile(DP_BuildIndexEntryContext *e, DP_Tile *t, + size_t *out_offset) +{ + if (t) { + DP_BuildIndexTileMap *entry; + if ((entry = search_tile(e->current.tiles, t)) != NULL) { + *out_offset = entry->offset; + } + else if ((entry = search_tile(e->last->tiles, t)) != NULL) { + move_tile_offset(e, entry); + *out_offset = entry->offset; + } + else { + size_t offset = write_index_tile(e, t); + if (offset != 0) { + *out_offset = offset; + } + else { + return false; + } + } + } + else { + *out_offset = 0; + } + return true; +} + +static size_t search_existing_layer(DP_BuildIndexEntryContext *e, + DP_BuildIndexLayerKey *key) +{ + DP_BuildIndexLayerMap *entry; + // If we already wrote the layer this round, it might be here. Shouldn't + // happen in practice, since layer ids are supposed to be unique. + HASH_FIND(hh, e->current.layers, key, sizeof(*key), entry); + if (entry) { + return entry->offset; + } + // If we wrote the layer last snapshot, move it to the current set. + HASH_FIND(hh, e->last->layers, key, sizeof(*key), entry); + if (entry) { + HASH_DEL(e->last->layers, entry); + HASH_ADD(hh, e->current.layers, key, sizeof(entry->key), entry); + if (!DP_layer_props_children_noinc(entry->key.lp)) { + move_tile_offsets(e, entry->key.lc); + } + return entry->offset; + } + else { + return 0; + } +} + +static size_t write_index_layer_list(DP_BuildIndexEntryContext *e, + struct DP_BuildIndexLayerProps *bilp, + DP_LayerList *ll, DP_LayerPropsList *lpl); + +static struct DP_BuildIndexLayerProps to_index_layer_props(DP_LayerProps *lp) +{ + size_t title_length; + const char *title = DP_layer_props_title(lp, &title_length); + return (struct DP_BuildIndexLayerProps){ + DP_int_to_uint16(DP_layer_props_id(lp)), + DP_size_to_uint16(title_length), + {.title = title}, + DP_channel15_to_8(DP_layer_props_opacity(lp)), + DP_int_to_uint8(DP_layer_props_blend_mode(lp)), + DP_layer_props_hidden(lp) ? 1 : 0, + DP_layer_props_censored(lp) ? 1 : 0, + DP_layer_props_isolated(lp) ? 1 : 0, + DP_layer_props_children_noinc(lp) ? 1 : 0}; +} + +static bool write_index_layer_props(DP_BuildIndexEntryContext *e, + struct DP_BuildIndexLayerProps *bilp) +{ + DP_Output *output = e->output; + return DP_OUTPUT_WRITE_LITTLEENDIAN(output, + DP_OUTPUT_UINT16(bilp->layer_id), + DP_OUTPUT_UINT16(bilp->title_length)) + && DP_output_write(output, bilp->title, bilp->title_length) + && DP_OUTPUT_WRITE_LITTLEENDIAN( + output, DP_OUTPUT_UINT8(bilp->opacity), + DP_OUTPUT_UINT8(bilp->blend_mode), DP_OUTPUT_UINT8(bilp->hidden), + DP_OUTPUT_UINT8(bilp->censored), DP_OUTPUT_UINT8(bilp->isolated), + DP_OUTPUT_UINT8(bilp->group)); +} + +static bool is_relevant_sublayer(DP_LayerProps *sub_lp) +{ + int sub_id = DP_layer_props_id(sub_lp); + return sub_id >= 0 && sub_id <= UINT8_MAX; +} + +static size_t write_index_layer_content(DP_BuildIndexEntryContext *e, + DP_LayerContent *lc, DP_LayerProps *lp, + bool sublayer) +{ + if (!sublayer) { // Sublayers are ephemeral, don't try re-using them. + DP_BuildIndexLayerKey key; // There's not gonna be padding here, but + memset(&key, 0, sizeof(key)); // we'll zero it anyway just to make sure. + key.lc = lc; + key.lp = lp; + size_t existing_offset = search_existing_layer(e, &key); + if (existing_offset != 0) { + return existing_offset; + } + } + + DP_LayerPropsList *sub_lpl = DP_layer_content_sub_props_noinc(lc); + int sub_count = DP_layer_props_list_count(sub_lpl); + size_t relevant_sub_count = 0; + for (int i = 0; i < sub_count; ++i) { + DP_LayerProps *sub_lp = DP_layer_props_list_at_noinc(sub_lpl, i); + if (is_relevant_sublayer(sub_lp)) { + ++relevant_sub_count; + } + } + + size_t sub_buffer_size = + sizeof(uint16_t) + sizeof(uint64_t) * relevant_sub_count; + unsigned char *sub_buffer = DP_malloc(sub_buffer_size); + DP_write_littleendian_uint16(DP_size_to_uint16(relevant_sub_count), + sub_buffer); + + size_t sub_index = 0; + DP_LayerList *sub_ll = DP_layer_content_sub_contents_noinc(lc); + for (int i = sub_count - 1; i >= 0; --i) { // Top to bottom. + DP_LayerProps *sub_lp = DP_layer_props_list_at_noinc(sub_lpl, i); + if (is_relevant_sublayer(sub_lp)) { + DP_LayerContent *sub_lc = DP_layer_list_content_at_noinc(sub_ll, i); + size_t sub_offset = + write_index_layer_content(e, sub_lc, sub_lp, true); + + if (sub_offset != 0) { + size_t ii = relevant_sub_count - sub_index - 1; + unsigned char *out = + sub_buffer + sizeof(uint16_t) + ii * sizeof(uint64_t); + DP_write_littleendian_uint64(sub_offset, out); + ++sub_index; + } + else { + DP_free(sub_buffer); + return 0; + } + } + } + DP_ASSERT(sub_index == relevant_sub_count); + + DP_TileCounts tile_counts = DP_tile_counts_round( + DP_layer_content_width(lc), DP_layer_content_height(lc)); + int tile_total = tile_counts.x * tile_counts.y; + size_t tile_buffer_size = DP_int_to_size(tile_total) * sizeof(uint64_t); + unsigned char *tile_buffer = DP_malloc(tile_buffer_size); + size_t written = 0; + for (int y = 0; y < tile_counts.y; ++y) { + for (int x = 0; x < tile_counts.x; ++x) { + DP_Tile *t = DP_layer_content_tile_at_noinc(lc, x, y); + size_t tile_offset; + if (!maybe_write_index_tile(e, t, &tile_offset)) { + DP_free(sub_buffer); + DP_free(tile_buffer); + return 0; + } + written += DP_write_littleendian_uint64(tile_offset, + tile_buffer + written); + } + } + DP_ASSERT(written == tile_buffer_size); + + bool error; + size_t offset = DP_output_tell(e->output, &error); + struct DP_BuildIndexLayerProps bilp = to_index_layer_props(lp); + bool ok = !error && write_index_layer_props(e, &bilp) + && DP_output_write(e->output, sub_buffer, sub_buffer_size) + && DP_output_write(e->output, tile_buffer, tile_buffer_size); + DP_free(sub_buffer); + DP_free(tile_buffer); + return ok ? offset : 0; +} + +static size_t write_index_layer_group(DP_BuildIndexEntryContext *e, + DP_LayerGroup *lg, DP_LayerProps *lp) +{ + DP_BuildIndexLayerKey key; // There's not gonna be padding here, but + memset(&key, 0, sizeof(key)); // we'll zero it anyway just to make sure. + key.lg = lg; + key.lp = lp; + size_t existing_offset = search_existing_layer(e, &key); + if (existing_offset != 0) { + return existing_offset; + } + + struct DP_BuildIndexLayerProps bilp = to_index_layer_props(lp); + size_t offset = + write_index_layer_list(e, &bilp, DP_layer_group_children_noinc(lg), + DP_layer_props_children_noinc(lp)); + + if (offset != 0) { + DP_BuildIndexLayerMap *entry = DP_malloc_zeroed(sizeof(*entry)); + entry->key.lg = DP_layer_group_incref(lg); + entry->key.lp = DP_layer_props_incref(lp); + entry->offset = offset; + HASH_ADD(hh, e->current.layers, key, sizeof(entry->key), entry); + } + + return offset; +} + +static size_t write_index_layer_list(DP_BuildIndexEntryContext *e, + struct DP_BuildIndexLayerProps *bilp, + DP_LayerList *ll, DP_LayerPropsList *lpl) +{ + int count = DP_layer_list_count(ll); + DP_ASSERT(DP_layer_props_list_count(lpl) == count); + // A buffer to hold the layer count plus each layer's offset. + size_t size = sizeof(uint16_t) + DP_int_to_size(count) * sizeof(uint64_t); + unsigned char *buffer = DP_malloc(size); + DP_write_littleendian_uint16(DP_int_to_uint16(count), buffer); + + for (int i = count - 1; i >= 0; --i) { // Top to bottom. + DP_LayerListEntry *lle = DP_layer_list_at_noinc(ll, i); + DP_LayerProps *lp = DP_layer_props_list_at_noinc(lpl, i); + size_t child_offset; + if (DP_layer_props_children_noinc(lp)) { + DP_LayerGroup *lg = DP_layer_list_entry_group_noinc(lle); + child_offset = write_index_layer_group(e, lg, lp); + } + else { + DP_LayerContent *lc = DP_layer_list_entry_content_noinc(lle); + child_offset = write_index_layer_content(e, lc, lp, false); + } + + if (child_offset != 0) { + size_t ii = DP_int_to_size(count - i - 1); + unsigned char *out = + buffer + sizeof(uint16_t) + ii * sizeof(uint64_t); + DP_write_littleendian_uint64(child_offset, out); + } + else { + DP_free(buffer); + return 0; + } + } + + bool error; + size_t offset = DP_output_tell(e->output, &error); + bool ok = !error && write_index_layer_props(e, bilp) + && DP_output_write(e->output, buffer, size); + DP_free(buffer); + return ok ? offset : 0; +} + +static bool write_index_layers(DP_BuildIndexEntryContext *e) +{ + struct DP_BuildIndexLayerProps bilp = { + 0, 0, {.title = ""}, UINT8_MAX, DP_BLEND_MODE_NORMAL, 0, 0, 0, 1}; + size_t offset = + write_index_layer_list(e, &bilp, DP_canvas_state_layers_noinc(e->cs), + DP_canvas_state_layer_props_noinc(e->cs)); + if (offset != 0) { + e->offset.layers = offset; + return true; + } + else { + return false; + } +} + +static size_t write_index_annotation(DP_BuildIndexEntryContext *e, + DP_Annotation *a) +{ + DP_BuildIndexAnnotationMap *entry; + + HASH_FIND_PTR(e->current.annotations, &a, entry); + if (entry) { + return entry->offset; + } + + HASH_FIND_PTR(e->last->annotations, &a, entry); + if (entry) { + HASH_DEL(e->last->annotations, entry); + HASH_ADD_PTR(e->current.annotations, a, entry); + return entry->offset; + } + + DP_Output *output = e->output; + bool error; + size_t offset = DP_output_tell(output, &error); + if (error) { + return 0; + } + + size_t text_length; + const char *text = DP_annotation_text(a, &text_length); + bool ok = DP_OUTPUT_WRITE_LITTLEENDIAN( + output, DP_OUTPUT_UINT16(DP_annotation_id(a)), + DP_OUTPUT_INT32(DP_annotation_x(a)), + DP_OUTPUT_INT32(DP_annotation_y(a)), + DP_OUTPUT_INT32(DP_annotation_width(a)), + DP_OUTPUT_INT32(DP_annotation_height(a)), + DP_OUTPUT_UINT32(DP_annotation_background_color(a)), + DP_OUTPUT_UINT8(DP_annotation_valign(a)), + DP_OUTPUT_UINT16(text_length)) + && DP_output_write(output, text, text_length); + if (!ok) { + return 0; + } + + entry = DP_malloc(sizeof(*entry)); + entry->a = DP_annotation_incref(a); + entry->offset = offset; + HASH_ADD_PTR(e->current.annotations, a, entry); + return offset; +} + +static bool write_index_annotations(DP_BuildIndexEntryContext *e) +{ + DP_AnnotationList *al = DP_canvas_state_annotations_noinc(e->cs); + int count = DP_annotation_list_count(al); + size_t size = sizeof(uint16_t) + DP_int_to_size(count) * sizeof(uint64_t); + unsigned char *buffer = DP_malloc(size); + DP_write_littleendian_uint16(DP_int_to_uint16(count), buffer); + + for (int i = 0; i < count; ++i) { + DP_Annotation *a = DP_annotation_list_at_noinc(al, i); + size_t offset = write_index_annotation(e, a); + if (offset != 0) { + unsigned char *out = buffer + sizeof(uint16_t) + + DP_int_to_size(i) * sizeof(uint64_t); + DP_write_littleendian_uint64(offset, out); + } + else { + DP_free(buffer); + return false; + } + } + + e->annotation.buffer = buffer; + e->annotation.size = size; + return true; +} + +static bool write_index_background_tile(DP_BuildIndexEntryContext *e) +{ + DP_Tile *t = DP_canvas_state_background_tile_noinc(e->cs); + size_t offset; + if (maybe_write_index_tile(e, t, &offset)) { + e->offset.background_tile = offset; + return true; + } + else { + return false; + } +} + +static bool write_index_key_frame(DP_Output *output, int frame_index, + DP_KeyFrame *kf) +{ + size_t title_length; + const char *title = DP_key_frame_title(kf, &title_length); + int layers_count; + const DP_KeyFrameLayer *layers = DP_key_frame_layers(kf, &layers_count); + + bool ok = DP_OUTPUT_WRITE_LITTLEENDIAN( + output, DP_OUTPUT_UINT16(layers_count), + DP_OUTPUT_UINT16(frame_index), + DP_OUTPUT_UINT16(DP_key_frame_layer_id(kf)), + DP_OUTPUT_UINT16(title_length)) + && DP_output_write(output, title, title_length); + if (!ok) { + return false; + } + + for (int i = 0; i < layers_count; ++i) { + const DP_KeyFrameLayer *kfl = &layers[i]; + if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, + DP_OUTPUT_UINT16(kfl->layer_id), + DP_OUTPUT_UINT16(kfl->flags))) { + return false; + } + } + + return true; +} + +static bool write_index_track(DP_Output *output, DP_Track *t) +{ + size_t title_length; + const char *title = DP_track_title(t, &title_length); + int key_frame_count = DP_track_key_frame_count(t); + + bool ok = + DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT16(key_frame_count), + DP_OUTPUT_UINT16(DP_track_id(t)), + DP_OUTPUT_UINT16(title_length)) + && DP_output_write(output, title, title_length); + if (!ok) { + return false; + } + + for (int i = 0; i < key_frame_count; ++i) { + int frame_index = DP_track_frame_index_at_noinc(t, i); + DP_KeyFrame *kf = DP_track_key_frame_at_noinc(t, i); + if (!write_index_key_frame(output, frame_index, kf)) { + return false; + } + } + + return true; +} + +static bool write_index_timeline(DP_BuildIndexEntryContext *e) +{ + DP_Timeline *tl = DP_canvas_state_timeline_noinc(e->cs); + if (e->last->timeline.tl == tl) { + e->current.timeline = e->last->timeline; + e->last->timeline.tl = NULL; + return true; + } + + DP_Output *output = e->output; + bool error; + size_t offset = DP_output_tell(output, &error); + if (error) { + return false; + } + + int track_count = DP_timeline_count(tl); + if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT16(track_count))) { + return false; + } + + for (int i = 0; i < track_count; ++i) { + DP_Track *t = DP_timeline_at_noinc(tl, i); + if (!write_index_track(output, t)) { + return false; + } + } + + e->current.timeline.tl = DP_timeline_incref(tl); + e->current.timeline.offset = offset; + return true; +} + +static bool write_index_metadata(DP_BuildIndexEntryContext *e) +{ + DP_DocumentMetadata *dm = DP_canvas_state_metadata_noinc(e->cs); + if (e->last->metadata.dm == dm) { + e->current.metadata = e->last->metadata; + e->last->metadata.dm = NULL; + return true; + } + + DP_Output *output = e->output; + bool error; + size_t offset = DP_output_tell(output, &error); + if (error) { + return false; + } + + if (!DP_OUTPUT_WRITE_LITTLEENDIAN( + output, DP_OUTPUT_INT32(DP_document_metadata_dpix(dm)), + DP_OUTPUT_INT32(DP_document_metadata_dpiy(dm)), + DP_OUTPUT_INT32(DP_document_metadata_framerate(dm)), + DP_OUTPUT_INT32(DP_document_metadata_frame_count(dm)))) { + return false; + } + + e->current.metadata.dm = DP_document_metadata_incref(dm); + e->current.metadata.offset = offset; + return true; +} + +static bool write_index_canvas_state(DP_BuildIndexEntryContext *e) +{ + DP_Output *output = e->output; + bool error; + size_t offset = DP_output_tell(output, &error); + if (error) { + return false; + } + + DP_debug("Write canvas state at offset %zu", offset); + DP_CanvasState *cs = e->cs; + if (!DP_OUTPUT_WRITE_LITTLEENDIAN( + output, DP_OUTPUT_UINT32(DP_canvas_state_width(cs)), + DP_OUTPUT_UINT32(DP_canvas_state_height(cs)), + DP_OUTPUT_UINT16(e->message_count), + DP_OUTPUT_UINT64(e->offset.history), + DP_OUTPUT_UINT64(e->offset.background_tile), + DP_OUTPUT_UINT64(e->current.timeline.offset), + DP_OUTPUT_UINT64(e->current.metadata.offset), + DP_OUTPUT_UINT64(e->offset.layers))) { + return false; + } + + if (!DP_output_write(output, e->annotation.buffer, e->annotation.size)) { + return false; + } + + e->offset.snapshot = offset; + return true; +} + +static bool write_index_snapshot(DP_BuildIndexEntryContext *e) +{ + bool ok = write_index_history(e) && write_index_layers(e) + && write_index_annotations(e) && write_index_background_tile(e) + && write_index_timeline(e) && write_index_metadata(e) + && write_index_canvas_state(e); + DP_free(e->annotation.buffer); + return ok; +} + +static bool write_index_thumbnail(DP_BuildIndexEntryContext *e) +{ + DP_Output *output = e->output; + bool error; + size_t thumbnail_offset = DP_output_tell(output, &error); + if (error) { + return false; + } + + DP_Image *img = DP_canvas_state_to_flat_image( + e->cs, DP_FLAT_IMAGE_RENDER_FLAGS, NULL, NULL); + if (!img) { + DP_warn("Error creating index thumbnail: %s", DP_error()); + return true; // Keep going without a thumbnail. + } + + DP_Image *thumb; + if (DP_image_thumbnail(img, e->dc, 256, 256, &thumb)) { + if (thumb) { + DP_image_free(img); + } + else { + thumb = img; + } + } + else { + DP_image_free(img); + DP_warn("Error scaling index thumbnail: %s", DP_error()); + return true; // Keep going without a thumbnail. + } + + if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT32(0))) { + return false; + } + + bool write_ok = DP_image_write_png(thumb, output); + DP_image_free(thumb); + if (!write_ok) { + return false; + } + + size_t end_offset = DP_output_tell(output, &error); + if (error) { + return false; + } + + if (!DP_output_seek(output, thumbnail_offset)) { + return false; + } + + size_t size = end_offset - thumbnail_offset - 4; + if (!DP_OUTPUT_WRITE_LITTLEENDIAN(output, DP_OUTPUT_UINT32(size))) { + return false; + } + + if (!DP_output_seek(output, end_offset)) { + return false; + } + + e->offset.thumbnail = thumbnail_offset; + return true; +} + +static void dispose_index_maps(DP_BuildIndexMaps *maps) +{ + DP_BuildIndexTileMap *tile_entry, *tile_tmp; + HASH_ITER(hh, maps->tiles, tile_entry, tile_tmp) { + HASH_DEL(maps->tiles, tile_entry); + DP_tile_decref(tile_entry->t); + DP_free(tile_entry); + } + + DP_BuildIndexLayerMap *layer_entry, *layer_tmp; + HASH_ITER(hh, maps->layers, layer_entry, layer_tmp) { + HASH_DEL(maps->layers, layer_entry); + if (DP_layer_props_children_noinc(layer_entry->key.lp)) { + DP_layer_group_decref(layer_entry->key.lg); + } + else { + DP_layer_content_decref(layer_entry->key.lc); + } + DP_layer_props_decref(layer_entry->key.lp); + DP_free(layer_entry); + } + + DP_BuildIndexAnnotationMap *annotation_entry, *annotation_tmp; + HASH_ITER(hh, maps->annotations, annotation_entry, annotation_tmp) { + HASH_DEL(maps->annotations, annotation_entry); + DP_annotation_decref(annotation_entry->a); + DP_free(annotation_entry); + } + + DP_document_metadata_decref_nullable(maps->metadata.dm); + DP_timeline_decref_nullable(maps->timeline.tl); +} + +static bool make_index_entry(DP_BuildIndexContext *c, long long message_index, + size_t message_offset) +{ + DP_BuildIndexEntryContext e = {c->output, + c->acls, + c->local_state, + c->ch, + NULL, + c->dc, + {NULL, NULL, NULL, {NULL, 0}, {NULL, 0}}, + &c->last, + 0, + {0, 0, 0, 0, 0}, + {NULL, 0}}; + bool ok = write_index_snapshot(&e) && write_index_thumbnail(&e); + if (!ok) { + dispose_index_maps(&e.current); + return false; + } + + DP_PlayerIndexEntry entry = {message_index, message_offset, + e.offset.snapshot, e.offset.thumbnail}; + DP_VECTOR_PUSH_TYPE(&c->entries, DP_PlayerIndexEntry, entry); + + dispose_index_maps(&c->last); + c->last = e.current; + + return true; +} + +static bool write_index_messages(DP_BuildIndexContext *c) +{ + DP_Player *player = c->player; + DP_AclState *acls = c->acls; + DP_LocalState *ls = c->local_state; + DP_CanvasHistory *ch = c->ch; + DP_DrawContext *dc = c->dc; + int last_percent = 0; + long long last_written_message_index = 0; + + while (true) { + size_t message_offset = DP_player_tell(player); + DP_Message *msg; + DP_PlayerResult result = DP_player_step(player, &msg); + if (result == DP_PLAYER_SUCCESS) { + bool filtered = DP_acl_state_handle(acls, msg, false) + & DP_ACL_STATE_FILTERED_BIT; + if (filtered) { + DP_debug( + "ACL filtered recorded %s message from user %u", + DP_message_type_enum_name_unprefixed(DP_message_type(msg)), + DP_message_context_id(msg)); + } + else { + DP_local_state_handle(ls, dc, msg); + if (DP_message_type_command(DP_message_type(msg))) { + if (!DP_canvas_history_handle(ch, dc, msg)) { + DP_warn("Error handling message in index: %s", + DP_error()); + } + + long long message_index = c->message_count++; + if (c->should_snapshot_fn(c->user)) { + if (!make_index_entry(c, message_index, + message_offset)) { + return false; + } + last_written_message_index = message_index; + } + } + } + DP_message_decref(msg); + + if (c->progress_fn) { + double progress = DP_player_progress(player); + int percent = DP_double_to_int(progress * 100.0 + 0.5); + if (percent > last_percent) { + last_percent = percent; + c->progress_fn(c->user, percent); + } + } + } + else if (result == DP_PLAYER_ERROR_PARSE) { + DP_warn("Can't index message: %s", DP_error()); + } + else if (result == DP_PLAYER_RECORDING_END) { + break; + } + else { + return false; // Input error, bail out. + } + } + + long long message_index = c->message_count - 1; + if (message_index >= 0 && message_index != last_written_message_index) { + return make_index_entry(c, message_index, DP_player_tell(player)); + } + else { + return true; + } +} + +static bool write_index_entry(DP_BuildIndexContext *c, + DP_PlayerIndexEntry *entry) +{ + return DP_OUTPUT_WRITE_LITTLEENDIAN( + c->output, DP_OUTPUT_UINT32(entry->message_index), + DP_OUTPUT_UINT64(entry->message_offset), + DP_OUTPUT_UINT64(entry->snapshot_offset), + DP_OUTPUT_UINT64(entry->thumbnail_offset)); +} + +static bool write_index_finish(DP_BuildIndexContext *c) +{ + DP_Output *output = c->output; + bool error; + size_t entries_offset = DP_output_tell(output, &error); + if (error) { + return false; + } + + size_t count = c->entries.used; + for (size_t i = 0; i < count; ++i) { + if (!write_index_entry( + c, &DP_VECTOR_AT_TYPE(&c->entries, DP_PlayerIndexEntry, i))) { + return false; + } + } + + if (!DP_output_seek(output, INDEX_MAGIC_LENGTH + INDEX_VERSION_LENGTH)) { + return false; + } + + return DP_OUTPUT_WRITE_LITTLEENDIAN(output, + DP_OUTPUT_UINT32(c->message_count), + DP_OUTPUT_UINT64(entries_offset)); +} + +static bool write_index(DP_BuildIndexContext *c) +{ + return write_index_header(c) && write_index_messages(c) + && write_index_finish(c) && DP_output_flush(c->output); +} + +bool DP_player_index_build(DP_Player *player, DP_DrawContext *dc, + DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, + DP_PlayerIndexProgressFn progress_fn, void *user) +{ + DP_ASSERT(player); + DP_ASSERT(dc); + DP_ASSERT(should_snapshot_fn); + DP_PlayerType type = DP_player_type(player); + if (type == DP_PLAYER_TYPE_DEBUG_DUMP) { + DP_error_set("Can't index a debug dump"); + return false; + } + + const char *recording_path = DP_player_recording_path(player); + if (!recording_path || !DP_player_index_path(player)) { + DP_error_set("Can't index player without a path"); + return false; + } + + DP_Input *input = DP_file_input_new_from_path(recording_path); + if (!input) { + return false; + } + + DP_Player *index_player = DP_player_new(type, recording_path, input, NULL); + if (!index_player) { + return false; + } + else if (!DP_player_compatible(player)) { + DP_error_set("Incompatible recording"); + DP_player_free(player); + return false; + } + + const char *path = DP_player_index_path(player); + DP_Output *output = DP_file_output_save_new_from_path(path); + if (!output) { + DP_player_free(index_player); + return false; + } + + DP_PERF_BEGIN_DETAIL(fn, "index_build", "path=%s", path); + DP_AclState *acls = DP_acl_state_new_playback(); + DP_LocalState *ls = DP_local_state_new(NULL, NULL, NULL); + DP_CanvasHistory *ch = DP_canvas_history_new(NULL, NULL, false, NULL); + DP_BuildIndexContext c = {index_player, + output, + acls, + ls, + ch, + dc, + 0, + DP_VECTOR_NULL, + {NULL, NULL, NULL, {NULL, 0}, {NULL, 0}}, + should_snapshot_fn, + progress_fn, + user}; + DP_VECTOR_INIT_TYPE(&c.entries, DP_PlayerIndexEntry, INITAL_ENTRY_CAPACITY); + bool ok = write_index(&c); + dispose_index_maps(&c.last); + DP_vector_dispose(&c.entries); + DP_canvas_history_free(ch); + DP_local_state_free(ls); + DP_acl_state_free(acls); + DP_output_free(output); + DP_player_free(index_player); + DP_PERF_END(fn); + return ok; +} + + +typedef struct DP_ReadIndexContext { + DP_BufferedInput input; + unsigned int message_count; + size_t index_offset; + DP_Vector entries; +} DP_ReadIndexContext; + +static bool read_index_input(DP_BufferedInput *input, size_t size) +{ + bool error; + size_t read = DP_buffered_input_read(input, size, &error); + if (error) { + return false; + } + else if (read != size) { + DP_error_set("Tried to read %zu byte(s), but got %zu", size, read); + return false; + } + else { + return true; + } +} + +#define READ_INDEX(INPUT, TYPE, OUT) \ + (read_index_input((INPUT), sizeof(TYPE##_t)) \ + && ((OUT) = DP_read_littleendian_##TYPE((INPUT)->buffer), true)) + +#define READ_INDEX_SIZE(INPUT, OUT) \ + (read_index_input((INPUT), sizeof(uint64_t)) \ + && ((OUT) = read_littleendian_size((INPUT)->buffer), true)) + +static size_t read_littleendian_size(const unsigned char *d) +{ + return DP_uint64_to_size(DP_read_littleendian_uint64(d)); +} + +static bool check_index_magic(DP_ReadIndexContext *c) +{ + DP_BufferedInput *input = &c->input; + if (read_index_input(input, INDEX_MAGIC_LENGTH)) { + if (memcmp(input->buffer, INDEX_MAGIC, INDEX_MAGIC_LENGTH) == 0) { + return true; + } + else { + DP_error_set("File does not start with magic '" INDEX_MAGIC "\\0'"); + } + } + return false; +} + +static bool check_index_version(DP_ReadIndexContext *c) +{ + int version; + if (READ_INDEX(&c->input, uint16, version)) { + if (version == INDEX_VERSION) { + return true; + } + else { + DP_error_set("Index version mismatch: expected %d, got %d", + INDEX_VERSION, version); + } + } + return false; +} + +static bool read_index_offset(DP_ReadIndexContext *c) +{ + if (READ_INDEX_SIZE(&c->input, c->index_offset)) { + if (c->index_offset >= INDEX_HEADER_LENGTH) { + return true; + } + else { + DP_error_set("Offset %zu is inside header (incomplete index?)", + c->index_offset); + } + } + return false; +} + +static bool read_index_header(DP_ReadIndexContext *c) +{ + DP_BufferedInput *input = &c->input; + return check_index_magic(c) && check_index_version(c) + && READ_INDEX(input, uint32, c->message_count) && read_index_offset(c); +} + +#define ENTRY_SIZE (sizeof(uint32_t) + sizeof(uint64_t) * (size_t)3) + +static bool read_index_entries(DP_ReadIndexContext *c) +{ + if (!DP_buffered_input_seek(&c->input, c->index_offset)) { + return false; + } + + DP_VECTOR_INIT_TYPE(&c->entries, DP_PlayerIndexEntry, + INITAL_ENTRY_CAPACITY); + + while (true) { + bool error; + size_t read = DP_buffered_input_read(&c->input, ENTRY_SIZE, &error); + if (error) { + return false; + } + else if (read == ENTRY_SIZE) { + DP_PlayerIndexEntry entry = { + DP_read_littleendian_uint32(c->input.buffer), + read_littleendian_size(c->input.buffer + 4), + read_littleendian_size(c->input.buffer + 12), + read_littleendian_size(c->input.buffer + 20), + }; + DP_debug("Read index entry %zu with message index %lld, message " + "offset %zu, snapshot offset %zu, thumbnail offset %zu", + c->entries.used, entry.message_index, entry.message_offset, + entry.snapshot_offset, entry.thumbnail_offset); + DP_VECTOR_PUSH_TYPE(&c->entries, DP_PlayerIndexEntry, entry); + } + else if (read == 0) { + DP_debug("Reached end of index entries"); + return true; + } + else { + DP_error_set("Expected index entry of %zu bytes, but got %zu", + ENTRY_SIZE, read); + return false; + } + } +} + +bool DP_player_index_load(DP_Player *player) +{ + DP_ASSERT(player); + if (DP_player_type(player) == DP_PLAYER_TYPE_DEBUG_DUMP) { + DP_error_set("Can't load index of a debug dump"); + return false; + } + + const char *path = DP_player_index_path(player); + if (!path) { + DP_error_set("Can't load index of a player without a path"); + return false; + } + + DP_Input *input = DP_file_input_new_from_path(path); + if (!input) { + return false; + } + + DP_PERF_BEGIN_DETAIL(fn, "index_load", "path=%s", path); + DP_ReadIndexContext c = {DP_buffered_input_init(input), 0, 0, + DP_VECTOR_NULL}; + + bool ok = read_index_header(&c) && read_index_entries(&c); + if (ok) { + DP_player_index_set(player, (DP_PlayerIndex){c.input, c.message_count, + c.entries.elements, + c.entries.used}); + } + else { + DP_vector_dispose(&c.entries); + DP_buffered_input_dispose(&c.input); + } + + DP_PERF_END(fn); + return ok; +} + +static bool check_index(DP_Player *player) +{ + if (DP_player_index_loaded(player)) { + return true; + } + else { + DP_error_set("No index set"); + return false; + } +} + + +typedef struct DP_ReadTileMap { + size_t offset; + DP_Tile *t; + UT_hash_handle hh; +} DP_ReadTileMap; + +typedef struct DP_ReadSnapshotContext { + DP_BufferedInput *input; + DP_DrawContext *dc; + DP_TransientCanvasState *tcs; + DP_ReadTileMap *tiles; + DP_PlayerIndexEntrySnapshot *snapshot; +} DP_ReadSnapshotContext; + +struct DP_ReadSnapshotLayer { + union { + DP_TransientLayerContent *tlc; + DP_TransientLayerGroup *tlg; + }; + DP_TransientLayerProps *tlp; +}; + +static struct DP_ReadSnapshotLayer read_snapshot_layer_null(void) +{ + return (struct DP_ReadSnapshotLayer){{NULL}, NULL}; +} + +static bool read_snapshot_layer_ok(struct DP_ReadSnapshotLayer rsl) +{ + return rsl.tlp != NULL; +} + +static bool read_index_tile_inc(DP_ReadSnapshotContext *c, size_t offset, + DP_Tile **out_tile) +{ + if (offset == 0) { + *out_tile = NULL; + return true; + } + + DP_ReadTileMap *entry; + HASH_FIND(hh, c->tiles, &offset, sizeof(offset), entry); + if (!entry) { + DP_debug("Loading tile from offset %zu", offset); + DP_BufferedInput *input = c->input; + size_t size; + bool ok = DP_buffered_input_seek(input, offset) + && READ_INDEX(input, uint16, size) + && read_index_input(input, size); + if (!ok) { + return false; + } + + DP_Tile *t = DP_tile_new_from_compressed(c->dc, 0, input->buffer, size); + if (!t) { + return false; + } + + entry = DP_malloc(sizeof(*entry)); + entry->offset = offset; + entry->t = t; + HASH_ADD(hh, c->tiles, offset, sizeof(offset), entry); + } + + *out_tile = DP_tile_incref(entry->t); + return true; +} + +static bool read_index_offsets(DP_BufferedInput *input, int count, + size_t **out_offsets) +{ + if (count == 0) { + DP_debug("Read nothing for 0 offsets"); + *out_offsets = NULL; + return true; + } + + size_t scount = DP_int_to_size(count); + size_t size = sizeof(uint64_t) * scount; + DP_debug("Read %zu bytes for %d offset(s)", size, count); + if (!read_index_input(input, size)) { + return false; + } + + size_t *offsets = scount == 0 ? NULL : DP_malloc(sizeof(*offsets) * scount); + for (size_t i = 0, j = 0; i < scount; i += 1, j += sizeof(uint64_t)) { + offsets[i] = read_littleendian_size(input->buffer + j); + } + *out_offsets = offsets; + return true; +} + +static bool read_index_canvas_state(DP_ReadSnapshotContext *c, + unsigned int width, unsigned int height) +{ + if (width > UINT16_MAX || height > UINT16_MAX) { + DP_error_set("Canvas dimensions %ux%u out of bounds", width, height); + return false; + } + c->tcs = DP_transient_canvas_state_new_init(); + DP_transient_canvas_state_width_set(c->tcs, DP_uint_to_int(width)); + DP_transient_canvas_state_height_set(c->tcs, DP_uint_to_int(height)); + return true; +} + +static DP_Annotation *read_index_annotation(DP_BufferedInput *input, + size_t offset) +{ + DP_debug("Read annotation at offset %zu", offset); + int id, x, y, width, height, valign; + uint32_t background_color; + size_t text_length; + bool ok = DP_buffered_input_seek(input, offset) + && READ_INDEX(input, uint16, id) && READ_INDEX(input, int32, x) + && READ_INDEX(input, int32, y) && READ_INDEX(input, int32, width) + && READ_INDEX(input, int32, height) + && READ_INDEX(input, uint32, background_color) + && READ_INDEX(input, uint8, valign) + && READ_INDEX(input, uint16, text_length) + && read_index_input(input, text_length); + if (ok) { + DP_TransientAnnotation *ta = + DP_transient_annotation_new_init(id, x, y, width, height); + DP_transient_annotation_background_color_set(ta, background_color); + DP_transient_annotation_valign_set(ta, valign); + DP_transient_annotation_text_set(ta, (const char *)input->buffer, + text_length); + return DP_transient_annotation_persist(ta); + } + else { + return NULL; + } +} + +static bool read_index_annotations(DP_ReadSnapshotContext *c, int count) +{ + DP_debug("Read %d annotation(s)", count); + DP_BufferedInput *input = c->input; + size_t *offsets; + if (!read_index_offsets(input, count, &offsets)) { + return false; + } + + bool ok = true; + DP_TransientAnnotationList *tal = + DP_transient_canvas_state_transient_annotations(c->tcs, count); + for (int i = 0; i < count; ++i) { + DP_Annotation *a = read_index_annotation(input, offsets[i]); + if (a) { + DP_transient_annotation_list_insert_noinc(tal, a, i); + } + else { + ok = false; + break; + } + } + + DP_free(offsets); + return ok; +} + +static bool read_index_background_tile(DP_ReadSnapshotContext *c, size_t offset) +{ + DP_debug("Read background tile at offset %zu", offset); + DP_Tile *t; + if (read_index_tile_inc(c, offset, &t)) { + DP_transient_canvas_state_background_tile_set_noinc(c->tcs, t, + DP_tile_opaque(t)); + return true; + } + else { + return false; + } +} + +static DP_TransientKeyFrame *read_index_key_frame(DP_ReadSnapshotContext *c, + int *out_frame_index) +{ + DP_BufferedInput *input = c->input; + int layers_count, layer_id; + size_t title_length; + bool ok = READ_INDEX(input, uint16, layers_count) + && READ_INDEX(input, uint16, *out_frame_index) + && READ_INDEX(input, uint16, layer_id) + && READ_INDEX(input, uint16, title_length) + && read_index_input(input, title_length); + if (!ok) { + return false; + } + + DP_TransientKeyFrame *tkf = + DP_transient_key_frame_new_init(layer_id, layers_count); + DP_transient_key_frame_title_set(tkf, (const char *)input->buffer, + title_length); + for (int i = 0; i < layers_count; ++i) { + DP_KeyFrameLayer kfl; + ok = READ_INDEX(input, uint16, kfl.layer_id) + && READ_INDEX(input, uint16, kfl.flags); + if (!ok) { + DP_transient_key_frame_decref(tkf); + return false; + } + DP_transient_key_frame_layer_set(tkf, kfl, i); + } + + return tkf; +} + +static DP_TransientTrack *read_index_track(DP_ReadSnapshotContext *c) +{ + DP_BufferedInput *input = c->input; + int key_frame_count, track_id; + size_t title_length; + bool ok = READ_INDEX(input, uint16, key_frame_count) + && READ_INDEX(input, uint16, track_id) + && READ_INDEX(input, uint16, title_length) + && read_index_input(input, title_length); + if (!ok) { + return false; + } + + DP_TransientTrack *tt = DP_transient_track_new_init(key_frame_count); + DP_transient_track_id_set(tt, track_id); + DP_transient_track_title_set(tt, (const char *)input->buffer, title_length); + for (int i = 0; i < key_frame_count; ++i) { + int frame_index; + DP_TransientKeyFrame *tkf = read_index_key_frame(c, &frame_index); + if (!tkf) { + DP_transient_track_decref(tt); + return false; + } + DP_transient_track_set_transient_noinc(tt, frame_index, tkf, i); + } + + return tt; +} + +static bool read_index_timeline(DP_ReadSnapshotContext *c, size_t offset) +{ + DP_debug("Read timeline at offset %zu", offset); + DP_BufferedInput *input = c->input; + int track_count; + bool ok = DP_buffered_input_seek(input, offset) + && READ_INDEX(input, uint16, track_count); + if (!ok) { + return false; + } + + DP_TransientTimeline *ttl = + DP_transient_canvas_state_transient_timeline(c->tcs, track_count); + DP_debug("Read %d timeline track(s)", track_count); + for (int i = 0; i < track_count; ++i) { + DP_TransientTrack *tt = read_index_track(c); + if (!tt) { + return false; + } + DP_transient_timeline_set_transient_noinc(ttl, tt, i); + } + return true; +} + +static bool read_index_metadata(DP_ReadSnapshotContext *c, size_t offset) +{ + DP_debug("Read document metadata at offset %zu", offset); + DP_BufferedInput *input = c->input; + int dpix, dpiy, framerate, frame_count; + bool ok = DP_buffered_input_seek(input, offset) + && READ_INDEX(input, int32, dpix) && READ_INDEX(input, int32, dpiy) + && READ_INDEX(input, int32, framerate) + && READ_INDEX(input, int32, frame_count); + if (ok) { + DP_TransientDocumentMetadata *tdm = + DP_transient_canvas_state_transient_metadata(c->tcs); + DP_transient_document_metadata_dpix_set(tdm, dpix); + DP_transient_document_metadata_dpiy_set(tdm, dpiy); + DP_transient_document_metadata_framerate_set(tdm, framerate); + DP_transient_document_metadata_frame_count_set(tdm, frame_count); + return true; + } + else { + return false; + } +} + +static bool read_index_layer_title(DP_BufferedInput *input, size_t title_length, + char **out_title) +{ + if (read_index_input(input, title_length)) { + char *title = DP_malloc(title_length); + memcpy(title, input->buffer, title_length); + *out_title = title; + return true; + } + else { + return false; + } +} + +static DP_TransientLayerProps * +to_transient_layer_props(struct DP_BuildIndexLayerProps *bilp, + DP_TransientLayerPropsList *child_tlpl) +{ + DP_TransientLayerProps *tlp = + DP_transient_layer_props_new_init_with_transient_children_noinc( + bilp->layer_id, child_tlpl); + DP_transient_layer_props_title_set(tlp, bilp->title, bilp->title_length); + DP_transient_layer_props_opacity_set(tlp, DP_channel8_to_15(bilp->opacity)); + if (DP_blend_mode_valid_for_layer(bilp->blend_mode)) { + DP_transient_layer_props_blend_mode_set(tlp, bilp->blend_mode); + } + DP_transient_layer_props_hidden_set(tlp, bilp->hidden != 0); + DP_transient_layer_props_censored_set(tlp, bilp->censored != 0); + DP_transient_layer_props_isolated_set(tlp, bilp->isolated != 0); + return tlp; +} + +#define READ_INDEX_LAYER_ALLOW_CONTENT (1 << 0) +#define READ_INDEX_LAYER_ALLOW_GROUP (1 << 1) +#define READ_INDEX_LAYER_ALLOW_ANY \ + (READ_INDEX_LAYER_ALLOW_CONTENT | READ_INDEX_LAYER_ALLOW_GROUP) +#define READ_INDEX_LAYER_SUBLAYER (1 << 2) + +static struct DP_ReadSnapshotLayer +read_index_layer(DP_ReadSnapshotContext *c, size_t offset, unsigned int flags); + +static struct DP_ReadSnapshotLayer +read_index_layer_content(DP_ReadSnapshotContext *c, + struct DP_BuildIndexLayerProps *bilp, + int sublayer_count, size_t *sublayer_offsets) +{ + DP_BufferedInput *input = c->input; + int width = DP_transient_canvas_state_width(c->tcs); + int height = DP_transient_canvas_state_height(c->tcs); + int tile_total = DP_tile_total_round(width, height); + + size_t *tile_offsets; + if (!read_index_offsets(input, tile_total, &tile_offsets)) { + return read_snapshot_layer_null(); + } + + DP_TransientLayerList *sub_tll = + DP_transient_layer_list_new_init(sublayer_count); + DP_TransientLayerPropsList *sub_tlpl = + DP_transient_layer_props_list_new_init(sublayer_count); + for (int i = 0; i < sublayer_count; ++i) { + struct DP_ReadSnapshotLayer rsl = read_index_layer( + c, sublayer_offsets[i], + READ_INDEX_LAYER_ALLOW_CONTENT | READ_INDEX_LAYER_SUBLAYER); + if (read_snapshot_layer_ok(rsl)) { + // Layers are stored inverted, so prepend the new ones at index 0. + DP_transient_layer_list_insert_transient_content_noinc(sub_tll, + rsl.tlc, 0); + DP_transient_layer_props_list_insert_transient_noinc(sub_tlpl, + rsl.tlp, 0); + } + else { + DP_transient_layer_props_list_decref(sub_tlpl); + DP_transient_layer_list_decref(sub_tll); + DP_free(tile_offsets); + return read_snapshot_layer_null(); + } + } + + DP_TransientLayerContent *tlc = + DP_transient_layer_content_new_init_with_transient_sublayers_noinc( + width, height, NULL, sub_tll, sub_tlpl); + + for (int i = 0; i < tile_total; ++i) { + DP_Tile *t; + if (read_index_tile_inc(c, tile_offsets[i], &t)) { + DP_transient_layer_content_tile_set_noinc(tlc, t, i); + } + else { + DP_transient_layer_content_decref(tlc); + DP_free(tile_offsets); + return read_snapshot_layer_null(); + } + } + DP_free(tile_offsets); + + DP_TransientLayerProps *tlp = to_transient_layer_props(bilp, NULL); + return (struct DP_ReadSnapshotLayer){{.tlc = tlc}, tlp}; +} + +static struct DP_ReadSnapshotLayer +read_index_layer_group(DP_ReadSnapshotContext *c, + struct DP_BuildIndexLayerProps *bilp, int child_count, + size_t *child_offsets) +{ + DP_TransientLayerList *child_tll = + DP_transient_layer_list_new_init(child_count); + DP_TransientLayerPropsList *child_tlpl = + DP_transient_layer_props_list_new_init(child_count); + for (int i = 0; i < child_count; ++i) { + struct DP_ReadSnapshotLayer rsl = + read_index_layer(c, child_offsets[i], READ_INDEX_LAYER_ALLOW_ANY); + if (read_snapshot_layer_ok(rsl)) { + // Layers are stored inverted, so prepend the new ones at index 0. + if (DP_transient_layer_props_children_noinc(rsl.tlp)) { + DP_transient_layer_list_insert_transient_group_noinc( + child_tll, rsl.tlg, 0); + } + else { + DP_transient_layer_list_insert_transient_content_noinc( + child_tll, rsl.tlc, 0); + } + DP_transient_layer_props_list_insert_transient_noinc(child_tlpl, + rsl.tlp, 0); + } + else { + DP_transient_layer_props_list_decref(child_tlpl); + DP_transient_layer_list_decref(child_tll); + return read_snapshot_layer_null(); + } + } + + DP_TransientLayerGroup *tlg = + DP_transient_layer_group_new_init_with_transient_children_noinc( + DP_transient_canvas_state_width(c->tcs), + DP_transient_canvas_state_height(c->tcs), child_tll); + DP_TransientLayerProps *tlp = to_transient_layer_props(bilp, child_tlpl); + return (struct DP_ReadSnapshotLayer){{.tlg = tlg}, tlp}; +} + +static struct DP_ReadSnapshotLayer +read_index_layer(DP_ReadSnapshotContext *c, size_t offset, unsigned int flags) +{ + DP_debug("Read layer at offset %zu", offset); + DP_BufferedInput *input = c->input; + struct DP_BuildIndexLayerProps bilp = {0}; + int child_count; + size_t *child_offsets = NULL; + bool ok = DP_buffered_input_seek(input, offset) + && READ_INDEX(input, uint16, bilp.layer_id) + && READ_INDEX(input, uint16, bilp.title_length) + && read_index_layer_title(input, bilp.title_length, &bilp.buffer) + && READ_INDEX(input, uint8, bilp.opacity) + && READ_INDEX(input, uint8, bilp.blend_mode) + && READ_INDEX(input, uint8, bilp.hidden) + && READ_INDEX(input, uint8, bilp.censored) + && READ_INDEX(input, uint8, bilp.isolated) + && READ_INDEX(input, uint8, bilp.group) + && READ_INDEX(input, uint16, child_count) + && read_index_offsets(input, child_count, &child_offsets); + + struct DP_ReadSnapshotLayer rsl = read_snapshot_layer_null(); + if (ok) { + if (bilp.group) { + if (flags & READ_INDEX_LAYER_ALLOW_GROUP) { + rsl = read_index_layer_group(c, &bilp, child_count, + child_offsets); + } + else { + DP_error_set("Expected layer content, but got layer group"); + } + } + else if (flags & READ_INDEX_LAYER_ALLOW_CONTENT) { + bool sublayer = flags & READ_INDEX_LAYER_SUBLAYER; + rsl = read_index_layer_content(c, &bilp, sublayer ? 0 : child_count, + sublayer ? NULL : child_offsets); + } + else { + DP_error_set("Expected layer group, but got layer content"); + } + } + + DP_free(child_offsets); + DP_free(bilp.buffer); + return rsl; +} + +static bool read_index_layers(DP_ReadSnapshotContext *c, size_t offset) +{ + DP_debug("Read layers at offset %zu", offset); + struct DP_ReadSnapshotLayer rsl = + read_index_layer(c, offset, READ_INDEX_LAYER_ALLOW_GROUP); + if (read_snapshot_layer_ok(rsl)) { + DP_transient_canvas_state_layers_set_inc( + c->tcs, DP_transient_layer_group_children_noinc(rsl.tlg)); + DP_transient_canvas_state_layer_props_set_inc( + c->tcs, DP_transient_layer_props_children_noinc(rsl.tlp)); + DP_transient_layer_group_decref(rsl.tlg); + DP_transient_layer_props_decref(rsl.tlp); + return true; + } + else { + return false; + } +} + +static bool read_index_history(DP_ReadSnapshotContext *c, size_t offset, + int message_count) +{ + DP_BufferedInput *input = c->input; + if (!DP_buffered_input_seek(input, offset)) { + return false; + } + + DP_PlayerIndexEntrySnapshot *snapshot = DP_malloc(DP_FLEX_SIZEOF( + DP_PlayerIndexEntrySnapshot, messages, DP_int_to_size(message_count))); + snapshot->cs = NULL; + snapshot->message_count = message_count; + for (int i = 0; i < message_count; ++i) { + snapshot->messages[i] = NULL; + } + c->snapshot = snapshot; + + for (int i = 0; i < message_count; ++i) { + size_t length; + bool ok = READ_INDEX(input, uint16, length) + && read_index_input(input, length); + if (!ok) { + return false; + } + + DP_Message *msg = DP_message_deserialize_length( + input->buffer, length, length < 2 ? 0 : length - 2, true); + if (msg) { + snapshot->messages[i] = msg; + } + else { + DP_warn("Error deserializing history message %d", i); + } + } + + return true; +} + +static bool read_index_snapshot(DP_ReadSnapshotContext *c) +{ + DP_BufferedInput *input = c->input; + uint32_t width, height; + size_t history_offset, background_tile_offset, timeline_offset, + metadata_offset, layers_offset; + int message_count, annotation_count; + return READ_INDEX(input, uint32, width) && READ_INDEX(input, uint32, height) + && READ_INDEX(input, uint16, message_count) + && READ_INDEX_SIZE(input, history_offset) + && READ_INDEX_SIZE(input, background_tile_offset) + && READ_INDEX_SIZE(input, timeline_offset) + && READ_INDEX_SIZE(input, metadata_offset) + && READ_INDEX_SIZE(input, layers_offset) + && READ_INDEX(input, uint16, annotation_count) + && read_index_canvas_state(c, width, height) + && read_index_annotations(c, annotation_count) + && read_index_background_tile(c, background_tile_offset) + && read_index_timeline(c, timeline_offset) + && read_index_metadata(c, metadata_offset) + && read_index_layers(c, layers_offset) + && read_index_history(c, history_offset, message_count); +} + +DP_PlayerIndexEntrySnapshot * +DP_player_index_entry_load(DP_Player *player, DP_DrawContext *dc, + DP_PlayerIndexEntry entry) +{ + DP_ASSERT(player); + size_t snapshot_offset = entry.snapshot_offset; + DP_debug("Load snapshot from offset %zu", snapshot_offset); + if (snapshot_offset == 0) { + DP_PlayerIndexEntrySnapshot *snapshot = DP_malloc(sizeof(*snapshot)); + snapshot->cs = DP_canvas_state_new(); + snapshot->message_count = 0; + return snapshot; + } + + DP_BufferedInput *input = &DP_player_index(player)->input; + if (!DP_buffered_input_seek(input, snapshot_offset)) { + return NULL; + } + + DP_PERF_BEGIN_DETAIL(fn, "index_entry_load", "offset=%zu", snapshot_offset); + DP_ReadSnapshotContext c = {input, dc, NULL, NULL, NULL}; + bool ok = read_index_snapshot(&c); + + DP_ReadTileMap *tile_entry, *tile_tmp; + HASH_ITER(hh, c.tiles, tile_entry, tile_tmp) { + HASH_DEL(c.tiles, tile_entry); + DP_tile_decref(tile_entry->t); + DP_free(tile_entry); + } + + if (ok) { + DP_transient_canvas_state_layer_routes_reindex(c.tcs, dc); + DP_transient_canvas_state_timeline_cleanup(c.tcs); + c.snapshot->cs = DP_transient_canvas_state_persist(c.tcs); + } + else { + DP_transient_canvas_state_decref_nullable(c.tcs); + DP_player_index_entry_snapshot_free(c.snapshot); + c.snapshot = NULL; + } + + DP_PERF_END(fn); + return c.snapshot; +} + +DP_CanvasState *DP_player_index_entry_snapshot_canvas_state_inc( + DP_PlayerIndexEntrySnapshot *snapshot) +{ + DP_ASSERT(snapshot); + return DP_canvas_state_incref(snapshot->cs); +} + +int DP_player_index_entry_snapshot_message_count( + DP_PlayerIndexEntrySnapshot *snapshot) +{ + DP_ASSERT(snapshot); + return snapshot->message_count; +} + +DP_Message *DP_player_index_entry_snapshot_message_at_inc( + DP_PlayerIndexEntrySnapshot *snapshot, int i) +{ + DP_ASSERT(snapshot); + DP_ASSERT(i >= 0); + DP_ASSERT(i < snapshot->message_count); + return DP_message_incref_nullable(snapshot->messages[i]); +} + +void DP_player_index_entry_snapshot_free(DP_PlayerIndexEntrySnapshot *snapshot) +{ + if (snapshot) { + int message_count = snapshot->message_count; + for (int i = 0; i < message_count; ++i) { + DP_message_decref_nullable(snapshot->messages[i]); + } + DP_canvas_state_decref_nullable(snapshot->cs); + DP_free(snapshot); + } +} + + +unsigned int DP_player_index_message_count(DP_Player *player) +{ + DP_ASSERT(player); + return check_index(player) ? DP_player_index(player)->message_count : 0; +} + +size_t DP_player_index_entry_count(DP_Player *player) +{ + DP_ASSERT(player); + return check_index(player) ? DP_player_index(player)->entry_count : 0; +} + +DP_PlayerIndexEntry DP_player_index_entry_search(DP_Player *player, + long long position, bool after) +{ + DP_ASSERT(player); + if (!check_index(player)) { + return (DP_PlayerIndexEntry){0, 0, 0, 0}; + } + + DP_PlayerIndex *pi = DP_player_index(player); + size_t entry_count = pi->entry_count; + DP_PlayerIndexEntry best_entry = (DP_PlayerIndexEntry){0, 0, 0, 0}; + long long best_message_index = -1; + for (size_t i = 0; i < entry_count; ++i) { + DP_PlayerIndexEntry entry = pi->entries[i]; + long long message_index = entry.message_index; + bool is_best_candidate = after + ? (message_index >= position + && (message_index < best_message_index + || best_message_index == -1)) + : (message_index <= position + && message_index > best_message_index); + if (is_best_candidate) { + best_entry = entry; + best_message_index = message_index; + } + } + // If we didn't find any index, rewind back to the beginning of the + // recording. Which isn't at offset 0, since there's a header to skip. + if (best_message_index == -1) { + best_entry.message_offset = DP_player_body_offset(player); + } + return best_entry; +} + +static void assign_out_error(bool error, bool *out_error) +{ + if (out_error) { + *out_error = error; + } +} + +DP_Image *DP_player_index_thumbnail_at(DP_Player *player, size_t index, + bool *out_error) +{ + DP_ASSERT(player); + if (!check_index(player)) { + assign_out_error(true, out_error); + return NULL; + } + + DP_PlayerIndex *pi = DP_player_index(player); + size_t entry_count = pi->entry_count; + if (index >= entry_count) { + DP_error_set("Entry index %zu beyond count %zu", index, entry_count); + assign_out_error(true, out_error); + return NULL; + } + + size_t thumbnail_offset = pi->entries[index].thumbnail_offset; + if (thumbnail_offset == 0) { + assign_out_error(false, out_error); + return NULL; + } + + DP_BufferedInput *input = &pi->input; + if (!DP_buffered_input_seek(input, thumbnail_offset)) { + assign_out_error(true, out_error); + return NULL; + } + + bool error; + size_t read = DP_buffered_input_read(input, sizeof(uint32_t), &error); + if (error) { + assign_out_error(true, out_error); + return NULL; + } + else if (read != sizeof(uint32_t)) { + DP_error_set("Premature end of thumbnail length"); + assign_out_error(true, out_error); + return NULL; + } + + size_t length = DP_read_littleendian_uint32(input->buffer); + if (length == 0) { + DP_error_set("Thumbnail has zero length"); + assign_out_error(true, out_error); + return NULL; + } + + read = DP_buffered_input_read(input, length, &error); + if (error) { + assign_out_error(true, out_error); + return NULL; + } + else if (read != length) { + DP_error_set("Premature end of thumbnail data"); + assign_out_error(true, out_error); + return NULL; + } + + DP_Input *mem_input = DP_mem_input_new(input->buffer, length, NULL, NULL); + DP_Image *img = DP_image_read_png(mem_input); + DP_input_free(mem_input); + + assign_out_error(img == NULL, out_error); + return img; +} diff --git a/src/drawdance/libimpex/dpimpex/player_index.h b/src/drawdance/libimpex/dpimpex/player_index.h new file mode 100644 index 0000000000..6291ed1994 --- /dev/null +++ b/src/drawdance/libimpex/dpimpex/player_index.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_PLAYER_INDEX_H +#define DPIMPEX_PLAYER_INDEX_H +#include +#include + +typedef struct DP_CanvasState DP_CanvasState; +typedef struct DP_DrawContext DP_DrawContext; +typedef struct DP_Image DP_Image; +typedef struct DP_Message DP_Message; +typedef struct DP_Player DP_Player; + + +typedef struct DP_PlayerIndexEntrySnapshot DP_PlayerIndexEntrySnapshot; + +typedef bool (*DP_PlayerIndexShouldSnapshotFn)(void *user); +typedef void (*DP_PlayerIndexProgressFn)(void *user, int percent); + + +bool DP_player_index_build(DP_Player *player, DP_DrawContext *dc, + DP_PlayerIndexShouldSnapshotFn should_snapshot_fn, + DP_PlayerIndexProgressFn progress_fn, void *user); + + +bool DP_player_index_load(DP_Player *player); + +unsigned int DP_player_index_message_count(DP_Player *player); + +size_t DP_player_index_entry_count(DP_Player *player); + +DP_PlayerIndexEntry +DP_player_index_entry_search(DP_Player *player, long long position, bool after); + +DP_PlayerIndexEntrySnapshot * +DP_player_index_entry_load(DP_Player *player, DP_DrawContext *dc, + DP_PlayerIndexEntry entry); + +DP_CanvasState *DP_player_index_entry_snapshot_canvas_state_inc( + DP_PlayerIndexEntrySnapshot *snapshot); + +int DP_player_index_entry_snapshot_message_count( + DP_PlayerIndexEntrySnapshot *snapshot); + +DP_Message *DP_player_index_entry_snapshot_message_at_inc( + DP_PlayerIndexEntrySnapshot *snapshot, int i); + +void DP_player_index_entry_snapshot_free(DP_PlayerIndexEntrySnapshot *snapshot); + +DP_Image *DP_player_index_thumbnail_at(DP_Player *player, size_t index, + bool *out_error); + +#endif diff --git a/src/drawdance/libengine/dpengine/save.c b/src/drawdance/libimpex/dpimpex/save.c similarity index 97% rename from src/drawdance/libengine/dpengine/save.c rename to src/drawdance/libimpex/dpimpex/save.c index 8829a722c6..201cb15232 100644 --- a/src/drawdance/libengine/dpengine/save.c +++ b/src/drawdance/libimpex/dpimpex/save.c @@ -1,43 +1,8 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * -------------------------------------------------------------------- - * - * This code is based on Drawpile, using it under the GNU General Public - * License, version 3. See 3rdparty/licenses/drawpile/COPYING for details. - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include "save.h" -#include "annotation.h" -#include "annotation_list.h" -#include "canvas_state.h" -#include "document_metadata.h" -#include "draw_context.h" -#include "image.h" +#include "image_impex.h" #include "image_png.h" -#include "key_frame.h" -#include "layer_content.h" -#include "layer_group.h" -#include "layer_list.h" -#include "layer_props.h" -#include "layer_props_list.h" #include "save_psd.h" -#include "tile.h" -#include "timeline.h" -#include "track.h" -#include "view_mode.h" #include "zip_archive.h" #include #include @@ -48,6 +13,22 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/drawdance/libengine/dpengine/save.h b/src/drawdance/libimpex/dpimpex/save.h similarity index 58% rename from src/drawdance/libengine/dpengine/save.h rename to src/drawdance/libimpex/dpimpex/save.h index 5bd6c8a982..6ccb01e1e1 100644 --- a/src/drawdance/libengine/dpengine/save.h +++ b/src/drawdance/libimpex/dpimpex/save.h @@ -1,27 +1,8 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * -------------------------------------------------------------------- - * - * This code is based on Drawpile, using it under the GNU General Public - * License, version 3. See 3rdparty/licenses/drawpile/COPYING for details. - */ -#ifndef DPENGINE_SAVE_H -#define DPENGINE_SAVE_H +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_SAVE_H +#define DPIMPEX_SAVE_H #include +#include typedef struct DP_Annotation DP_Annotation; typedef struct DP_CanvasState DP_CanvasState; @@ -41,27 +22,6 @@ typedef bool (*DP_SaveBakeAnnotationFn)(void *user, DP_Annotation *a, unsigned char *out); -typedef enum DP_SaveImageType { - DP_SAVE_IMAGE_UNKNOWN, - DP_SAVE_IMAGE_ORA, - DP_SAVE_IMAGE_PNG, - DP_SAVE_IMAGE_JPEG, - DP_SAVE_IMAGE_PSD, - DP_SAVE_IMAGE_WEBP, -} DP_SaveImageType; - -typedef enum DP_SaveResult { - DP_SAVE_RESULT_SUCCESS, - DP_SAVE_RESULT_BAD_ARGUMENTS, - DP_SAVE_RESULT_UNKNOWN_FORMAT, - DP_SAVE_RESULT_FLATTEN_ERROR, - DP_SAVE_RESULT_OPEN_ERROR, - DP_SAVE_RESULT_WRITE_ERROR, - DP_SAVE_RESULT_INTERNAL_ERROR, - DP_SAVE_RESULT_CANCEL, -} DP_SaveResult; - - DP_SaveImageType DP_save_image_type_guess(const char *path); DP_SaveResult DP_save(DP_CanvasState *cs, DP_DrawContext *dc, diff --git a/src/drawdance/libengine/dpengine/save_psd.c b/src/drawdance/libimpex/dpimpex/save_psd.c similarity index 99% rename from src/drawdance/libengine/dpengine/save_psd.c rename to src/drawdance/libimpex/dpimpex/save_psd.c index 8ed1e79888..7acc15b59b 100644 --- a/src/drawdance/libengine/dpengine/save_psd.c +++ b/src/drawdance/libimpex/dpimpex/save_psd.c @@ -1,18 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "save_psd.h" -#include "canvas_state.h" -#include "draw_context.h" -#include "layer_content.h" -#include "layer_group.h" -#include "layer_list.h" -#include "layer_props.h" -#include "layer_props_list.h" -#include "pixels.h" #include "utf16be.h" #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/drawdance/libengine/dpengine/save_psd.h b/src/drawdance/libimpex/dpimpex/save_psd.h similarity index 84% rename from src/drawdance/libengine/dpengine/save_psd.h rename to src/drawdance/libimpex/dpimpex/save_psd.h index c323df1044..c2b4a922e2 100644 --- a/src/drawdance/libengine/dpengine/save_psd.h +++ b/src/drawdance/libimpex/dpimpex/save_psd.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DPENGINE_SAVE_PSD_H -#define DPENGINE_SAVE_PSD_H +#ifndef DPIMPEX_SAVE_PSD_H +#define DPIMPEX_SAVE_PSD_H #include "save.h" #include diff --git a/src/drawdance/libengine/dpengine/save_video.c b/src/drawdance/libimpex/dpimpex/save_video.c similarity index 99% rename from src/drawdance/libengine/dpengine/save_video.c rename to src/drawdance/libimpex/dpimpex/save_video.c index 5dcdda67a4..c3db14d768 100644 --- a/src/drawdance/libengine/dpengine/save_video.c +++ b/src/drawdance/libimpex/dpimpex/save_video.c @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "save_video.h" -#include "canvas_state.h" -#include "image.h" #include "save.h" -#include "view_mode.h" #include #include #include #include +#include +#include +#include #include #include #include diff --git a/src/drawdance/libengine/dpengine/save_video.h b/src/drawdance/libimpex/dpimpex/save_video.h similarity index 92% rename from src/drawdance/libengine/dpengine/save_video.h rename to src/drawdance/libimpex/dpimpex/save_video.h index af29935a3b..6ae8af8a49 100644 --- a/src/drawdance/libengine/dpengine/save_video.h +++ b/src/drawdance/libimpex/dpimpex/save_video.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DPENGINE_SAVE_VIDEO_H -#define DPENGINE_SAVE_VIDEO_H +#ifndef DPIMPEX_SAVE_VIDEO_H +#define DPIMPEX_SAVE_VIDEO_H #include "save.h" #include diff --git a/src/drawdance/libengine/dpengine/utf16be.h b/src/drawdance/libimpex/dpimpex/utf16be.h similarity index 89% rename from src/drawdance/libengine/dpengine/utf16be.h rename to src/drawdance/libimpex/dpimpex/utf16be.h index 1d8320311d..5d359e699a 100644 --- a/src/drawdance/libengine/dpengine/utf16be.h +++ b/src/drawdance/libimpex/dpimpex/utf16be.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DPENGINE_UTF16BE_H -#define DPENGINE_UTF16BE_H +#ifndef DPIMPEX_UTF16BE_H +#define DPIMPEX_UTF16BE_H #include // Callback gives the size in bytes and is not guaranteed to be 16-bit aligned! diff --git a/src/drawdance/libengine/dpengine/utf16be_qt.cpp b/src/drawdance/libimpex/dpimpex/utf16be_qt.cpp similarity index 100% rename from src/drawdance/libengine/dpengine/utf16be_qt.cpp rename to src/drawdance/libimpex/dpimpex/utf16be_qt.cpp diff --git a/src/drawdance/libengine/dpengine/xml_stream.h b/src/drawdance/libimpex/dpimpex/xml_stream.h similarity index 60% rename from src/drawdance/libengine/dpengine/xml_stream.h rename to src/drawdance/libimpex/dpimpex/xml_stream.h index b4d3d505a7..703fbb0918 100644 --- a/src/drawdance/libengine/dpengine/xml_stream.h +++ b/src/drawdance/libimpex/dpimpex/xml_stream.h @@ -1,21 +1,6 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef DPENGINE_XML_STREAM -#define DPENGINE_XML_STREAM +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_XML_STREAM +#define DPIMPEX_XML_STREAM #include diff --git a/src/drawdance/libengine/dpengine/xml_stream_qt.cpp b/src/drawdance/libimpex/dpimpex/xml_stream_qt.cpp similarity index 89% rename from src/drawdance/libengine/dpengine/xml_stream_qt.cpp rename to src/drawdance/libimpex/dpimpex/xml_stream_qt.cpp index 65e543cbc4..46d1465163 100644 --- a/src/drawdance/libengine/dpengine/xml_stream_qt.cpp +++ b/src/drawdance/libimpex/dpimpex/xml_stream_qt.cpp @@ -1,19 +1,4 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later extern "C" { #include "xml_stream.h" #include diff --git a/src/drawdance/libengine/dpengine/zip_archive.h b/src/drawdance/libimpex/dpimpex/zip_archive.h similarity index 56% rename from src/drawdance/libengine/dpengine/zip_archive.h rename to src/drawdance/libimpex/dpimpex/zip_archive.h index 3a2b18e6d7..832428482d 100644 --- a/src/drawdance/libengine/dpengine/zip_archive.h +++ b/src/drawdance/libimpex/dpimpex/zip_archive.h @@ -1,21 +1,6 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef DPENGINE_ZIP_ARCHIVE_H -#define DPENGINE_ZIP_ARCHIVE_H +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPIMPEX_ZIP_ARCHIVE_H +#define DPIMPEX_ZIP_ARCHIVE_H #include typedef struct DP_ZipReader DP_ZipReader; diff --git a/src/drawdance/libengine/dpengine/zip_archive_karchive.cpp b/src/drawdance/libimpex/dpimpex/zip_archive_karchive.cpp similarity index 83% rename from src/drawdance/libengine/dpengine/zip_archive_karchive.cpp rename to src/drawdance/libimpex/dpimpex/zip_archive_karchive.cpp index 2143cbd4db..33f82fa181 100644 --- a/src/drawdance/libengine/dpengine/zip_archive_karchive.cpp +++ b/src/drawdance/libimpex/dpimpex/zip_archive_karchive.cpp @@ -1,19 +1,4 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later extern "C" { #include "zip_archive.h" #include diff --git a/src/drawdance/libengine/dpengine/zip_archive_libzip.c b/src/drawdance/libimpex/dpimpex/zip_archive_libzip.c similarity index 88% rename from src/drawdance/libengine/dpengine/zip_archive_libzip.c rename to src/drawdance/libimpex/dpimpex/zip_archive_libzip.c index ee089826f0..6e9909eee2 100644 --- a/src/drawdance/libengine/dpengine/zip_archive_libzip.c +++ b/src/drawdance/libimpex/dpimpex/zip_archive_libzip.c @@ -1,19 +1,4 @@ -/* - * Copyright (C) 2022 askmeaboufoom - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include "zip_archive.h" #include #include diff --git a/src/drawdance/libengine/test/dptest/dptest_engine.c b/src/drawdance/libimpex/test/dptest/dptest_impex.c similarity index 76% rename from src/drawdance/libengine/test/dptest/dptest_engine.c rename to src/drawdance/libimpex/test/dptest/dptest_impex.c index 18d87f8bca..cef6fd6380 100644 --- a/src/drawdance/libengine/test/dptest/dptest_engine.c +++ b/src/drawdance/libimpex/test/dptest/dptest_impex.c @@ -1,27 +1,8 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "dptest_engine.h" +// SPDX-License-Identifier: GPL-3.0-or-later +#include "dptest_impex.h" #include #include +#include #include diff --git a/src/drawdance/libimpex/test/dptest/dptest_impex.h b/src/drawdance/libimpex/test/dptest/dptest_impex.h new file mode 100644 index 0000000000..d521beab80 --- /dev/null +++ b/src/drawdance/libimpex/test/dptest/dptest_impex.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef DPTEST_DPTEST_IMPEX_H +#define DPTEST_DPTEST_IMPEX_H +#include + +typedef struct DP_Image DP_Image; + + +bool DP_test_image_eq_ok(DP_TestContext *T, const char *file, int line, + const char *sa, const char *sb, DP_Image *a, + DP_Image *b, const char *fmt, ...) DP_FORMAT(8, 9); + +bool DP_test_image_file_eq_ok(DP_TestContext *T, const char *file, int line, + const char *sa, const char *sb, const char *a, + const char *b, const char *fmt, ...) + DP_FORMAT(8, 9); + + +#define IMAGE_EQ_OK(A, B, ...) \ + DP_test_image_eq_ok(TEST_ARGS, __FILE__, __LINE__, #A, #B, (A), (B), \ + __VA_ARGS__) + +#define IMAGE_FILE_EQ_OK(A, B, ...) \ + DP_test_image_file_eq_ok(TEST_ARGS, __FILE__, __LINE__, #A, #B, (A), (B), \ + __VA_ARGS__) + + +#endif diff --git a/src/drawdance/libengine/test/image_thumbnail.c b/src/drawdance/libimpex/test/image_thumbnail.c similarity index 78% rename from src/drawdance/libengine/test/image_thumbnail.c rename to src/drawdance/libimpex/test/image_thumbnail.c index c6e232e064..f3063d8d58 100644 --- a/src/drawdance/libengine/test/image_thumbnail.c +++ b/src/drawdance/libimpex/test/image_thumbnail.c @@ -1,29 +1,10 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include -#include +#include +#include static DP_Image *read_image(TEST_PARAMS, const char *path) diff --git a/src/drawdance/libengine/test/resize_image.c b/src/drawdance/libimpex/test/resize_image.c similarity index 63% rename from src/drawdance/libengine/test/resize_image.c rename to src/drawdance/libimpex/test/resize_image.c index bd39dc5628..bd848de03f 100644 --- a/src/drawdance/libengine/test/resize_image.c +++ b/src/drawdance/libimpex/test/resize_image.c @@ -1,31 +1,12 @@ -/* - * Copyright (c) 2022 askmeaboutloom - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-License-Identifier: GPL-3.0-or-later #include "resize_image.h" #include #include #include #include +#include #include -#include +#include static DP_Image *generate_base_image(void) diff --git a/src/drawdance/libimpex/test/resize_image.h b/src/drawdance/libimpex/test/resize_image.h new file mode 100644 index 0000000000..6c942bfe9f --- /dev/null +++ b/src/drawdance/libimpex/test/resize_image.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#define WIDTH 64 +#define HEIGHT 32 + +typedef struct ResizeTestCase { + const char *name; + int x, y, dw, dh; +} ResizeTestCase; + +ResizeTestCase resize_test_cases[] = { + {"same", 0, 0, 0, 0}, + {"negative_x", -20, 0, 0, 0}, + {"positive_x", 20, 0, 0, 0}, + {"negative_y", 0, -10, 0, 0}, + {"positive_y", 0, 10, 0, 0}, + {"smaller_width", 0, 0, -16, 0}, + {"larger_width", 0, 0, 16, 0}, + {"smaller_height", 0, 0, 0, -8}, + {"larger_height", 0, 10, 0, 8}, + {"everything", -20, 10, 50, -12}, + {"out_of_bounds", -WIDTH, -HEIGHT, 0, 0}, +}; diff --git a/src/drawdance/rust/bindings.rs b/src/drawdance/rust/bindings.rs index 4324b1327f..4ab9d975bd 100644 --- a/src/drawdance/rust/bindings.rs +++ b/src/drawdance/rust/bindings.rs @@ -33,8 +33,6 @@ pub const DP_USER_CURSOR_FLAG_PEN_UP: u32 = 4; pub const DP_USER_CURSOR_FLAG_PEN_DOWN: u32 = 8; pub const DP_CANVAS_HISTORY_UNDO_DEPTH_MIN: u32 = 3; pub const DP_CANVAS_HISTORY_UNDO_DEPTH_MAX: u32 = 255; -pub const DP_LOAD_FLAG_NONE: u32 = 0; -pub const DP_LOAD_FLAG_SINGLE_THREAD: u32 = 1; pub const DP_PREVIEW_BASE_SUBLAYER_ID: i32 = -100; pub const DP_PREVIEW_TRANSFORM_COUNT: u32 = 16; pub const DP_PAINT_ENGINE_FILTER_MESSAGE_FLAG_NO_TIME: u32 = 1; @@ -2333,13 +2331,6 @@ extern "C" { extern "C" { pub fn DP_image_guess(buf: *const ::std::os::raw::c_uchar, size: usize) -> DP_ImageFileType; } -extern "C" { - pub fn DP_image_new_from_file( - input: *mut DP_Input, - type_: DP_ImageFileType, - out_type: *mut DP_ImageFileType, - ) -> *mut DP_Image; -} extern "C" { pub fn DP_image_new_from_compressed( width: ::std::os::raw::c_int, @@ -2449,21 +2440,6 @@ extern "C" { in_out_last_diameter: *mut ::std::os::raw::c_int, ) -> DP_UPixelFloat; } -extern "C" { - pub fn DP_image_read_png(input: *mut DP_Input) -> *mut DP_Image; -} -extern "C" { - pub fn DP_image_read_jpeg(input: *mut DP_Input) -> *mut DP_Image; -} -extern "C" { - pub fn DP_image_write_png(img: *mut DP_Image, output: *mut DP_Output) -> bool; -} -extern "C" { - pub fn DP_image_write_jpeg(img: *mut DP_Image, output: *mut DP_Output) -> bool; -} -extern "C" { - pub fn DP_image_write_webp(img: *mut DP_Image, output: *mut DP_Output) -> bool; -} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct DP_KeyFrame { @@ -5922,172 +5898,6 @@ extern "C" { onion_skin: bool, ) -> *mut DP_Message; } -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct DP_SaveFormat { - pub title: *const ::std::os::raw::c_char, - pub extensions: *mut *const ::std::os::raw::c_char, -} -#[test] -fn bindgen_test_layout_DP_SaveFormat() { - const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); - let ptr = UNINIT.as_ptr(); - assert_eq!( - ::std::mem::size_of::(), - 16usize, - concat!("Size of: ", stringify!(DP_SaveFormat)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(DP_SaveFormat)) - ); - assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).title) as usize - ptr as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(DP_SaveFormat), - "::", - stringify!(title) - ) - ); - assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).extensions) as usize - ptr as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(DP_SaveFormat), - "::", - stringify!(extensions) - ) - ); -} -extern "C" { - pub fn DP_save_supported_formats() -> *const DP_SaveFormat; -} -pub type DP_SaveBakeAnnotationFn = ::std::option::Option< - unsafe extern "C" fn( - user: *mut ::std::os::raw::c_void, - a: *mut DP_Annotation, - out: *mut ::std::os::raw::c_uchar, - ) -> bool, ->; -pub const DP_SAVE_IMAGE_UNKNOWN: DP_SaveImageType = 0; -pub const DP_SAVE_IMAGE_ORA: DP_SaveImageType = 1; -pub const DP_SAVE_IMAGE_PNG: DP_SaveImageType = 2; -pub const DP_SAVE_IMAGE_JPEG: DP_SaveImageType = 3; -pub const DP_SAVE_IMAGE_PSD: DP_SaveImageType = 4; -pub const DP_SAVE_IMAGE_WEBP: DP_SaveImageType = 5; -pub type DP_SaveImageType = ::std::os::raw::c_uint; -pub const DP_SAVE_RESULT_SUCCESS: DP_SaveResult = 0; -pub const DP_SAVE_RESULT_BAD_ARGUMENTS: DP_SaveResult = 1; -pub const DP_SAVE_RESULT_UNKNOWN_FORMAT: DP_SaveResult = 2; -pub const DP_SAVE_RESULT_FLATTEN_ERROR: DP_SaveResult = 3; -pub const DP_SAVE_RESULT_OPEN_ERROR: DP_SaveResult = 4; -pub const DP_SAVE_RESULT_WRITE_ERROR: DP_SaveResult = 5; -pub const DP_SAVE_RESULT_INTERNAL_ERROR: DP_SaveResult = 6; -pub const DP_SAVE_RESULT_CANCEL: DP_SaveResult = 7; -pub type DP_SaveResult = ::std::os::raw::c_uint; -extern "C" { - pub fn DP_save_image_type_guess(path: *const ::std::os::raw::c_char) -> DP_SaveImageType; -} -extern "C" { - pub fn DP_save( - cs: *mut DP_CanvasState, - dc: *mut DP_DrawContext, - type_: DP_SaveImageType, - path: *const ::std::os::raw::c_char, - bake_annotation: DP_SaveBakeAnnotationFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_SaveResult; -} -pub type DP_SaveAnimationProgressFn = ::std::option::Option< - unsafe extern "C" fn(user: *mut ::std::os::raw::c_void, progress: f64) -> bool, ->; -extern "C" { - pub fn DP_save_animation_frames( - cs: *mut DP_CanvasState, - path: *const ::std::os::raw::c_char, - crop: *mut DP_Rect, - start: ::std::os::raw::c_int, - end_inclusive: ::std::os::raw::c_int, - progress_fn: DP_SaveAnimationProgressFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_SaveResult; -} -extern "C" { - pub fn DP_save_animation_zip( - cs: *mut DP_CanvasState, - path: *const ::std::os::raw::c_char, - crop: *mut DP_Rect, - start: ::std::os::raw::c_int, - end_inclusive: ::std::os::raw::c_int, - progress_fn: DP_SaveAnimationProgressFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_SaveResult; -} -extern "C" { - pub fn DP_save_animation_gif( - cs: *mut DP_CanvasState, - path: *const ::std::os::raw::c_char, - crop: *mut DP_Rect, - start: ::std::os::raw::c_int, - end_inclusive: ::std::os::raw::c_int, - framerate: ::std::os::raw::c_int, - progress_fn: DP_SaveAnimationProgressFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_SaveResult; -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct DP_Player { - _unused: [u8; 0], -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct DP_LoadFormat { - pub title: *const ::std::os::raw::c_char, - pub extensions: *mut *const ::std::os::raw::c_char, -} -#[test] -fn bindgen_test_layout_DP_LoadFormat() { - const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); - let ptr = UNINIT.as_ptr(); - assert_eq!( - ::std::mem::size_of::(), - 16usize, - concat!("Size of: ", stringify!(DP_LoadFormat)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(DP_LoadFormat)) - ); - assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).title) as usize - ptr as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(DP_LoadFormat), - "::", - stringify!(title) - ) - ); - assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).extensions) as usize - ptr as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(DP_LoadFormat), - "::", - stringify!(extensions) - ) - ); -} -extern "C" { - pub fn DP_load_supported_formats() -> *const DP_LoadFormat; -} pub const DP_LOAD_RESULT_SUCCESS: DP_LoadResult = 0; pub const DP_LOAD_RESULT_BAD_ARGUMENTS: DP_LoadResult = 1; pub const DP_LOAD_RESULT_UNKNOWN_FORMAT: DP_LoadResult = 2; @@ -6100,57 +5910,17 @@ pub const DP_LOAD_RESULT_UNSUPPORTED_PSD_COLOR_MODE: DP_LoadResult = 8; pub const DP_LOAD_RESULT_IMAGE_TOO_LARGE: DP_LoadResult = 9; pub const DP_LOAD_RESULT_INTERNAL_ERROR: DP_LoadResult = 10; pub type DP_LoadResult = ::std::os::raw::c_uint; -pub type DP_LoadFixedLayerFn = ::std::option::Option< - unsafe extern "C" fn(user: *mut ::std::os::raw::c_void, layer_id: ::std::os::raw::c_int), ->; -extern "C" { - pub fn DP_load_guess(buf: *const ::std::os::raw::c_uchar, size: usize) -> DP_SaveImageType; -} -extern "C" { - pub fn DP_load( - dc: *mut DP_DrawContext, - path: *const ::std::os::raw::c_char, - flat_image_layer_title: *const ::std::os::raw::c_char, - flags: ::std::os::raw::c_uint, - out_result: *mut DP_LoadResult, - out_type: *mut DP_SaveImageType, - ) -> *mut DP_CanvasState; -} -extern "C" { - pub fn DP_load_ora( - dc: *mut DP_DrawContext, - path: *const ::std::os::raw::c_char, - flags: ::std::os::raw::c_uint, - on_fixed_layer: DP_LoadFixedLayerFn, - user: *mut ::std::os::raw::c_void, - out_result: *mut DP_LoadResult, - ) -> *mut DP_CanvasState; -} -extern "C" { - pub fn DP_load_psd( - dc: *mut DP_DrawContext, - input: *mut DP_Input, - out_result: *mut DP_LoadResult, - ) -> *mut DP_CanvasState; -} -extern "C" { - pub fn DP_load_recording( - path: *const ::std::os::raw::c_char, - out_result: *mut DP_LoadResult, - ) -> *mut DP_Player; -} -extern "C" { - pub fn DP_load_debug_dump( - path: *const ::std::os::raw::c_char, - out_result: *mut DP_LoadResult, - ) -> *mut DP_Player; -} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct json_object_t { _unused: [u8; 0], } pub type JSON_Object = json_object_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct DP_Player { + _unused: [u8; 0], +} pub const DP_PLAYER_TYPE_GUESS: DP_PlayerType = 0; pub const DP_PLAYER_TYPE_BINARY: DP_PlayerType = 1; pub const DP_PLAYER_TYPE_TEXT: DP_PlayerType = 2; @@ -6232,137 +6002,147 @@ fn bindgen_test_layout_DP_PlayerIndexEntry() { } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct DP_PlayerIndexEntrySnapshot { - _unused: [u8; 0], -} -pub type DP_PlayerIndexShouldSnapshotFn = - ::std::option::Option bool>; -pub type DP_PlayerIndexProgressFn = ::std::option::Option< - unsafe extern "C" fn(user: *mut ::std::os::raw::c_void, percent: ::std::os::raw::c_int), ->; -extern "C" { - pub fn DP_player_new( - type_: DP_PlayerType, - path_or_null: *const ::std::os::raw::c_char, - input: *mut DP_Input, - out_result: *mut DP_LoadResult, - ) -> *mut DP_Player; -} -extern "C" { - pub fn DP_player_free(player: *mut DP_Player); -} -extern "C" { - pub fn DP_player_type(player: *mut DP_Player) -> DP_PlayerType; -} -extern "C" { - pub fn DP_player_header(player: *mut DP_Player) -> *mut JSON_Value; -} -extern "C" { - pub fn DP_player_compatibility(player: *mut DP_Player) -> DP_PlayerCompatibility; -} -extern "C" { - pub fn DP_player_compatible(player: *mut DP_Player) -> bool; +pub struct DP_PlayerIndex { + pub input: DP_BufferedInput, + pub message_count: ::std::os::raw::c_uint, + pub entries: *mut DP_PlayerIndexEntry, + pub entry_count: usize, } -extern "C" { - pub fn DP_player_acl_override_set(player: *mut DP_Player, override_: bool); -} -extern "C" { - pub fn DP_player_index_loaded(player: *mut DP_Player) -> bool; -} -extern "C" { - pub fn DP_player_tell(player: *mut DP_Player) -> usize; +#[test] +fn bindgen_test_layout_DP_PlayerIndex() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(DP_PlayerIndex)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(DP_PlayerIndex)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).input) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(DP_PlayerIndex), + "::", + stringify!(input) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).message_count) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(DP_PlayerIndex), + "::", + stringify!(message_count) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).entries) as usize - ptr as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(DP_PlayerIndex), + "::", + stringify!(entries) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).entry_count) as usize - ptr as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(DP_PlayerIndex), + "::", + stringify!(entry_count) + ) + ); } extern "C" { - pub fn DP_player_progress(player: *mut DP_Player) -> f64; + pub fn DP_player_new( + type_: DP_PlayerType, + path_or_null: *const ::std::os::raw::c_char, + input: *mut DP_Input, + out_result: *mut DP_LoadResult, + ) -> *mut DP_Player; } extern "C" { - pub fn DP_player_position(player: *mut DP_Player) -> ::std::os::raw::c_longlong; + pub fn DP_player_free(player: *mut DP_Player); } extern "C" { - pub fn DP_player_step(player: *mut DP_Player, out_msg: *mut *mut DP_Message) - -> DP_PlayerResult; + pub fn DP_player_type(player: *mut DP_Player) -> DP_PlayerType; } extern "C" { - pub fn DP_player_step_dump( - player: *mut DP_Player, - out_type: *mut DP_DumpType, - out_count: *mut ::std::os::raw::c_int, - out_msgs: *mut *mut *mut DP_Message, - ) -> DP_PlayerResult; + pub fn DP_player_header(player: *mut DP_Player) -> *mut JSON_Value; } extern "C" { - pub fn DP_player_seek( - player: *mut DP_Player, - position: ::std::os::raw::c_longlong, - offset: usize, - ) -> bool; + pub fn DP_player_compatibility(player: *mut DP_Player) -> DP_PlayerCompatibility; } extern "C" { - pub fn DP_player_rewind(player: *mut DP_Player) -> bool; + pub fn DP_player_compatible(player: *mut DP_Player) -> bool; } extern "C" { - pub fn DP_player_seek_dump( - player: *mut DP_Player, - position: ::std::os::raw::c_longlong, - ) -> bool; + pub fn DP_player_acl_override_set(player: *mut DP_Player, override_: bool); } extern "C" { - pub fn DP_player_index_build( - player: *mut DP_Player, - dc: *mut DP_DrawContext, - should_snapshot_fn: DP_PlayerIndexShouldSnapshotFn, - progress_fn: DP_PlayerIndexProgressFn, - user: *mut ::std::os::raw::c_void, - ) -> bool; + pub fn DP_player_recording_path(player: *mut DP_Player) -> *const ::std::os::raw::c_char; } extern "C" { - pub fn DP_player_index_load(player: *mut DP_Player) -> bool; + pub fn DP_player_index_path(player: *mut DP_Player) -> *const ::std::os::raw::c_char; } extern "C" { - pub fn DP_player_index_message_count(player: *mut DP_Player) -> ::std::os::raw::c_uint; + pub fn DP_player_index_loaded(player: *mut DP_Player) -> bool; } extern "C" { - pub fn DP_player_index_entry_count(player: *mut DP_Player) -> usize; + pub fn DP_player_index(player: *mut DP_Player) -> *mut DP_PlayerIndex; } extern "C" { - pub fn DP_player_index_entry_search( - player: *mut DP_Player, - position: ::std::os::raw::c_longlong, - after: bool, - ) -> DP_PlayerIndexEntry; + pub fn DP_player_index_set(player: *mut DP_Player, index: DP_PlayerIndex); } extern "C" { - pub fn DP_player_index_entry_load( - player: *mut DP_Player, - dc: *mut DP_DrawContext, - entry: DP_PlayerIndexEntry, - ) -> *mut DP_PlayerIndexEntrySnapshot; + pub fn DP_player_tell(player: *mut DP_Player) -> usize; } extern "C" { - pub fn DP_player_index_entry_snapshot_canvas_state_inc( - snapshot: *mut DP_PlayerIndexEntrySnapshot, - ) -> *mut DP_CanvasState; + pub fn DP_player_progress(player: *mut DP_Player) -> f64; } extern "C" { - pub fn DP_player_index_entry_snapshot_message_count( - snapshot: *mut DP_PlayerIndexEntrySnapshot, - ) -> ::std::os::raw::c_int; + pub fn DP_player_position(player: *mut DP_Player) -> ::std::os::raw::c_longlong; } extern "C" { - pub fn DP_player_index_entry_snapshot_message_at_inc( - snapshot: *mut DP_PlayerIndexEntrySnapshot, - i: ::std::os::raw::c_int, - ) -> *mut DP_Message; + pub fn DP_player_step(player: *mut DP_Player, out_msg: *mut *mut DP_Message) + -> DP_PlayerResult; } extern "C" { - pub fn DP_player_index_entry_snapshot_free(snapshot: *mut DP_PlayerIndexEntrySnapshot); + pub fn DP_player_step_dump( + player: *mut DP_Player, + out_type: *mut DP_DumpType, + out_count: *mut ::std::os::raw::c_int, + out_msgs: *mut *mut *mut DP_Message, + ) -> DP_PlayerResult; } extern "C" { - pub fn DP_player_index_thumbnail_at( + pub fn DP_player_seek( player: *mut DP_Player, - index: usize, - out_error: *mut bool, - ) -> *mut DP_Image; + position: ::std::os::raw::c_longlong, + offset: usize, + ) -> bool; +} +extern "C" { + pub fn DP_player_body_offset(player: *mut DP_Player) -> usize; +} +extern "C" { + pub fn DP_player_rewind(player: *mut DP_Player) -> bool; +} +extern "C" { + pub fn DP_player_seek_dump( + player: *mut DP_Player, + position: ::std::os::raw::c_longlong, + ) -> bool; } pub const DP_PREVIEW_CUT: DP_PreviewType = 0; pub const DP_PREVIEW_DABS: DP_PreviewType = 1; @@ -6688,6 +6468,92 @@ pub type DP_PaintEnginePushMessageFn = ::std::option::Option< pub struct DP_PaintEngine { _unused: [u8; 0], } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct DP_PaintEnginePlayback { + pub player: *mut DP_Player, + pub msecs: ::std::os::raw::c_longlong, + pub next_has_time: bool, + pub fn_: DP_PaintEnginePlaybackFn, + pub dump_fn: DP_PaintEngineDumpPlaybackFn, + pub user: *mut ::std::os::raw::c_void, +} +#[test] +fn bindgen_test_layout_DP_PaintEnginePlayback() { + const UNINIT: ::std::mem::MaybeUninit = + ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(DP_PaintEnginePlayback)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(DP_PaintEnginePlayback)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).player) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(DP_PaintEnginePlayback), + "::", + stringify!(player) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).msecs) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(DP_PaintEnginePlayback), + "::", + stringify!(msecs) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).next_has_time) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(DP_PaintEnginePlayback), + "::", + stringify!(next_has_time) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).fn_) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(DP_PaintEnginePlayback), + "::", + stringify!(fn_) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).dump_fn) as usize - ptr as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(DP_PaintEnginePlayback), + "::", + stringify!(dump_fn) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).user) as usize - ptr as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(DP_PaintEnginePlayback), + "::", + stringify!(user) + ) + ); +} extern "C" { pub fn DP_paint_engine_new_inc( paint_dc: *mut DP_DrawContext, @@ -6820,159 +6686,57 @@ extern "C" { pub fn DP_paint_engine_recorder_is_recording(pe: *mut DP_PaintEngine) -> bool; } extern "C" { - pub fn DP_paint_engine_playback_step( - pe: *mut DP_PaintEngine, - steps: ::std::os::raw::c_longlong, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; + pub fn DP_paint_engine_playback(pe: *mut DP_PaintEngine) -> *mut DP_PaintEnginePlayback; } extern "C" { - pub fn DP_paint_engine_playback_skip_by( + pub fn DP_paint_engine_handle_inc( pe: *mut DP_PaintEngine, - dc: *mut DP_DrawContext, - steps: ::std::os::raw::c_longlong, - by_snapshots: bool, - push_message: DP_PaintEnginePushMessageFn, + local: bool, + override_acls: bool, + count: ::std::os::raw::c_int, + msgs: *mut *mut DP_Message, + acls_changed: DP_PaintEngineAclsChangedFn, + laser_trail: DP_PaintEngineLaserTrailFn, + move_pointer: DP_PaintEngineMovePointerFn, user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; + ) -> ::std::os::raw::c_int; } extern "C" { - pub fn DP_paint_engine_playback_jump_to( + pub fn DP_paint_engine_tick( pe: *mut DP_PaintEngine, - dc: *mut DP_DrawContext, - position: ::std::os::raw::c_longlong, - push_message: DP_PaintEnginePushMessageFn, + tile_bounds: DP_Rect, + render_outside_tile_bounds: bool, + catchup: DP_PaintEngineCatchupFn, + reset_lock_changed: DP_PaintEngineResetLockChangedFn, + recorder_state_changed: DP_PaintEngineRecorderStateChangedFn, + layer_props_changed: DP_PaintEngineLayerPropsChangedFn, + annotations_changed: DP_PaintEngineAnnotationsChangedFn, + document_metadata_changed: DP_PaintEngineDocumentMetadataChangedFn, + timeline_changed: DP_PaintEngineTimelineChangedFn, + selections_changed: DP_PaintEngineSelectionsChangedFn, + cursor_moved: DP_PaintEngineCursorMovedFn, + default_layer_set: DP_PaintEngineDefaultLayerSetFn, + undo_depth_limit_set: DP_PaintEngineUndoDepthLimitSetFn, + censored_layer_revealed: DP_PaintEngineCensoredLayerRevealedFn, user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; -} -extern "C" { - pub fn DP_paint_engine_playback_begin(pe: *mut DP_PaintEngine) -> DP_PlayerResult; + ); } extern "C" { - pub fn DP_paint_engine_playback_play( + pub fn DP_paint_engine_render_continuous( pe: *mut DP_PaintEngine, - msecs: ::std::os::raw::c_longlong, - filter_message_or_null: DP_PaintEngineFilterMessageFn, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; + tile_bounds: DP_Rect, + render_outside_tile_bounds: bool, + ); } extern "C" { - pub fn DP_paint_engine_playback_index_build( + pub fn DP_paint_engine_change_bounds( pe: *mut DP_PaintEngine, - dc: *mut DP_DrawContext, - should_snapshot_fn: DP_PlayerIndexShouldSnapshotFn, - progress_fn: DP_PlayerIndexProgressFn, - user: *mut ::std::os::raw::c_void, - ) -> bool; + tile_bounds: DP_Rect, + render_outside_tile_bounds: bool, + ); } extern "C" { - pub fn DP_paint_engine_playback_index_load(pe: *mut DP_PaintEngine) -> bool; -} -extern "C" { - pub fn DP_paint_engine_playback_index_message_count( - pe: *mut DP_PaintEngine, - ) -> ::std::os::raw::c_uint; -} -extern "C" { - pub fn DP_paint_engine_playback_index_entry_count(pe: *mut DP_PaintEngine) -> usize; -} -extern "C" { - pub fn DP_paint_engine_playback_index_thumbnail_at( - pe: *mut DP_PaintEngine, - index: usize, - out_error: *mut bool, - ) -> *mut DP_Image; -} -extern "C" { - pub fn DP_paint_engine_playback_dump_step( - pe: *mut DP_PaintEngine, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; -} -extern "C" { - pub fn DP_paint_engine_playback_dump_jump_previous_reset( - pe: *mut DP_PaintEngine, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; -} -extern "C" { - pub fn DP_paint_engine_playback_dump_jump_next_reset( - pe: *mut DP_PaintEngine, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; -} -extern "C" { - pub fn DP_paint_engine_playback_dump_jump( - pe: *mut DP_PaintEngine, - position: ::std::os::raw::c_longlong, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> DP_PlayerResult; -} -extern "C" { - pub fn DP_paint_engine_playback_flush( - pe: *mut DP_PaintEngine, - push_message: DP_PaintEnginePushMessageFn, - user: *mut ::std::os::raw::c_void, - ) -> bool; -} -extern "C" { - pub fn DP_paint_engine_playback_close(pe: *mut DP_PaintEngine) -> bool; -} -extern "C" { - pub fn DP_paint_engine_handle_inc( - pe: *mut DP_PaintEngine, - local: bool, - override_acls: bool, - count: ::std::os::raw::c_int, - msgs: *mut *mut DP_Message, - acls_changed: DP_PaintEngineAclsChangedFn, - laser_trail: DP_PaintEngineLaserTrailFn, - move_pointer: DP_PaintEngineMovePointerFn, - user: *mut ::std::os::raw::c_void, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn DP_paint_engine_tick( - pe: *mut DP_PaintEngine, - tile_bounds: DP_Rect, - render_outside_tile_bounds: bool, - catchup: DP_PaintEngineCatchupFn, - reset_lock_changed: DP_PaintEngineResetLockChangedFn, - recorder_state_changed: DP_PaintEngineRecorderStateChangedFn, - layer_props_changed: DP_PaintEngineLayerPropsChangedFn, - annotations_changed: DP_PaintEngineAnnotationsChangedFn, - document_metadata_changed: DP_PaintEngineDocumentMetadataChangedFn, - timeline_changed: DP_PaintEngineTimelineChangedFn, - selections_changed: DP_PaintEngineSelectionsChangedFn, - cursor_moved: DP_PaintEngineCursorMovedFn, - default_layer_set: DP_PaintEngineDefaultLayerSetFn, - undo_depth_limit_set: DP_PaintEngineUndoDepthLimitSetFn, - censored_layer_revealed: DP_PaintEngineCensoredLayerRevealedFn, - user: *mut ::std::os::raw::c_void, - ); -} -extern "C" { - pub fn DP_paint_engine_render_continuous( - pe: *mut DP_PaintEngine, - tile_bounds: DP_Rect, - render_outside_tile_bounds: bool, - ); -} -extern "C" { - pub fn DP_paint_engine_change_bounds( - pe: *mut DP_PaintEngine, - tile_bounds: DP_Rect, - render_outside_tile_bounds: bool, - ); -} -extern "C" { - pub fn DP_paint_engine_render_everything(pe: *mut DP_PaintEngine); + pub fn DP_paint_engine_render_everything(pe: *mut DP_PaintEngine); } extern "C" { pub fn DP_paint_engine_preview_cut( @@ -7901,6 +7665,318 @@ extern "C" { extern "C" { pub fn DP_transient_track_delete_at(tt: *mut DP_TransientTrack, index: ::std::os::raw::c_int); } +extern "C" { + pub fn DP_image_new_from_file( + input: *mut DP_Input, + type_: DP_ImageFileType, + out_type: *mut DP_ImageFileType, + ) -> *mut DP_Image; +} +extern "C" { + pub fn DP_image_read_png(input: *mut DP_Input) -> *mut DP_Image; +} +extern "C" { + pub fn DP_image_read_jpeg(input: *mut DP_Input) -> *mut DP_Image; +} +extern "C" { + pub fn DP_image_write_png(img: *mut DP_Image, output: *mut DP_Output) -> bool; +} +extern "C" { + pub fn DP_image_write_jpeg(img: *mut DP_Image, output: *mut DP_Output) -> bool; +} +extern "C" { + pub fn DP_image_write_webp(img: *mut DP_Image, output: *mut DP_Output) -> bool; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct DP_PlayerIndexEntrySnapshot { + _unused: [u8; 0], +} +pub type DP_PlayerIndexShouldSnapshotFn = + ::std::option::Option bool>; +pub type DP_PlayerIndexProgressFn = ::std::option::Option< + unsafe extern "C" fn(user: *mut ::std::os::raw::c_void, percent: ::std::os::raw::c_int), +>; +extern "C" { + pub fn DP_player_index_build( + player: *mut DP_Player, + dc: *mut DP_DrawContext, + should_snapshot_fn: DP_PlayerIndexShouldSnapshotFn, + progress_fn: DP_PlayerIndexProgressFn, + user: *mut ::std::os::raw::c_void, + ) -> bool; +} +extern "C" { + pub fn DP_player_index_load(player: *mut DP_Player) -> bool; +} +extern "C" { + pub fn DP_player_index_message_count(player: *mut DP_Player) -> ::std::os::raw::c_uint; +} +extern "C" { + pub fn DP_player_index_entry_count(player: *mut DP_Player) -> usize; +} +extern "C" { + pub fn DP_player_index_entry_search( + player: *mut DP_Player, + position: ::std::os::raw::c_longlong, + after: bool, + ) -> DP_PlayerIndexEntry; +} +extern "C" { + pub fn DP_player_index_entry_load( + player: *mut DP_Player, + dc: *mut DP_DrawContext, + entry: DP_PlayerIndexEntry, + ) -> *mut DP_PlayerIndexEntrySnapshot; +} +extern "C" { + pub fn DP_player_index_entry_snapshot_canvas_state_inc( + snapshot: *mut DP_PlayerIndexEntrySnapshot, + ) -> *mut DP_CanvasState; +} +extern "C" { + pub fn DP_player_index_entry_snapshot_message_count( + snapshot: *mut DP_PlayerIndexEntrySnapshot, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn DP_player_index_entry_snapshot_message_at_inc( + snapshot: *mut DP_PlayerIndexEntrySnapshot, + i: ::std::os::raw::c_int, + ) -> *mut DP_Message; +} +extern "C" { + pub fn DP_player_index_entry_snapshot_free(snapshot: *mut DP_PlayerIndexEntrySnapshot); +} +extern "C" { + pub fn DP_player_index_thumbnail_at( + player: *mut DP_Player, + index: usize, + out_error: *mut bool, + ) -> *mut DP_Image; +} +extern "C" { + pub fn DP_paint_engine_playback_step( + pe: *mut DP_PaintEngine, + steps: ::std::os::raw::c_longlong, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_skip_by( + pe: *mut DP_PaintEngine, + dc: *mut DP_DrawContext, + steps: ::std::os::raw::c_longlong, + by_snapshots: bool, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_jump_to( + pe: *mut DP_PaintEngine, + dc: *mut DP_DrawContext, + position: ::std::os::raw::c_longlong, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_begin(pe: *mut DP_PaintEngine) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_play( + pe: *mut DP_PaintEngine, + msecs: ::std::os::raw::c_longlong, + filter_message_or_null: DP_PaintEngineFilterMessageFn, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_index_build( + pe: *mut DP_PaintEngine, + dc: *mut DP_DrawContext, + should_snapshot_fn: DP_PlayerIndexShouldSnapshotFn, + progress_fn: DP_PlayerIndexProgressFn, + user: *mut ::std::os::raw::c_void, + ) -> bool; +} +extern "C" { + pub fn DP_paint_engine_playback_index_load(pe: *mut DP_PaintEngine) -> bool; +} +extern "C" { + pub fn DP_paint_engine_playback_index_message_count( + pe: *mut DP_PaintEngine, + ) -> ::std::os::raw::c_uint; +} +extern "C" { + pub fn DP_paint_engine_playback_index_entry_count(pe: *mut DP_PaintEngine) -> usize; +} +extern "C" { + pub fn DP_paint_engine_playback_index_thumbnail_at( + pe: *mut DP_PaintEngine, + index: usize, + out_error: *mut bool, + ) -> *mut DP_Image; +} +extern "C" { + pub fn DP_paint_engine_playback_dump_step( + pe: *mut DP_PaintEngine, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_dump_jump_previous_reset( + pe: *mut DP_PaintEngine, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_dump_jump_next_reset( + pe: *mut DP_PaintEngine, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_dump_jump( + pe: *mut DP_PaintEngine, + position: ::std::os::raw::c_longlong, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_PlayerResult; +} +extern "C" { + pub fn DP_paint_engine_playback_flush( + pe: *mut DP_PaintEngine, + push_message: DP_PaintEnginePushMessageFn, + user: *mut ::std::os::raw::c_void, + ) -> bool; +} +extern "C" { + pub fn DP_paint_engine_playback_close(pe: *mut DP_PaintEngine) -> bool; +} +pub const DP_SAVE_IMAGE_UNKNOWN: DP_SaveImageType = 0; +pub const DP_SAVE_IMAGE_ORA: DP_SaveImageType = 1; +pub const DP_SAVE_IMAGE_PNG: DP_SaveImageType = 2; +pub const DP_SAVE_IMAGE_JPEG: DP_SaveImageType = 3; +pub const DP_SAVE_IMAGE_PSD: DP_SaveImageType = 4; +pub const DP_SAVE_IMAGE_WEBP: DP_SaveImageType = 5; +pub type DP_SaveImageType = ::std::os::raw::c_uint; +pub const DP_SAVE_RESULT_SUCCESS: DP_SaveResult = 0; +pub const DP_SAVE_RESULT_BAD_ARGUMENTS: DP_SaveResult = 1; +pub const DP_SAVE_RESULT_UNKNOWN_FORMAT: DP_SaveResult = 2; +pub const DP_SAVE_RESULT_FLATTEN_ERROR: DP_SaveResult = 3; +pub const DP_SAVE_RESULT_OPEN_ERROR: DP_SaveResult = 4; +pub const DP_SAVE_RESULT_WRITE_ERROR: DP_SaveResult = 5; +pub const DP_SAVE_RESULT_INTERNAL_ERROR: DP_SaveResult = 6; +pub const DP_SAVE_RESULT_CANCEL: DP_SaveResult = 7; +pub type DP_SaveResult = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct DP_SaveFormat { + pub title: *const ::std::os::raw::c_char, + pub extensions: *mut *const ::std::os::raw::c_char, +} +#[test] +fn bindgen_test_layout_DP_SaveFormat() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(DP_SaveFormat)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(DP_SaveFormat)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).title) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(DP_SaveFormat), + "::", + stringify!(title) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).extensions) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(DP_SaveFormat), + "::", + stringify!(extensions) + ) + ); +} +extern "C" { + pub fn DP_save_supported_formats() -> *const DP_SaveFormat; +} +pub type DP_SaveBakeAnnotationFn = ::std::option::Option< + unsafe extern "C" fn( + user: *mut ::std::os::raw::c_void, + a: *mut DP_Annotation, + out: *mut ::std::os::raw::c_uchar, + ) -> bool, +>; +extern "C" { + pub fn DP_save_image_type_guess(path: *const ::std::os::raw::c_char) -> DP_SaveImageType; +} +extern "C" { + pub fn DP_save( + cs: *mut DP_CanvasState, + dc: *mut DP_DrawContext, + type_: DP_SaveImageType, + path: *const ::std::os::raw::c_char, + bake_annotation: DP_SaveBakeAnnotationFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_SaveResult; +} +pub type DP_SaveAnimationProgressFn = ::std::option::Option< + unsafe extern "C" fn(user: *mut ::std::os::raw::c_void, progress: f64) -> bool, +>; +extern "C" { + pub fn DP_save_animation_frames( + cs: *mut DP_CanvasState, + path: *const ::std::os::raw::c_char, + crop: *mut DP_Rect, + start: ::std::os::raw::c_int, + end_inclusive: ::std::os::raw::c_int, + progress_fn: DP_SaveAnimationProgressFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_SaveResult; +} +extern "C" { + pub fn DP_save_animation_zip( + cs: *mut DP_CanvasState, + path: *const ::std::os::raw::c_char, + crop: *mut DP_Rect, + start: ::std::os::raw::c_int, + end_inclusive: ::std::os::raw::c_int, + progress_fn: DP_SaveAnimationProgressFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_SaveResult; +} +extern "C" { + pub fn DP_save_animation_gif( + cs: *mut DP_CanvasState, + path: *const ::std::os::raw::c_char, + crop: *mut DP_Rect, + start: ::std::os::raw::c_int, + end_inclusive: ::std::os::raw::c_int, + framerate: ::std::os::raw::c_int, + progress_fn: DP_SaveAnimationProgressFn, + user: *mut ::std::os::raw::c_void, + ) -> DP_SaveResult; +} pub const DP_ACCESS_TIER_OPERATOR: DP_AccessTier = 0; pub const DP_ACCESS_TIER_TRUSTED: DP_AccessTier = 1; pub const DP_ACCESS_TIER_AUTHENTICATED: DP_AccessTier = 2; diff --git a/src/drawdance/rust/wrapper.h b/src/drawdance/rust/wrapper.h index 35acc812bb..0127b4cdee 100644 --- a/src/drawdance/rust/wrapper.h +++ b/src/drawdance/rust/wrapper.h @@ -13,10 +13,12 @@ #include #include #include -#include #include #include #include +#include +#include +#include #include #include #include diff --git a/src/libclient/CMakeLists.txt b/src/libclient/CMakeLists.txt index 341817161c..a2856f1312 100644 --- a/src/libclient/CMakeLists.txt +++ b/src/libclient/CMakeLists.txt @@ -274,7 +274,7 @@ target_link_libraries(dpclient cmake-config PUBLIC dpshared - drawdance + drawdance_client ${QT_PACKAGE_NAME}::Core ${QT_PACKAGE_NAME}::Network ${QT_PACKAGE_NAME}::Gui diff --git a/src/libclient/document.cpp b/src/libclient/document.cpp index 7d419e8cb5..62a00fca9f 100644 --- a/src/libclient/document.cpp +++ b/src/libclient/document.cpp @@ -1,11 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "libclient/document.h" +extern "C" { +#include +} #include "libclient/canvas/canvasmodel.h" #include "libclient/canvas/layerlist.h" #include "libclient/canvas/paintengine.h" #include "libclient/canvas/selectionmodel.h" #include "libclient/canvas/transformmodel.h" #include "libclient/canvas/userlist.h" +#include "libclient/document.h" #include "libclient/export/canvassaverrunnable.h" #include "libclient/settings.h" #include "libclient/tools/selection.h" diff --git a/src/libclient/document.h b/src/libclient/document.h index 5fe34c81e1..e6faf5d20e 100644 --- a/src/libclient/document.h +++ b/src/libclient/document.h @@ -2,8 +2,8 @@ #ifndef DRAWPILE_DOCUMENT_H #define DRAWPILE_DOCUMENT_H extern "C" { -#include -#include +#include +#include #include #include } @@ -13,7 +13,6 @@ extern "C" { #include "libclient/net/banlistmodel.h" #include "libclient/net/client.h" #include "libclient/net/message.h" -#include "libshared/util/qtcompat.h" #include #include #ifdef Q_OS_ANDROID diff --git a/src/libclient/drawdance/canvasstate.cpp b/src/libclient/drawdance/canvasstate.cpp index 4d0833e47a..4acb52ce17 100644 --- a/src/libclient/drawdance/canvasstate.cpp +++ b/src/libclient/drawdance/canvasstate.cpp @@ -12,6 +12,7 @@ extern "C" { #include #include #include +#include } #include "libclient/canvas/blendmodes.h" #include "libclient/drawdance/canvasstate.h" diff --git a/src/libclient/drawdance/canvasstate.h b/src/libclient/drawdance/canvasstate.h index 67e18c8f4b..6feb6f5995 100644 --- a/src/libclient/drawdance/canvasstate.h +++ b/src/libclient/drawdance/canvasstate.h @@ -3,7 +3,8 @@ #define DRAWDANCE_CANVASSTATE_H extern "C" { #include -#include +#include +#include } #include "libclient/drawdance/annotationlist.h" #include "libclient/drawdance/documentmetadata.h" diff --git a/src/libclient/drawdance/paintengine.cpp b/src/libclient/drawdance/paintengine.cpp index 731d3ca514..07c3117d53 100644 --- a/src/libclient/drawdance/paintengine.cpp +++ b/src/libclient/drawdance/paintengine.cpp @@ -2,6 +2,7 @@ extern "C" { #include #include +#include } #include "libclient/drawdance/aclstate.h" #include "libclient/drawdance/image.h" diff --git a/src/libclient/drawdance/ziparchive.cpp b/src/libclient/drawdance/ziparchive.cpp index bd30cb6328..5c45cc5c0b 100644 --- a/src/libclient/drawdance/ziparchive.cpp +++ b/src/libclient/drawdance/ziparchive.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later extern "C" { -#include +#include } #include "libclient/drawdance/ziparchive.h" diff --git a/src/libclient/export/animationformat.cpp b/src/libclient/export/animationformat.cpp index b3b4ade054..5b3e1a6237 100644 --- a/src/libclient/export/animationformat.cpp +++ b/src/libclient/export/animationformat.cpp @@ -3,7 +3,7 @@ #include #ifdef DP_LIBAV extern "C" { -# include +# include } #endif diff --git a/src/libclient/export/animationsaverrunnable.cpp b/src/libclient/export/animationsaverrunnable.cpp index c7de2dd6dd..3f32beb1c7 100644 --- a/src/libclient/export/animationsaverrunnable.cpp +++ b/src/libclient/export/animationsaverrunnable.cpp @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later extern "C" { #include -#include +#include #ifdef DP_LIBAV -# include +# include #endif } #include "libclient/export/animationformat.h" diff --git a/src/libclient/export/animationsaverrunnable.h b/src/libclient/export/animationsaverrunnable.h index 2e1b963b29..d3be652a39 100644 --- a/src/libclient/export/animationsaverrunnable.h +++ b/src/libclient/export/animationsaverrunnable.h @@ -2,7 +2,7 @@ #ifndef LIBCLIENT_EXPORT_ANIMATIONSAVERRUNNABLE_H #define LIBCLIENT_EXPORT_ANIMATIONSAVERRUNNABLE_H extern "C" { -#include +#include } #include "libclient/drawdance/canvasstate.h" #include diff --git a/src/libclient/export/canvassaverrunnable.cpp b/src/libclient/export/canvassaverrunnable.cpp index 406ccd58aa..4aa368c707 100644 --- a/src/libclient/export/canvassaverrunnable.cpp +++ b/src/libclient/export/canvassaverrunnable.cpp @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "libclient/export/canvassaverrunnable.h" +extern "C" { +#include +} #include "libclient/drawdance/annotation.h" #include "libclient/drawdance/global.h" +#include "libclient/export/canvassaverrunnable.h" #include "libclient/utils/annotations.h" #include #include diff --git a/src/libclient/export/canvassaverrunnable.h b/src/libclient/export/canvassaverrunnable.h index f7fac440be..09775efba5 100644 --- a/src/libclient/export/canvassaverrunnable.h +++ b/src/libclient/export/canvassaverrunnable.h @@ -2,7 +2,7 @@ #ifndef LIBCLIENT_EXPORT_CANVASSAVERRUNNABLE_H #define LIBCLIENT_EXPORT_CANVASSAVERRUNNABLE_H extern "C" { -#include +#include } #include "libclient/drawdance/canvasstate.h" #include diff --git a/src/libclient/import/animationimporter.cpp b/src/libclient/import/animationimporter.cpp index 1adb1a5255..d03e879443 100644 --- a/src/libclient/import/animationimporter.cpp +++ b/src/libclient/import/animationimporter.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later extern "C" { -#include +#include } #include "libclient/drawdance/global.h" #include "libclient/import/animationimporter.h" diff --git a/src/libclient/import/loadresult.h b/src/libclient/import/loadresult.h index 955d2b84ab..739786d330 100644 --- a/src/libclient/import/loadresult.h +++ b/src/libclient/import/loadresult.h @@ -1,7 +1,7 @@ #ifndef LIBCLIENT_IMPORT_LOADMESSAGE_H #define LIBCLIENT_IMPORT_LOADMESSAGE_H extern "C" { -#include +#include } #include diff --git a/src/libclient/utils/images.cpp b/src/libclient/utils/images.cpp index b7d19afc5b..dcf4c9d040 100644 --- a/src/libclient/utils/images.cpp +++ b/src/libclient/utils/images.cpp @@ -4,8 +4,8 @@ #include "cmake-config/config.h" extern "C" { -#include -#include +#include +#include } #include diff --git a/src/libshared/CMakeLists.txt b/src/libshared/CMakeLists.txt index 0c6b537e1a..acd6885df3 100644 --- a/src/libshared/CMakeLists.txt +++ b/src/libshared/CMakeLists.txt @@ -51,7 +51,7 @@ target_link_libraries(dpshared PRIVATE cmake-config PUBLIC - drawdance + drawdance_server ${QT_PACKAGE_NAME}::Core ${QT_PACKAGE_NAME}::Network ${QT_PACKAGE_NAME}::Sql diff --git a/src/thinsrv/CMakeLists.txt b/src/thinsrv/CMakeLists.txt index 31e3f566c8..0a617af721 100644 --- a/src/thinsrv/CMakeLists.txt +++ b/src/thinsrv/CMakeLists.txt @@ -39,7 +39,7 @@ target_link_libraries(${srvlib} PUBLIC cmake-config dpshared dpserver - drawdance + drawdance_server ${QT_PACKAGE_NAME}::Core ${QT_PACKAGE_NAME}::Network ${QT_PACKAGE_NAME}::Sql diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 8c6d6b9c90..b44687cfc0 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -6,19 +6,21 @@ set_property(SOURCE drawpile-cmd/main.c PROPERTY SKIP_AUTOGEN ON) add_cargo_library(dprectool_rust dprectool) add_executable(dprectool dprectool/main.c) dp_sign_executable(dprectool) -target_link_libraries(dprectool PUBLIC dprectool_rust drawdance cmake-config) +target_link_libraries( + dprectool PUBLIC dprectool_rust drawdance_client cmake-config) add_cargo_library(drawpile-cmd_rust drawpilecmd) add_executable(drawpile-cmd drawpile-cmd/main.c) dp_sign_executable(drawpile-cmd) target_link_libraries( - drawpile-cmd PUBLIC drawpile-cmd_rust drawdance cmake-config) + drawpile-cmd PUBLIC drawpile-cmd_rust drawdance_client cmake-config) add_cargo_library(drawpile-timelapse_rust drawpiletimelapse) add_executable( drawpile-timelapse drawpile-timelapse/main.cpp drawpile-timelapse/logo.qrc) dp_sign_executable(drawpile-timelapse) target_link_libraries(drawpile-timelapse PUBLIC - Qt${QT_VERSION_MAJOR}::Core drawpile-timelapse_rust drawdance cmake-config) + Qt${QT_VERSION_MAJOR}::Core drawpile-timelapse_rust drawdance_client + cmake-config) dp_install_executables(TARGETS dprectool drawpile-cmd drawpile-timelapse)